@alpaca-software/40kdc-data 1.0.18 → 1.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"gw-headerless.d.ts","sourceRoot":"","sources":["../../src/import/gw-headerless.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA+ClD,sEAAsE;AACtE,iBAAS,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAuBvD;AAED,UAAU,MAAM;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wEAAwE;IACxE,aAAa,EAAE,OAAO,CAAC;IACvB,iEAAiE;IACjE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7C;AASD,iBAAS,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAuDzD;AAqGD,eAAO,MAAM,mBAAmB,EAAE,aAoIjC,CAAC;AAGF,eAAO,MAAM,UAAU;;;CAGtB,CAAC"}
1
+ {"version":3,"file":"gw-headerless.d.ts","sourceRoot":"","sources":["../../src/import/gw-headerless.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA+ElD,sEAAsE;AACtE,iBAAS,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAwBvD;AAED,UAAU,MAAM;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wEAAwE;IACxE,aAAa,EAAE,OAAO,CAAC;IACvB,iEAAiE;IACjE,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;CAC7C;AASD,iBAAS,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAuDzD;AAqGD,eAAO,MAAM,mBAAmB,EAAE,aAoIjC,CAAC;AAGF,eAAO,MAAM,UAAU;;;CAGtB,CAAC"}
@@ -38,17 +38,52 @@ function parsePts(raw) {
38
38
  const n = Number.parseInt(raw.replace(/,/g, ""), 10);
39
39
  return Number.isNaN(n) ? null : n;
40
40
  }
41
+ /** A line of only `+` characters — the BCP summary block's fence. */
42
+ const RE_PLUS_FENCE = /^\++$/;
43
+ /** A line inside that block identifying it as BCP's (not GW's own `+ …` fence). */
44
+ const RE_BCP_SUMMARY_MARKER = /^\s*(?:Player Name|Team Name|Factions Used|Army Points)\s*:/im;
45
+ /**
46
+ * BCP prepends a `++…++`-fenced summary block (`Player Name:` / `Factions Used:`
47
+ * / `Army Points: N` / …) to text-type lists. It is BCP metadata, not part of the
48
+ * pasted roster, and it derails the body grammar: the fence line gets consumed as
49
+ * the roster title, so the *real* title line (`House Rosecairn (1995 points)`)
50
+ * becomes a phantom unit and its points double the computed total. Strip the
51
+ * leading block when present. Only a block whose fence pair wraps a BCP marker is
52
+ * removed, so a framed GW export's own `+ FACTION KEYWORD:` fence is left intact.
53
+ */
54
+ function stripBcpSummary(text) {
55
+ const lines = text.split(/\r?\n/);
56
+ let open = 0;
57
+ while (open < lines.length && lines[open].trim() === "")
58
+ open += 1;
59
+ if (open >= lines.length || !RE_PLUS_FENCE.test(lines[open].trim()))
60
+ return text;
61
+ let close = -1;
62
+ for (let j = open + 1; j < lines.length; j += 1) {
63
+ if (RE_PLUS_FENCE.test(lines[j].trim())) {
64
+ close = j;
65
+ break;
66
+ }
67
+ }
68
+ if (close === -1)
69
+ return text;
70
+ const block = lines.slice(open + 1, close).join("\n");
71
+ if (!RE_BCP_SUMMARY_MARKER.test(block))
72
+ return text;
73
+ return lines.slice(close + 1).join("\n");
74
+ }
41
75
  /** Accept bullet-bearing plain text that no framed adapter claims. */
42
76
  function headerlessText(decoded) {
43
77
  if (typeof decoded !== "string")
44
78
  return null;
45
- if (!RE_BULLET_ANYWHERE.test(decoded))
79
+ const text = stripBcpSummary(decoded);
80
+ if (!RE_BULLET_ANYWHERE.test(text))
46
81
  return null; // need a bullet
47
- if (decoded.includes("+ FACTION KEYWORD:"))
82
+ if (text.includes("+ FACTION KEYWORD:"))
48
83
  return null; // framed GW → gwAdapter
49
- if (RE_WITH_LINE.test(decoded))
84
+ if (RE_WITH_LINE.test(text))
50
85
  return null; // WTC-full
51
- const lines = decoded.split(/\r?\n/);
86
+ const lines = text.split(/\r?\n/);
52
87
  // ListForge-text's `name - faction - detachment (N Points)` header → defer to
53
88
  // listForgeTextAdapter (registered ahead of us). Mirrors its own matcher so
54
89
  // the two stay disjoint, per the importer's single-match invariant.
@@ -64,7 +99,7 @@ function headerlessText(decoded) {
64
99
  return null;
65
100
  }
66
101
  // Require a `Name (N pts|Points)` line somewhere — the unit/title signature.
67
- return lines.some((l) => RE_PTS_LINE.test(l.trim())) ? decoded : null;
102
+ return lines.some((l) => RE_PTS_LINE.test(l.trim())) ? text : null;
68
103
  }
69
104
  function parseBullet(indent, body) {
70
105
  // Enhancement label first — `Enhancements: X` must not read as a model.
@@ -1 +1 @@
1
- {"version":3,"file":"gw-headerless.js","sourceRoot":"","sources":["../../src/import/gw-headerless.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Headerless plain-text adapter: the GW 40K app's *exported* list (no\n * `++…++` / `+ FACTION KEYWORD:` summary fence), the NewRecruit \"copy as text\"\n * dialect, and the markdown-ish `## Section (N pts)` shape hand-authored lists\n * use. All three share one body grammar; they differ only in cosmetic framing,\n * so a single lenient parser covers them.\n *\n * Shape (any of):\n *\n * ```\n * <list name> (1995 Points) ← title line (consumed, not a unit)\n * World Eaters ← faction (bare preamble line)\n * Berzerker Warband ← detachment (bare preamble line)\n * Strike Force (2,000 Points) ← battle-size metadata\n *\n * CHARACTERS ← ALL-CAPS role section …\n * ## Battleline (200 pts) ← … or `##` markdown section …\n * Epic Hero: ← … or `Title:` colon section\n *\n * Khârn the Betrayer (100 Points) ← unit header: Name (N pts|Points)\n * • Warlord ← annotation\n * • 1x Gorechild ← Nx wargear (single-model unit)\n * • Enhancements: Berzerker Glaive ← enhancement\n * Khorne Berzerkers (180 Points)\n * • 9x Khorne Berzerker ← model group (has ◦ children) …\n * ◦ 8x Bolt pistol ← … children are squad-wide wargear\n * • 4x Intercessor: Bolt rifle ← model group (colon wargear, no children)\n * ```\n *\n * **Model vs wargear** (the crux), unified across dialects: a top-level bullet\n * is a *model group* when it carries a `: wargear` colon **or** is followed by\n * deeper-indented child bullets; its `Nx` count (default 1) adds to the model\n * count. Otherwise it is plain wargear (an `Nx`/bare item) or an annotation\n * (`Warlord`, `… Character`, `Enhancements: …`).\n *\n * **Faction / detachment**: the GW export lists them as bare lines between the\n * title and the first section (`World Eaters` / `Berzerker Warband`). The first\n * two such preamble lines are captured so `resolve` can scope to them.\n *\n * **Disjointness**: this adapter is the fallback for bullet-bearing text that\n * the framed adapters reject — it declines input carrying the GW\n * `+ FACTION KEYWORD:` fence (→ {@link gwAdapter}), the NewRecruit\n * `# ++ Army Roster ++` header (→ newrecruit-simple), or WTC `N with` body\n * lines, and requires at least one `•`/`◦` bullet.\n *\n * @packageDocumentation\n */\nimport type { FormatAdapter } from \"./adapter.js\";\nimport type { ParsedRoster, ParsedUnit, ParsedWargear } from \"./types.js\";\nimport { inferBattleSizeRaw } from \"./newrecruit-text.js\";\n\nconst CHARACTERS_SECTION = \"CHARACTERS\";\nconst ALLIED_SECTION = \"ALLIED UNITS\";\nconst CHARACTER_SUFFIX = \" Character\";\nconst WARLORD_MARKER = \"Warlord\";\n\n/** Title / unit header: `Name (N pts|Points)` with an optional trailing comment\n * (the GW export sometimes appends TO notes). Points may carry thousands\n * commas. Case-insensitive `pts`/`points`. */\nconst RE_PTS_LINE = /^(.+?)\\s*\\(\\s*([\\d,]+)\\s*(?:pts?|points?)\\s*\\).*$/i;\n/** `## Section [ (N pts) ]` markdown header. */\nconst RE_MD_SECTION = /^#{1,6}\\s*(.+?)\\s*$/;\n/** ALL-CAPS role section (`CHARACTERS`, `OTHER DATASHEETS`, …). */\nconst RE_CAPS_SECTION = /^[A-Z][A-Z0-9 \\-/&]+$/;\n/** `Title:` colon section (`Epic Hero:`, `Battleline:`). */\nconst RE_COLON_SECTION = /^([A-Za-z][\\w /&-]*):\\s*$/;\n/** Bullet line: leading indent, a `•` or `◦` marker, then the body. */\nconst RE_BULLET = /^([\\t ]*)[•◦]\\s*(.+?)\\s*$/u;\nconst RE_NX_PREFIX = /^(\\d+)x\\s+(.+)$/i;\n/** Inline enhancement annotation: `Name (+N pts)`. */\nconst RE_ENHANCEMENT_ANNOT = /^(.+?)\\s*\\(\\+\\s*(\\d+)\\s*pts?\\s*\\)\\s*$/i;\n/** `Enhancements: X` / `E: X` enhancement bullet. */\nconst RE_ENHANCEMENT_LABEL = /^(?:e|enh|enhancement|enhancements)\\s*:\\s*(.+)$/i;\nconst RE_WITH_LINE = /^[\\t ]*\\d+\\s+with\\b/m;\nconst RE_BULLET_ANYWHERE = /^[\\t ]*[•◦]/mu;\n/** ListForge-text first line: `<name> - <faction> - <detachment> (N Points)`.\n * Used only to *decline* — that framed header belongs to `listForgeTextAdapter`,\n * which runs ahead of us; declining keeps the matchers mutually exclusive. */\nconst RE_LISTFORGE_FIRST_LINE = /^(.+)\\s\\(\\s*\\d+\\s*Points?\\s*\\)\\s*$/i;\n\n/** Battle-size labels that look like unit headers (`Strike Force (2,000 Points)`)\n * but are army metadata, not datasheets. */\nconst BATTLE_SIZE_NAMES = new Set([\n \"combat patrol\",\n \"incursion\",\n \"strike force\",\n \"onslaught\",\n]);\n\nfunction parsePts(raw: string): number | null {\n const n = Number.parseInt(raw.replace(/,/g, \"\"), 10);\n return Number.isNaN(n) ? null : n;\n}\n\n/** Accept bullet-bearing plain text that no framed adapter claims. */\nfunction headerlessText(decoded: unknown): string | null {\n if (typeof decoded !== \"string\") return null;\n if (!RE_BULLET_ANYWHERE.test(decoded)) return null; // need a bullet\n if (decoded.includes(\"+ FACTION KEYWORD:\")) return null; // framed GW → gwAdapter\n if (RE_WITH_LINE.test(decoded)) return null; // WTC-full\n const lines = decoded.split(/\\r?\\n/);\n // ListForge-text's `name - faction - detachment (N Points)` header → defer to\n // listForgeTextAdapter (registered ahead of us). Mirrors its own matcher so\n // the two stay disjoint, per the importer's single-match invariant.\n const firstNonBlank = lines.find((l) => l.trim().length > 0);\n const lf = firstNonBlank && RE_LISTFORGE_FIRST_LINE.exec(firstNonBlank.trim());\n if (lf && lf[1].split(\" - \").length >= 3) return null;\n // NewRecruit `# ++ Army Roster ++` → newrecruit-simple.\n if (\n lines.some((l) => {\n const t = l.trim();\n return t.startsWith(\"# ++\") && t.includes(\"Army Roster\");\n })\n ) {\n return null;\n }\n // Require a `Name (N pts|Points)` line somewhere — the unit/title signature.\n return lines.some((l) => RE_PTS_LINE.test(l.trim())) ? decoded : null;\n}\n\ninterface Bullet {\n indent: number;\n count: number | null;\n /** Model/wargear name (after any `Nx` and before any `: wargear`). */\n name: string;\n /** Comma-separated wargear listed after a `:` on a model bullet. */\n colon_wargear: string | null;\n /** True for `Warlord` / `… Character` / `Enhancements:` annotations. */\n is_annotation: boolean;\n /** `[name, points]` when this bullet declares an enhancement. */\n enhancement: [string, number | null] | null;\n}\n\ninterface UnitAcc {\n raw_name: string;\n displayed_pts: number | null;\n is_character_section: boolean;\n bullets: Bullet[];\n}\n\nfunction parseBullet(indent: number, body: string): Bullet {\n // Enhancement label first — `Enhancements: X` must not read as a model.\n const label = RE_ENHANCEMENT_LABEL.exec(body);\n if (label) {\n return {\n indent,\n count: null,\n name: \"\",\n colon_wargear: null,\n is_annotation: true,\n enhancement: [label[1].trim(), null],\n };\n }\n\n const nx = RE_NX_PREFIX.exec(body);\n const count = nx ? Number.parseInt(nx[1], 10) : null;\n const rest = (nx ? nx[2] : body).trim();\n\n // `Name (+N pts)` enhancement annotation.\n const annot = RE_ENHANCEMENT_ANNOT.exec(rest);\n if (annot) {\n const pts = Number.parseInt(annot[2], 10);\n return {\n indent,\n count,\n name: rest,\n colon_wargear: null,\n is_annotation: true,\n enhancement: [annot[1].trim(), Number.isNaN(pts) ? null : pts],\n };\n }\n\n // `ModelType: w1, w2` — a model bullet with inline wargear.\n const idx = rest.indexOf(\":\");\n if (idx >= 0) {\n const wargear = rest.slice(idx + 1).trim();\n return {\n indent,\n count,\n name: rest.slice(0, idx).trim(),\n colon_wargear: wargear.length > 0 ? wargear : null,\n is_annotation: false,\n enhancement: null,\n };\n }\n\n // Bare token: annotation iff it has no count (Warlord / Character / wargear).\n return {\n indent,\n count,\n name: rest,\n colon_wargear: null,\n is_annotation: count === null,\n enhancement: null,\n };\n}\n\nfunction finishUnit(acc: UnitAcc): ParsedUnit {\n const topIndent = acc.bullets.length\n ? Math.min(...acc.bullets.map((b) => b.indent))\n : 0;\n\n const wargear = new Map<string, number>();\n const addWargear = (raw_name: string, count: number): void => {\n const name = raw_name.trim();\n if (!name) return;\n wargear.set(name, (wargear.get(name) ?? 0) + count);\n };\n\n let model_count = 0;\n let is_warlord = false;\n let is_character = acc.is_character_section;\n let enhancement_raw_name: string | null = null;\n let enhancement_points: number | null = null;\n\n for (let i = 0; i < acc.bullets.length; i += 1) {\n const b = acc.bullets[i];\n\n // Child bullet: a model group's squad-wide wargear (count already total).\n if (b.indent > topIndent) {\n addWargear(b.name, b.count ?? 1);\n continue;\n }\n\n // Enhancement annotation (`Enhancements: X` or `X (+N pts)`).\n if (b.enhancement) {\n if (enhancement_raw_name === null) {\n enhancement_raw_name = b.enhancement[0];\n enhancement_points = b.enhancement[1];\n }\n continue;\n }\n\n // Model with inline `: wargear` (the `##`/fixture dialect).\n if (b.colon_wargear !== null) {\n const n = b.count ?? 1;\n model_count += n;\n for (const item of b.colon_wargear.split(\",\").map((s) => s.trim())) {\n if (item) addWargear(item, n);\n }\n continue;\n }\n\n // Model group: top-level bullet followed by deeper child bullets.\n const next = acc.bullets[i + 1];\n if (next && next.indent > topIndent) {\n model_count += b.count ?? 1;\n continue;\n }\n\n // Annotation (no count): Warlord / Character flags, else bare wargear.\n if (b.is_annotation) {\n const leftover: string[] = [];\n for (const token of b.name.split(\",\").map((t) => t.trim())) {\n if (!token) continue;\n if (token === WARLORD_MARKER) {\n is_warlord = true;\n } else if (token.endsWith(CHARACTER_SUFFIX)) {\n is_character = true;\n } else {\n leftover.push(token);\n }\n }\n for (const token of leftover) addWargear(token, 1);\n continue;\n }\n\n // Plain `Nx` wargear on a single-model unit.\n addWargear(b.name, b.count ?? 1);\n }\n\n if (model_count === 0) model_count = 1;\n\n let points = acc.displayed_pts;\n if (acc.displayed_pts !== null && enhancement_points !== null) {\n points = Math.max(0, acc.displayed_pts - enhancement_points);\n }\n\n return {\n raw_name: acc.raw_name,\n is_character,\n model_count,\n points,\n is_warlord,\n enhancement_raw_name,\n enhancement_points,\n wargear: [...wargear].map(\n ([raw_name, count]): ParsedWargear => ({ raw_name, count }),\n ),\n };\n}\n\nfunction isBattleSize(name: string): boolean {\n return BATTLE_SIZE_NAMES.has(name.trim().toLowerCase());\n}\n\nexport const gwHeaderlessAdapter: FormatAdapter = {\n // Provenance: a GW-family plain-text export. Reuses the `gw` id so no schema\n // churn is needed for a new label (mirrors the Rust adapter).\n id: \"gw\",\n\n matches(decoded: unknown): boolean {\n return headerlessText(decoded) !== null;\n },\n\n parse(decoded: unknown): ParsedRoster {\n const text = headerlessText(decoded);\n if (text === null) {\n throw new Error(\"gw-headerless: not a headerless plain-text list\");\n }\n\n let name = \"Imported roster\";\n let declared_limit: number | null = null;\n let battle_size_raw: string | null = null;\n const units: ParsedUnit[] = [];\n let current: UnitAcc | null = null;\n let section: string | null = null;\n let allied = 0;\n let consumed_title = false;\n // The GW app export lists faction then detachment as bare lines between the\n // title and the first section. Capture the first two so `resolve` can scope\n // to them; later bare lines (stray notes) are ignored.\n let faction_raw_name: string | null = null;\n const detachment_raw_names: string[] = [];\n\n const flush = (): void => {\n if (current) {\n units.push(finishUnit(current));\n current = null;\n }\n };\n\n for (const rawLine of text.split(\"\\n\")) {\n const raw = rawLine.replace(/\\r+$/, \"\");\n const line = raw.trim();\n if (!line) continue;\n\n // Bullets attach to the open unit.\n const bullet = RE_BULLET.exec(raw);\n if (bullet) {\n if (current) current.bullets.push(parseBullet(bullet[1].length, bullet[2]));\n continue;\n }\n\n // GW export footer.\n if (line.startsWith(\"Exported with\")) continue;\n\n // `## Section` markdown header (strip an optional `(N pts)` tail).\n const md = RE_MD_SECTION.exec(line);\n if (md) {\n flush();\n const pts = RE_PTS_LINE.exec(md[1]);\n section = pts ? pts[1].trim() : md[1].trim();\n continue;\n }\n\n // First `Name (N pts|Points)` line is the roster title, not a unit.\n const pts = RE_PTS_LINE.exec(line);\n if (pts) {\n const header_name = pts[1].trim();\n const points = parsePts(pts[2]);\n if (!consumed_title && current === null && units.length === 0) {\n consumed_title = true;\n name = header_name;\n declared_limit = points;\n continue;\n }\n // Battle-size metadata (`Strike Force (2,000 Points)`).\n if (isBattleSize(header_name)) {\n battle_size_raw = line;\n if (declared_limit === null) declared_limit = points;\n continue;\n }\n // A real unit header.\n flush();\n const inChars = section?.toLowerCase() === CHARACTERS_SECTION.toLowerCase();\n if (section === ALLIED_SECTION) allied += 1;\n current = {\n raw_name: header_name,\n displayed_pts: points,\n is_character_section: inChars,\n bullets: [],\n };\n continue;\n }\n\n // Section headers without points (ALL-CAPS role, `Title:` colon).\n if (RE_CAPS_SECTION.test(line) || RE_COLON_SECTION.test(line)) {\n flush();\n section = line.replace(/:\\s*$/, \"\").trim();\n continue;\n }\n\n // Anything else (faction/detachment preamble, stray notes).\n if (!consumed_title && current === null && units.length === 0) {\n // Very first content line with no `(N pts)` title → use as name.\n consumed_title = true;\n name = line;\n } else if (current === null && units.length === 0) {\n // Preamble after the title, before the first unit: faction then\n // detachment. Names are resolved (and warned on miss) downstream.\n if (faction_raw_name === null) {\n faction_raw_name = line;\n } else if (detachment_raw_names.length === 0) {\n detachment_raw_names.push(line);\n }\n }\n }\n flush();\n\n let total_computed = 0;\n for (const u of units) {\n total_computed += (u.points ?? 0) + (u.enhancement_points ?? 0);\n }\n\n return {\n name,\n generated_by: null,\n faction_raw_name,\n detachment_raw_names,\n battle_size_raw: battle_size_raw ?? inferBattleSizeRaw(declared_limit),\n declared_limit,\n total_reported: null,\n total_computed,\n units,\n multi_force: allied > 0,\n };\n },\n};\n\n// Internals re-exported for unit tests.\nexport const _internals = {\n headerlessText,\n parseBullet,\n};\n"]}
1
+ {"version":3,"file":"gw-headerless.js","sourceRoot":"","sources":["../../src/import/gw-headerless.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Headerless plain-text adapter: the GW 40K app's *exported* list (no\n * `++…++` / `+ FACTION KEYWORD:` summary fence), the NewRecruit \"copy as text\"\n * dialect, and the markdown-ish `## Section (N pts)` shape hand-authored lists\n * use. All three share one body grammar; they differ only in cosmetic framing,\n * so a single lenient parser covers them.\n *\n * Shape (any of):\n *\n * ```\n * <list name> (1995 Points) ← title line (consumed, not a unit)\n * World Eaters ← faction (bare preamble line)\n * Berzerker Warband ← detachment (bare preamble line)\n * Strike Force (2,000 Points) ← battle-size metadata\n *\n * CHARACTERS ← ALL-CAPS role section …\n * ## Battleline (200 pts) ← … or `##` markdown section …\n * Epic Hero: ← … or `Title:` colon section\n *\n * Khârn the Betrayer (100 Points) ← unit header: Name (N pts|Points)\n * • Warlord ← annotation\n * • 1x Gorechild ← Nx wargear (single-model unit)\n * • Enhancements: Berzerker Glaive ← enhancement\n * Khorne Berzerkers (180 Points)\n * • 9x Khorne Berzerker ← model group (has ◦ children) …\n * ◦ 8x Bolt pistol ← … children are squad-wide wargear\n * • 4x Intercessor: Bolt rifle ← model group (colon wargear, no children)\n * ```\n *\n * **Model vs wargear** (the crux), unified across dialects: a top-level bullet\n * is a *model group* when it carries a `: wargear` colon **or** is followed by\n * deeper-indented child bullets; its `Nx` count (default 1) adds to the model\n * count. Otherwise it is plain wargear (an `Nx`/bare item) or an annotation\n * (`Warlord`, `… Character`, `Enhancements: …`).\n *\n * **Faction / detachment**: the GW export lists them as bare lines between the\n * title and the first section (`World Eaters` / `Berzerker Warband`). The first\n * two such preamble lines are captured so `resolve` can scope to them.\n *\n * **Disjointness**: this adapter is the fallback for bullet-bearing text that\n * the framed adapters reject — it declines input carrying the GW\n * `+ FACTION KEYWORD:` fence (→ {@link gwAdapter}), the NewRecruit\n * `# ++ Army Roster ++` header (→ newrecruit-simple), or WTC `N with` body\n * lines, and requires at least one `•`/`◦` bullet.\n *\n * @packageDocumentation\n */\nimport type { FormatAdapter } from \"./adapter.js\";\nimport type { ParsedRoster, ParsedUnit, ParsedWargear } from \"./types.js\";\nimport { inferBattleSizeRaw } from \"./newrecruit-text.js\";\n\nconst CHARACTERS_SECTION = \"CHARACTERS\";\nconst ALLIED_SECTION = \"ALLIED UNITS\";\nconst CHARACTER_SUFFIX = \" Character\";\nconst WARLORD_MARKER = \"Warlord\";\n\n/** Title / unit header: `Name (N pts|Points)` with an optional trailing comment\n * (the GW export sometimes appends TO notes). Points may carry thousands\n * commas. Case-insensitive `pts`/`points`. */\nconst RE_PTS_LINE = /^(.+?)\\s*\\(\\s*([\\d,]+)\\s*(?:pts?|points?)\\s*\\).*$/i;\n/** `## Section [ (N pts) ]` markdown header. */\nconst RE_MD_SECTION = /^#{1,6}\\s*(.+?)\\s*$/;\n/** ALL-CAPS role section (`CHARACTERS`, `OTHER DATASHEETS`, …). */\nconst RE_CAPS_SECTION = /^[A-Z][A-Z0-9 \\-/&]+$/;\n/** `Title:` colon section (`Epic Hero:`, `Battleline:`). */\nconst RE_COLON_SECTION = /^([A-Za-z][\\w /&-]*):\\s*$/;\n/** Bullet line: leading indent, a `•` or `◦` marker, then the body. */\nconst RE_BULLET = /^([\\t ]*)[•◦]\\s*(.+?)\\s*$/u;\nconst RE_NX_PREFIX = /^(\\d+)x\\s+(.+)$/i;\n/** Inline enhancement annotation: `Name (+N pts)`. */\nconst RE_ENHANCEMENT_ANNOT = /^(.+?)\\s*\\(\\+\\s*(\\d+)\\s*pts?\\s*\\)\\s*$/i;\n/** `Enhancements: X` / `E: X` enhancement bullet. */\nconst RE_ENHANCEMENT_LABEL = /^(?:e|enh|enhancement|enhancements)\\s*:\\s*(.+)$/i;\nconst RE_WITH_LINE = /^[\\t ]*\\d+\\s+with\\b/m;\nconst RE_BULLET_ANYWHERE = /^[\\t ]*[•◦]/mu;\n/** ListForge-text first line: `<name> - <faction> - <detachment> (N Points)`.\n * Used only to *decline* — that framed header belongs to `listForgeTextAdapter`,\n * which runs ahead of us; declining keeps the matchers mutually exclusive. */\nconst RE_LISTFORGE_FIRST_LINE = /^(.+)\\s\\(\\s*\\d+\\s*Points?\\s*\\)\\s*$/i;\n\n/** Battle-size labels that look like unit headers (`Strike Force (2,000 Points)`)\n * but are army metadata, not datasheets. */\nconst BATTLE_SIZE_NAMES = new Set([\n \"combat patrol\",\n \"incursion\",\n \"strike force\",\n \"onslaught\",\n]);\n\nfunction parsePts(raw: string): number | null {\n const n = Number.parseInt(raw.replace(/,/g, \"\"), 10);\n return Number.isNaN(n) ? null : n;\n}\n\n/** A line of only `+` characters — the BCP summary block's fence. */\nconst RE_PLUS_FENCE = /^\\++$/;\n/** A line inside that block identifying it as BCP's (not GW's own `+ …` fence). */\nconst RE_BCP_SUMMARY_MARKER = /^\\s*(?:Player Name|Team Name|Factions Used|Army Points)\\s*:/im;\n\n/**\n * BCP prepends a `++…++`-fenced summary block (`Player Name:` / `Factions Used:`\n * / `Army Points: N` / …) to text-type lists. It is BCP metadata, not part of the\n * pasted roster, and it derails the body grammar: the fence line gets consumed as\n * the roster title, so the *real* title line (`House Rosecairn (1995 points)`)\n * becomes a phantom unit and its points double the computed total. Strip the\n * leading block when present. Only a block whose fence pair wraps a BCP marker is\n * removed, so a framed GW export's own `+ FACTION KEYWORD:` fence is left intact.\n */\nfunction stripBcpSummary(text: string): string {\n const lines = text.split(/\\r?\\n/);\n let open = 0;\n while (open < lines.length && lines[open].trim() === \"\") open += 1;\n if (open >= lines.length || !RE_PLUS_FENCE.test(lines[open].trim())) return text;\n let close = -1;\n for (let j = open + 1; j < lines.length; j += 1) {\n if (RE_PLUS_FENCE.test(lines[j].trim())) {\n close = j;\n break;\n }\n }\n if (close === -1) return text;\n const block = lines.slice(open + 1, close).join(\"\\n\");\n if (!RE_BCP_SUMMARY_MARKER.test(block)) return text;\n return lines.slice(close + 1).join(\"\\n\");\n}\n\n/** Accept bullet-bearing plain text that no framed adapter claims. */\nfunction headerlessText(decoded: unknown): string | null {\n if (typeof decoded !== \"string\") return null;\n const text = stripBcpSummary(decoded);\n if (!RE_BULLET_ANYWHERE.test(text)) return null; // need a bullet\n if (text.includes(\"+ FACTION KEYWORD:\")) return null; // framed GW → gwAdapter\n if (RE_WITH_LINE.test(text)) return null; // WTC-full\n const lines = text.split(/\\r?\\n/);\n // ListForge-text's `name - faction - detachment (N Points)` header → defer to\n // listForgeTextAdapter (registered ahead of us). Mirrors its own matcher so\n // the two stay disjoint, per the importer's single-match invariant.\n const firstNonBlank = lines.find((l) => l.trim().length > 0);\n const lf = firstNonBlank && RE_LISTFORGE_FIRST_LINE.exec(firstNonBlank.trim());\n if (lf && lf[1].split(\" - \").length >= 3) return null;\n // NewRecruit `# ++ Army Roster ++` → newrecruit-simple.\n if (\n lines.some((l) => {\n const t = l.trim();\n return t.startsWith(\"# ++\") && t.includes(\"Army Roster\");\n })\n ) {\n return null;\n }\n // Require a `Name (N pts|Points)` line somewhere — the unit/title signature.\n return lines.some((l) => RE_PTS_LINE.test(l.trim())) ? text : null;\n}\n\ninterface Bullet {\n indent: number;\n count: number | null;\n /** Model/wargear name (after any `Nx` and before any `: wargear`). */\n name: string;\n /** Comma-separated wargear listed after a `:` on a model bullet. */\n colon_wargear: string | null;\n /** True for `Warlord` / `… Character` / `Enhancements:` annotations. */\n is_annotation: boolean;\n /** `[name, points]` when this bullet declares an enhancement. */\n enhancement: [string, number | null] | null;\n}\n\ninterface UnitAcc {\n raw_name: string;\n displayed_pts: number | null;\n is_character_section: boolean;\n bullets: Bullet[];\n}\n\nfunction parseBullet(indent: number, body: string): Bullet {\n // Enhancement label first — `Enhancements: X` must not read as a model.\n const label = RE_ENHANCEMENT_LABEL.exec(body);\n if (label) {\n return {\n indent,\n count: null,\n name: \"\",\n colon_wargear: null,\n is_annotation: true,\n enhancement: [label[1].trim(), null],\n };\n }\n\n const nx = RE_NX_PREFIX.exec(body);\n const count = nx ? Number.parseInt(nx[1], 10) : null;\n const rest = (nx ? nx[2] : body).trim();\n\n // `Name (+N pts)` enhancement annotation.\n const annot = RE_ENHANCEMENT_ANNOT.exec(rest);\n if (annot) {\n const pts = Number.parseInt(annot[2], 10);\n return {\n indent,\n count,\n name: rest,\n colon_wargear: null,\n is_annotation: true,\n enhancement: [annot[1].trim(), Number.isNaN(pts) ? null : pts],\n };\n }\n\n // `ModelType: w1, w2` — a model bullet with inline wargear.\n const idx = rest.indexOf(\":\");\n if (idx >= 0) {\n const wargear = rest.slice(idx + 1).trim();\n return {\n indent,\n count,\n name: rest.slice(0, idx).trim(),\n colon_wargear: wargear.length > 0 ? wargear : null,\n is_annotation: false,\n enhancement: null,\n };\n }\n\n // Bare token: annotation iff it has no count (Warlord / Character / wargear).\n return {\n indent,\n count,\n name: rest,\n colon_wargear: null,\n is_annotation: count === null,\n enhancement: null,\n };\n}\n\nfunction finishUnit(acc: UnitAcc): ParsedUnit {\n const topIndent = acc.bullets.length\n ? Math.min(...acc.bullets.map((b) => b.indent))\n : 0;\n\n const wargear = new Map<string, number>();\n const addWargear = (raw_name: string, count: number): void => {\n const name = raw_name.trim();\n if (!name) return;\n wargear.set(name, (wargear.get(name) ?? 0) + count);\n };\n\n let model_count = 0;\n let is_warlord = false;\n let is_character = acc.is_character_section;\n let enhancement_raw_name: string | null = null;\n let enhancement_points: number | null = null;\n\n for (let i = 0; i < acc.bullets.length; i += 1) {\n const b = acc.bullets[i];\n\n // Child bullet: a model group's squad-wide wargear (count already total).\n if (b.indent > topIndent) {\n addWargear(b.name, b.count ?? 1);\n continue;\n }\n\n // Enhancement annotation (`Enhancements: X` or `X (+N pts)`).\n if (b.enhancement) {\n if (enhancement_raw_name === null) {\n enhancement_raw_name = b.enhancement[0];\n enhancement_points = b.enhancement[1];\n }\n continue;\n }\n\n // Model with inline `: wargear` (the `##`/fixture dialect).\n if (b.colon_wargear !== null) {\n const n = b.count ?? 1;\n model_count += n;\n for (const item of b.colon_wargear.split(\",\").map((s) => s.trim())) {\n if (item) addWargear(item, n);\n }\n continue;\n }\n\n // Model group: top-level bullet followed by deeper child bullets.\n const next = acc.bullets[i + 1];\n if (next && next.indent > topIndent) {\n model_count += b.count ?? 1;\n continue;\n }\n\n // Annotation (no count): Warlord / Character flags, else bare wargear.\n if (b.is_annotation) {\n const leftover: string[] = [];\n for (const token of b.name.split(\",\").map((t) => t.trim())) {\n if (!token) continue;\n if (token === WARLORD_MARKER) {\n is_warlord = true;\n } else if (token.endsWith(CHARACTER_SUFFIX)) {\n is_character = true;\n } else {\n leftover.push(token);\n }\n }\n for (const token of leftover) addWargear(token, 1);\n continue;\n }\n\n // Plain `Nx` wargear on a single-model unit.\n addWargear(b.name, b.count ?? 1);\n }\n\n if (model_count === 0) model_count = 1;\n\n let points = acc.displayed_pts;\n if (acc.displayed_pts !== null && enhancement_points !== null) {\n points = Math.max(0, acc.displayed_pts - enhancement_points);\n }\n\n return {\n raw_name: acc.raw_name,\n is_character,\n model_count,\n points,\n is_warlord,\n enhancement_raw_name,\n enhancement_points,\n wargear: [...wargear].map(\n ([raw_name, count]): ParsedWargear => ({ raw_name, count }),\n ),\n };\n}\n\nfunction isBattleSize(name: string): boolean {\n return BATTLE_SIZE_NAMES.has(name.trim().toLowerCase());\n}\n\nexport const gwHeaderlessAdapter: FormatAdapter = {\n // Provenance: a GW-family plain-text export. Reuses the `gw` id so no schema\n // churn is needed for a new label (mirrors the Rust adapter).\n id: \"gw\",\n\n matches(decoded: unknown): boolean {\n return headerlessText(decoded) !== null;\n },\n\n parse(decoded: unknown): ParsedRoster {\n const text = headerlessText(decoded);\n if (text === null) {\n throw new Error(\"gw-headerless: not a headerless plain-text list\");\n }\n\n let name = \"Imported roster\";\n let declared_limit: number | null = null;\n let battle_size_raw: string | null = null;\n const units: ParsedUnit[] = [];\n let current: UnitAcc | null = null;\n let section: string | null = null;\n let allied = 0;\n let consumed_title = false;\n // The GW app export lists faction then detachment as bare lines between the\n // title and the first section. Capture the first two so `resolve` can scope\n // to them; later bare lines (stray notes) are ignored.\n let faction_raw_name: string | null = null;\n const detachment_raw_names: string[] = [];\n\n const flush = (): void => {\n if (current) {\n units.push(finishUnit(current));\n current = null;\n }\n };\n\n for (const rawLine of text.split(\"\\n\")) {\n const raw = rawLine.replace(/\\r+$/, \"\");\n const line = raw.trim();\n if (!line) continue;\n\n // Bullets attach to the open unit.\n const bullet = RE_BULLET.exec(raw);\n if (bullet) {\n if (current) current.bullets.push(parseBullet(bullet[1].length, bullet[2]));\n continue;\n }\n\n // GW export footer.\n if (line.startsWith(\"Exported with\")) continue;\n\n // `## Section` markdown header (strip an optional `(N pts)` tail).\n const md = RE_MD_SECTION.exec(line);\n if (md) {\n flush();\n const pts = RE_PTS_LINE.exec(md[1]);\n section = pts ? pts[1].trim() : md[1].trim();\n continue;\n }\n\n // First `Name (N pts|Points)` line is the roster title, not a unit.\n const pts = RE_PTS_LINE.exec(line);\n if (pts) {\n const header_name = pts[1].trim();\n const points = parsePts(pts[2]);\n if (!consumed_title && current === null && units.length === 0) {\n consumed_title = true;\n name = header_name;\n declared_limit = points;\n continue;\n }\n // Battle-size metadata (`Strike Force (2,000 Points)`).\n if (isBattleSize(header_name)) {\n battle_size_raw = line;\n if (declared_limit === null) declared_limit = points;\n continue;\n }\n // A real unit header.\n flush();\n const inChars = section?.toLowerCase() === CHARACTERS_SECTION.toLowerCase();\n if (section === ALLIED_SECTION) allied += 1;\n current = {\n raw_name: header_name,\n displayed_pts: points,\n is_character_section: inChars,\n bullets: [],\n };\n continue;\n }\n\n // Section headers without points (ALL-CAPS role, `Title:` colon).\n if (RE_CAPS_SECTION.test(line) || RE_COLON_SECTION.test(line)) {\n flush();\n section = line.replace(/:\\s*$/, \"\").trim();\n continue;\n }\n\n // Anything else (faction/detachment preamble, stray notes).\n if (!consumed_title && current === null && units.length === 0) {\n // Very first content line with no `(N pts)` title → use as name.\n consumed_title = true;\n name = line;\n } else if (current === null && units.length === 0) {\n // Preamble after the title, before the first unit: faction then\n // detachment. Names are resolved (and warned on miss) downstream.\n if (faction_raw_name === null) {\n faction_raw_name = line;\n } else if (detachment_raw_names.length === 0) {\n detachment_raw_names.push(line);\n }\n }\n }\n flush();\n\n let total_computed = 0;\n for (const u of units) {\n total_computed += (u.points ?? 0) + (u.enhancement_points ?? 0);\n }\n\n return {\n name,\n generated_by: null,\n faction_raw_name,\n detachment_raw_names,\n battle_size_raw: battle_size_raw ?? inferBattleSizeRaw(declared_limit),\n declared_limit,\n total_reported: null,\n total_computed,\n units,\n multi_force: allied > 0,\n };\n },\n};\n\n// Internals re-exported for unit tests.\nexport const _internals = {\n headerlessText,\n parseBullet,\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alpaca-software/40kdc-data",
3
- "version": "1.0.18",
3
+ "version": "1.0.19",
4
4
  "type": "module",
5
5
  "description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
6
6
  "keywords": [