@alpaca-software/40kdc-data 0.5.0 → 0.5.2
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.
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/gen-conformance.js +6 -1
- package/dist/gen-conformance.js.map +1 -1
- package/dist/import/decode.d.ts.map +1 -1
- package/dist/import/decode.js +20 -4
- package/dist/import/decode.js.map +1 -1
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +10 -6
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/index.d.ts +1 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -0
- package/dist/import/index.js.map +1 -1
- package/dist/import/listforge-text.d.ts +65 -0
- package/dist/import/listforge-text.d.ts.map +1 -0
- package/dist/import/listforge-text.js +194 -0
- package/dist/import/listforge-text.js.map +1 -0
- package/dist/import/newrecruit-simple.d.ts.map +1 -1
- package/dist/import/newrecruit-simple.js +28 -15
- package/dist/import/newrecruit-simple.js.map +1 -1
- package/dist/import/types.d.ts +1 -1
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/schemas/core/roster.schema.json +1 -0
package/dist/import/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { ImportOptions, ImportResult, ImportFailureReason, AdapterTrial, }
|
|
|
14
14
|
export { decodeListForge } from "./decode.js";
|
|
15
15
|
export { resolve } from "./resolve.js";
|
|
16
16
|
export { listForgeAdapter } from "./listforge.js";
|
|
17
|
+
export { listForgeTextAdapter } from "./listforge-text.js";
|
|
17
18
|
export { newRecruitJsonAdapter } from "./newrecruit-json.js";
|
|
18
19
|
export { newRecruitSimpleAdapter } from "./newrecruit-simple.js";
|
|
19
20
|
export { newRecruitWtcCompactAdapter, newRecruitWtcFullAdapter, } from "./newrecruit-wtc.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/import/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EACV,MAAM,EACN,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,SAAS,EACT,sBAAsB,EACtB,WAAW,EACX,OAAO,EACP,WAAW,EACX,UAAU,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/import/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EACV,MAAM,EACN,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,SAAS,EACT,sBAAsB,EACtB,WAAW,EACX,OAAO,EACP,WAAW,EACX,UAAU,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/import/index.js
CHANGED
|
@@ -13,6 +13,7 @@ export { importListForge, importNewRecruit, importRoster, tryImportRoster, REGIS
|
|
|
13
13
|
export { decodeListForge } from "./decode.js";
|
|
14
14
|
export { resolve } from "./resolve.js";
|
|
15
15
|
export { listForgeAdapter } from "./listforge.js";
|
|
16
|
+
export { listForgeTextAdapter } from "./listforge-text.js";
|
|
16
17
|
export { newRecruitJsonAdapter } from "./newrecruit-json.js";
|
|
17
18
|
export { newRecruitSimpleAdapter } from "./newrecruit-simple.js";
|
|
18
19
|
export { newRecruitWtcCompactAdapter, newRecruitWtcFullAdapter, } from "./newrecruit-wtc.js";
|
package/dist/import/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/import/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,qBAAqB,CAAC","sourcesContent":["/**\n * Army-list importer: turn an external list-builder export into a resolved\n * 40kdc roster.\n *\n * v1 supports ListForge's \"share JSON\" payload. The output is a {@link Roster}\n * keyed on 40kdc entity ids and validatable against\n * `schemas/core/roster.schema.json`. Resolution is lenient — unmatched names are\n * retained with candidate suggestions and summarised in diagnostics.\n *\n * @packageDocumentation\n */\nexport {\n importListForge,\n importNewRecruit,\n importRoster,\n tryImportRoster,\n REGISTERED_ADAPTERS,\n} from \"./import-roster.js\";\nexport type {\n ImportOptions,\n ImportResult,\n ImportFailureReason,\n AdapterTrial,\n} from \"./import-roster.js\";\nexport { decodeListForge } from \"./decode.js\";\nexport { resolve } from \"./resolve.js\";\nexport { listForgeAdapter } from \"./listforge.js\";\nexport { newRecruitJsonAdapter } from \"./newrecruit-json.js\";\nexport { newRecruitSimpleAdapter } from \"./newrecruit-simple.js\";\nexport {\n newRecruitWtcCompactAdapter,\n newRecruitWtcFullAdapter,\n} from \"./newrecruit-wtc.js\";\nexport type { FormatAdapter } from \"./adapter.js\";\nexport type {\n Roster,\n RosterUnit,\n RosterWargear,\n RosterSource,\n RosterFormat,\n RosterPoints,\n ResolvedRef,\n Candidate,\n RosterLeaderAttachment,\n Diagnostics,\n Warning,\n WarningCode,\n BattleSize,\n GameVersionRef,\n ParsedRoster,\n ParsedUnit,\n ParsedWargear,\n} from \"./types.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/import/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,mBAAmB,GACpB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EACL,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,qBAAqB,CAAC","sourcesContent":["/**\n * Army-list importer: turn an external list-builder export into a resolved\n * 40kdc roster.\n *\n * v1 supports ListForge's \"share JSON\" payload. The output is a {@link Roster}\n * keyed on 40kdc entity ids and validatable against\n * `schemas/core/roster.schema.json`. Resolution is lenient — unmatched names are\n * retained with candidate suggestions and summarised in diagnostics.\n *\n * @packageDocumentation\n */\nexport {\n importListForge,\n importNewRecruit,\n importRoster,\n tryImportRoster,\n REGISTERED_ADAPTERS,\n} from \"./import-roster.js\";\nexport type {\n ImportOptions,\n ImportResult,\n ImportFailureReason,\n AdapterTrial,\n} from \"./import-roster.js\";\nexport { decodeListForge } from \"./decode.js\";\nexport { resolve } from \"./resolve.js\";\nexport { listForgeAdapter } from \"./listforge.js\";\nexport { listForgeTextAdapter } from \"./listforge-text.js\";\nexport { newRecruitJsonAdapter } from \"./newrecruit-json.js\";\nexport { newRecruitSimpleAdapter } from \"./newrecruit-simple.js\";\nexport {\n newRecruitWtcCompactAdapter,\n newRecruitWtcFullAdapter,\n} from \"./newrecruit-wtc.js\";\nexport type { FormatAdapter } from \"./adapter.js\";\nexport type {\n Roster,\n RosterUnit,\n RosterWargear,\n RosterSource,\n RosterFormat,\n RosterPoints,\n ResolvedRef,\n Candidate,\n RosterLeaderAttachment,\n Diagnostics,\n Warning,\n WarningCode,\n BattleSize,\n GameVersionRef,\n ParsedRoster,\n ParsedUnit,\n ParsedWargear,\n} from \"./types.js\";\n"]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ListForge plain-text adapter: lower ListForge's copy-paste text export to a
|
|
3
|
+
* {@link ParsedRoster}.
|
|
4
|
+
*
|
|
5
|
+
* This is the bullet-list text users copy out of the ListForge app (distinct
|
|
6
|
+
* from the base64+gzip share-JSON the `listforge` adapter handles). Shape:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* all gas no breaks - Chaos Daemons - Daemonic Incursion (1995 Points)
|
|
10
|
+
*
|
|
11
|
+
* Epic Hero:
|
|
12
|
+
* Rotigus (250 pts)
|
|
13
|
+
* • Gnarlrod
|
|
14
|
+
* • Streams of brackish filth
|
|
15
|
+
*
|
|
16
|
+
* Battleline:
|
|
17
|
+
* Bloodletters (110 pts)
|
|
18
|
+
* • Bloodreaper
|
|
19
|
+
* • Hellblade
|
|
20
|
+
* • Daemonic Icon
|
|
21
|
+
* • 9x Bloodletter
|
|
22
|
+
* • 9x Hellblade
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* - The first non-blank line is `<list name> - <faction> - <detachment>
|
|
26
|
+
* (<N> Points)`. A list name containing ` - ` breaks the split — a documented
|
|
27
|
+
* ListForge limitation, not ours.
|
|
28
|
+
* - Sections are mixed-case battlefield-role lines ending with `:`
|
|
29
|
+
* (`Epic Hero:`, `Character:`, `Battleline:`, …). Units under `Epic Hero:` or
|
|
30
|
+
* `Character:` are characters.
|
|
31
|
+
* - Bullet classification mirrors the GW adapter: a top-level bullet with
|
|
32
|
+
* deeper children is a **model group** (its `Nx` count — implicitly 1 —
|
|
33
|
+
* adds to the model count); without children it's **wargear**. Child-bullet
|
|
34
|
+
* `Nx` counts are already squad-wide totals; a child without a count is one
|
|
35
|
+
* item (`• Hellblade` under a lone Bloodreaper).
|
|
36
|
+
* - `E: <name>` is the enhancement annotation (ListForge reports no points for
|
|
37
|
+
* it, so `enhancement_points` stays null and unit points stay as displayed).
|
|
38
|
+
* A bare `Warlord` bullet flags the warlord.
|
|
39
|
+
*
|
|
40
|
+
* **Disjointness**: the `(N Points)` first-line suffix is unique to this
|
|
41
|
+
* format — newrecruit-simple's first line ends `- [N pts]`, the GW export
|
|
42
|
+
* opens with a `++++` fence, and the WTC formats carry `N with` lines or no
|
|
43
|
+
* bullets at all.
|
|
44
|
+
*
|
|
45
|
+
* @packageDocumentation
|
|
46
|
+
*/
|
|
47
|
+
import type { FormatAdapter } from "./adapter.js";
|
|
48
|
+
/** Accept plain text whose first non-blank line is the ListForge
|
|
49
|
+
* `name - faction - detachment (N Points)` header, with `•` bullets and no
|
|
50
|
+
* WTC `N with` lines. */
|
|
51
|
+
declare function isListForgeText(decoded: unknown): string | null;
|
|
52
|
+
interface Header {
|
|
53
|
+
name: string;
|
|
54
|
+
faction_raw_name: string | null;
|
|
55
|
+
detachment_raw_name: string | null;
|
|
56
|
+
total_reported: number | null;
|
|
57
|
+
}
|
|
58
|
+
declare function parseFirstLine(line: string): Header | null;
|
|
59
|
+
export declare const listForgeTextAdapter: FormatAdapter;
|
|
60
|
+
export declare const _internals: {
|
|
61
|
+
isListForgeText: typeof isListForgeText;
|
|
62
|
+
parseFirstLine: typeof parseFirstLine;
|
|
63
|
+
};
|
|
64
|
+
export {};
|
|
65
|
+
//# sourceMappingURL=listforge-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listforge-text.d.ts","sourceRoot":"","sources":["../../src/import/listforge-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAgBlD;;yBAEyB;AACzB,iBAAS,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAWxD;AAED,UAAU,MAAM;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,iBAAS,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcnD;AAiFD,eAAO,MAAM,oBAAoB,EAAE,aA8FlC,CAAC;AAGF,eAAO,MAAM,UAAU;;;CAGtB,CAAC"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { inferBattleSizeRaw } from "./newrecruit-text.js";
|
|
2
|
+
const FIRST_LINE = /^(.+)\s\(\s*(\d+)\s*Points?\s*\)\s*$/i;
|
|
3
|
+
const SECTION_HEADER = /^[A-Za-z][A-Za-z0-9 /&'-]*:$/;
|
|
4
|
+
const UNIT_HEADER = /^(.+?)\s*\(\s*(\d+)\s*pts?\s*\)\s*$/i;
|
|
5
|
+
const BULLET_LINE = /^(\s*)•\s*(.+?)\s*$/u;
|
|
6
|
+
const NX_PREFIX = /^(\d+)x\s+(.+)$/;
|
|
7
|
+
const BULLET = /^[\t ]*•/mu;
|
|
8
|
+
const WITH_LINE = /^[\t ]*\d+\s+with\b/m;
|
|
9
|
+
const ENHANCEMENT_PREFIX = "E: ";
|
|
10
|
+
const WARLORD_MARKER = "Warlord";
|
|
11
|
+
const CHARACTER_SECTIONS = new Set(["epic hero", "character"]);
|
|
12
|
+
/** Accept plain text whose first non-blank line is the ListForge
|
|
13
|
+
* `name - faction - detachment (N Points)` header, with `•` bullets and no
|
|
14
|
+
* WTC `N with` lines. */
|
|
15
|
+
function isListForgeText(decoded) {
|
|
16
|
+
if (typeof decoded !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
const firstNonBlank = decoded
|
|
19
|
+
.split(/\r?\n/)
|
|
20
|
+
.find((l) => l.trim().length > 0);
|
|
21
|
+
if (!firstNonBlank)
|
|
22
|
+
return null;
|
|
23
|
+
const first = FIRST_LINE.exec(firstNonBlank.trim());
|
|
24
|
+
if (!first || first[1].split(" - ").length < 3)
|
|
25
|
+
return null;
|
|
26
|
+
if (!BULLET.test(decoded))
|
|
27
|
+
return null;
|
|
28
|
+
if (WITH_LINE.test(decoded))
|
|
29
|
+
return null;
|
|
30
|
+
return decoded;
|
|
31
|
+
}
|
|
32
|
+
function parseFirstLine(line) {
|
|
33
|
+
const m = FIRST_LINE.exec(line.trim());
|
|
34
|
+
if (!m)
|
|
35
|
+
return null;
|
|
36
|
+
const parts = m[1].split(" - ").map((s) => s.trim()).filter(Boolean);
|
|
37
|
+
if (parts.length < 3)
|
|
38
|
+
return null;
|
|
39
|
+
// `<list name> - <faction> - <detachment>`; the name is everything before
|
|
40
|
+
// the trailing two segments so faction names with hyphens stay intact only
|
|
41
|
+
// when ListForge itself doesn't insert ` - ` (it doesn't).
|
|
42
|
+
return {
|
|
43
|
+
name: parts.slice(0, parts.length - 2).join(" - "),
|
|
44
|
+
faction_raw_name: parts[parts.length - 2],
|
|
45
|
+
detachment_raw_name: parts[parts.length - 1],
|
|
46
|
+
total_reported: Number.parseInt(m[2], 10),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function finishUnit(acc) {
|
|
50
|
+
const topIndent = acc.bullets.length
|
|
51
|
+
? Math.min(...acc.bullets.map((b) => b.indent))
|
|
52
|
+
: 0;
|
|
53
|
+
const wargear = new Map();
|
|
54
|
+
let model_count = 0;
|
|
55
|
+
let is_warlord = false;
|
|
56
|
+
let enhancement_raw_name = null;
|
|
57
|
+
const addWargear = (raw_name, count) => {
|
|
58
|
+
wargear.set(raw_name, (wargear.get(raw_name) ?? 0) + count);
|
|
59
|
+
};
|
|
60
|
+
for (let i = 0; i < acc.bullets.length; i += 1) {
|
|
61
|
+
const b = acc.bullets[i];
|
|
62
|
+
// Child bullet: a model group's weapon. ListForge child counts are
|
|
63
|
+
// squad-wide totals; a count-less child is a single item.
|
|
64
|
+
if (b.indent > topIndent) {
|
|
65
|
+
addWargear(b.text, b.count ?? 1);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Top-level annotations.
|
|
69
|
+
if (b.count === null) {
|
|
70
|
+
if (b.text === WARLORD_MARKER) {
|
|
71
|
+
is_warlord = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (b.text.startsWith(ENHANCEMENT_PREFIX)) {
|
|
75
|
+
if (enhancement_raw_name === null) {
|
|
76
|
+
enhancement_raw_name = b.text.slice(ENHANCEMENT_PREFIX.length).trim();
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Top-level entry: a model group when it has child bullets beneath it,
|
|
82
|
+
// otherwise plain wargear. Either way a missing `Nx` count means 1.
|
|
83
|
+
const next = acc.bullets[i + 1];
|
|
84
|
+
if (next && next.indent > b.indent) {
|
|
85
|
+
model_count += b.count ?? 1;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
addWargear(b.text, b.count ?? 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (model_count === 0)
|
|
92
|
+
model_count = 1;
|
|
93
|
+
return {
|
|
94
|
+
raw_name: acc.raw_name,
|
|
95
|
+
is_character: acc.is_character,
|
|
96
|
+
model_count,
|
|
97
|
+
points: acc.displayed_pts,
|
|
98
|
+
is_warlord,
|
|
99
|
+
enhancement_raw_name,
|
|
100
|
+
// ListForge's text export reports no enhancement cost, so the unit's
|
|
101
|
+
// displayed points stay as-is and no enhancement points are claimed.
|
|
102
|
+
enhancement_points: null,
|
|
103
|
+
wargear: [...wargear].map(([raw_name, count]) => ({ raw_name, count })),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export const listForgeTextAdapter = {
|
|
107
|
+
id: "listforge-text",
|
|
108
|
+
matches(decoded) {
|
|
109
|
+
return isListForgeText(decoded) !== null;
|
|
110
|
+
},
|
|
111
|
+
parse(decoded) {
|
|
112
|
+
const text = isListForgeText(decoded);
|
|
113
|
+
if (text === null) {
|
|
114
|
+
throw new Error("listforge-text: input is not a ListForge text export");
|
|
115
|
+
}
|
|
116
|
+
const lines = text.split(/\r?\n/);
|
|
117
|
+
let header = null;
|
|
118
|
+
const units = [];
|
|
119
|
+
let current = null;
|
|
120
|
+
let sectionIsCharacter = false;
|
|
121
|
+
const finalize = () => {
|
|
122
|
+
if (current) {
|
|
123
|
+
units.push(finishUnit(current));
|
|
124
|
+
current = null;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
for (const raw of lines) {
|
|
128
|
+
const line = raw.trim();
|
|
129
|
+
if (!line)
|
|
130
|
+
continue;
|
|
131
|
+
if (!header) {
|
|
132
|
+
header = parseFirstLine(line);
|
|
133
|
+
if (header)
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const bulletMatch = BULLET_LINE.exec(raw);
|
|
137
|
+
if (bulletMatch) {
|
|
138
|
+
if (current) {
|
|
139
|
+
const rest = bulletMatch[2];
|
|
140
|
+
const nx = NX_PREFIX.exec(rest);
|
|
141
|
+
current.bullets.push({
|
|
142
|
+
indent: bulletMatch[1].length,
|
|
143
|
+
count: nx ? Number.parseInt(nx[1], 10) : null,
|
|
144
|
+
text: (nx ? nx[2] : rest).trim(),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (SECTION_HEADER.test(line)) {
|
|
150
|
+
finalize();
|
|
151
|
+
sectionIsCharacter = CHARACTER_SECTIONS.has(line.slice(0, -1).trim().toLowerCase());
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const unitMatch = UNIT_HEADER.exec(line);
|
|
155
|
+
if (unitMatch) {
|
|
156
|
+
finalize();
|
|
157
|
+
current = {
|
|
158
|
+
raw_name: unitMatch[1].trim(),
|
|
159
|
+
displayed_pts: Number.parseInt(unitMatch[2], 10),
|
|
160
|
+
is_character: sectionIsCharacter,
|
|
161
|
+
bullets: [],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
finalize();
|
|
166
|
+
if (!header) {
|
|
167
|
+
throw new Error("listforge-text: missing ListForge header line");
|
|
168
|
+
}
|
|
169
|
+
let total_computed = 0;
|
|
170
|
+
for (const u of units)
|
|
171
|
+
total_computed += u.points ?? 0;
|
|
172
|
+
// Like the GW export, ListForge text reports only the army total — use it
|
|
173
|
+
// as the declared limit so battle-size inference stays round-trippable.
|
|
174
|
+
const declared_limit = header.total_reported;
|
|
175
|
+
return {
|
|
176
|
+
name: header.name,
|
|
177
|
+
generated_by: "List Forge",
|
|
178
|
+
faction_raw_name: header.faction_raw_name,
|
|
179
|
+
detachment_raw_name: header.detachment_raw_name,
|
|
180
|
+
battle_size_raw: inferBattleSizeRaw(declared_limit),
|
|
181
|
+
declared_limit,
|
|
182
|
+
total_reported: header.total_reported,
|
|
183
|
+
total_computed,
|
|
184
|
+
units,
|
|
185
|
+
multi_force: false,
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
// Internals re-exported for unit tests.
|
|
190
|
+
export const _internals = {
|
|
191
|
+
isListForgeText,
|
|
192
|
+
parseFirstLine,
|
|
193
|
+
};
|
|
194
|
+
//# sourceMappingURL=listforge-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listforge-text.js","sourceRoot":"","sources":["../../src/import/listforge-text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ListForge plain-text adapter: lower ListForge's copy-paste text export to a\n * {@link ParsedRoster}.\n *\n * This is the bullet-list text users copy out of the ListForge app (distinct\n * from the base64+gzip share-JSON the `listforge` adapter handles). Shape:\n *\n * ```\n * all gas no breaks - Chaos Daemons - Daemonic Incursion (1995 Points)\n *\n * Epic Hero:\n * Rotigus (250 pts)\n * • Gnarlrod\n * • Streams of brackish filth\n *\n * Battleline:\n * Bloodletters (110 pts)\n * • Bloodreaper\n * • Hellblade\n * • Daemonic Icon\n * • 9x Bloodletter\n * • 9x Hellblade\n * ```\n *\n * - The first non-blank line is `<list name> - <faction> - <detachment>\n * (<N> Points)`. A list name containing ` - ` breaks the split — a documented\n * ListForge limitation, not ours.\n * - Sections are mixed-case battlefield-role lines ending with `:`\n * (`Epic Hero:`, `Character:`, `Battleline:`, …). Units under `Epic Hero:` or\n * `Character:` are characters.\n * - Bullet classification mirrors the GW adapter: a top-level bullet with\n * deeper children is a **model group** (its `Nx` count — implicitly 1 —\n * adds to the model count); without children it's **wargear**. Child-bullet\n * `Nx` counts are already squad-wide totals; a child without a count is one\n * item (`• Hellblade` under a lone Bloodreaper).\n * - `E: <name>` is the enhancement annotation (ListForge reports no points for\n * it, so `enhancement_points` stays null and unit points stay as displayed).\n * A bare `Warlord` bullet flags the warlord.\n *\n * **Disjointness**: the `(N Points)` first-line suffix is unique to this\n * format — newrecruit-simple's first line ends `- [N pts]`, the GW export\n * opens with a `++++` fence, and the WTC formats carry `N with` lines or no\n * bullets at all.\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 FIRST_LINE = /^(.+)\\s\\(\\s*(\\d+)\\s*Points?\\s*\\)\\s*$/i;\nconst SECTION_HEADER = /^[A-Za-z][A-Za-z0-9 /&'-]*:$/;\nconst UNIT_HEADER = /^(.+?)\\s*\\(\\s*(\\d+)\\s*pts?\\s*\\)\\s*$/i;\nconst BULLET_LINE = /^(\\s*)•\\s*(.+?)\\s*$/u;\nconst NX_PREFIX = /^(\\d+)x\\s+(.+)$/;\nconst BULLET = /^[\\t ]*•/mu;\nconst WITH_LINE = /^[\\t ]*\\d+\\s+with\\b/m;\n\nconst ENHANCEMENT_PREFIX = \"E: \";\nconst WARLORD_MARKER = \"Warlord\";\nconst CHARACTER_SECTIONS = new Set([\"epic hero\", \"character\"]);\n\n/** Accept plain text whose first non-blank line is the ListForge\n * `name - faction - detachment (N Points)` header, with `•` bullets and no\n * WTC `N with` lines. */\nfunction isListForgeText(decoded: unknown): string | null {\n if (typeof decoded !== \"string\") return null;\n const firstNonBlank = decoded\n .split(/\\r?\\n/)\n .find((l) => l.trim().length > 0);\n if (!firstNonBlank) return null;\n const first = FIRST_LINE.exec(firstNonBlank.trim());\n if (!first || first[1].split(\" - \").length < 3) return null;\n if (!BULLET.test(decoded)) return null;\n if (WITH_LINE.test(decoded)) return null;\n return decoded;\n}\n\ninterface Header {\n name: string;\n faction_raw_name: string | null;\n detachment_raw_name: string | null;\n total_reported: number | null;\n}\n\nfunction parseFirstLine(line: string): Header | null {\n const m = FIRST_LINE.exec(line.trim());\n if (!m) return null;\n const parts = m[1].split(\" - \").map((s) => s.trim()).filter(Boolean);\n if (parts.length < 3) return null;\n // `<list name> - <faction> - <detachment>`; the name is everything before\n // the trailing two segments so faction names with hyphens stay intact only\n // when ListForge itself doesn't insert ` - ` (it doesn't).\n return {\n name: parts.slice(0, parts.length - 2).join(\" - \"),\n faction_raw_name: parts[parts.length - 2],\n detachment_raw_name: parts[parts.length - 1],\n total_reported: Number.parseInt(m[2], 10),\n };\n}\n\ninterface Bullet {\n indent: number;\n count: number | null;\n text: string;\n}\n\ninterface UnitAcc {\n raw_name: string;\n displayed_pts: number | null;\n is_character: boolean;\n bullets: Bullet[];\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 let model_count = 0;\n let is_warlord = false;\n let enhancement_raw_name: string | null = null;\n\n const addWargear = (raw_name: string, count: number): void => {\n wargear.set(raw_name, (wargear.get(raw_name) ?? 0) + count);\n };\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 weapon. ListForge child counts are\n // squad-wide totals; a count-less child is a single item.\n if (b.indent > topIndent) {\n addWargear(b.text, b.count ?? 1);\n continue;\n }\n\n // Top-level annotations.\n if (b.count === null) {\n if (b.text === WARLORD_MARKER) {\n is_warlord = true;\n continue;\n }\n if (b.text.startsWith(ENHANCEMENT_PREFIX)) {\n if (enhancement_raw_name === null) {\n enhancement_raw_name = b.text.slice(ENHANCEMENT_PREFIX.length).trim();\n }\n continue;\n }\n }\n\n // Top-level entry: a model group when it has child bullets beneath it,\n // otherwise plain wargear. Either way a missing `Nx` count means 1.\n const next = acc.bullets[i + 1];\n if (next && next.indent > b.indent) {\n model_count += b.count ?? 1;\n } else {\n addWargear(b.text, b.count ?? 1);\n }\n }\n\n if (model_count === 0) model_count = 1;\n\n return {\n raw_name: acc.raw_name,\n is_character: acc.is_character,\n model_count,\n points: acc.displayed_pts,\n is_warlord,\n enhancement_raw_name,\n // ListForge's text export reports no enhancement cost, so the unit's\n // displayed points stay as-is and no enhancement points are claimed.\n enhancement_points: null,\n wargear: [...wargear].map(\n ([raw_name, count]): ParsedWargear => ({ raw_name, count }),\n ),\n };\n}\n\nexport const listForgeTextAdapter: FormatAdapter = {\n id: \"listforge-text\",\n\n matches(decoded: unknown): boolean {\n return isListForgeText(decoded) !== null;\n },\n\n parse(decoded: unknown): ParsedRoster {\n const text = isListForgeText(decoded);\n if (text === null) {\n throw new Error(\"listforge-text: input is not a ListForge text export\");\n }\n\n const lines = text.split(/\\r?\\n/);\n let header: Header | null = null;\n const units: ParsedUnit[] = [];\n let current: UnitAcc | null = null;\n let sectionIsCharacter = false;\n\n const finalize = (): void => {\n if (current) {\n units.push(finishUnit(current));\n current = null;\n }\n };\n\n for (const raw of lines) {\n const line = raw.trim();\n if (!line) continue;\n\n if (!header) {\n header = parseFirstLine(line);\n if (header) continue;\n }\n\n const bulletMatch = BULLET_LINE.exec(raw);\n if (bulletMatch) {\n if (current) {\n const rest = bulletMatch[2];\n const nx = NX_PREFIX.exec(rest);\n current.bullets.push({\n indent: bulletMatch[1].length,\n count: nx ? Number.parseInt(nx[1], 10) : null,\n text: (nx ? nx[2] : rest).trim(),\n });\n }\n continue;\n }\n\n if (SECTION_HEADER.test(line)) {\n finalize();\n sectionIsCharacter = CHARACTER_SECTIONS.has(\n line.slice(0, -1).trim().toLowerCase(),\n );\n continue;\n }\n\n const unitMatch = UNIT_HEADER.exec(line);\n if (unitMatch) {\n finalize();\n current = {\n raw_name: unitMatch[1].trim(),\n displayed_pts: Number.parseInt(unitMatch[2], 10),\n is_character: sectionIsCharacter,\n bullets: [],\n };\n }\n }\n finalize();\n\n if (!header) {\n throw new Error(\"listforge-text: missing ListForge header line\");\n }\n\n let total_computed = 0;\n for (const u of units) total_computed += u.points ?? 0;\n\n // Like the GW export, ListForge text reports only the army total — use it\n // as the declared limit so battle-size inference stays round-trippable.\n const declared_limit = header.total_reported;\n\n return {\n name: header.name,\n generated_by: \"List Forge\",\n faction_raw_name: header.faction_raw_name,\n detachment_raw_name: header.detachment_raw_name,\n battle_size_raw: inferBattleSizeRaw(declared_limit),\n declared_limit,\n total_reported: header.total_reported,\n total_computed,\n units,\n multi_force: false,\n };\n },\n};\n\n// Internals re-exported for unit tests.\nexport const _internals = {\n isListForgeText,\n parseFirstLine,\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-simple.d.ts","sourceRoot":"","sources":["../../src/import/newrecruit-simple.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"newrecruit-simple.d.ts","sourceRoot":"","sources":["../../src/import/newrecruit-simple.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA0FlD,eAAO,MAAM,uBAAuB,EAAE,aAmJrC,CAAC"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { classifyWargearList, splitWargearList } from "./newrecruit-text.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
2
|
+
// Point brackets may carry comma-separated faction resources after the pts
|
|
3
|
+
// figure (e.g. `[4485pts, 29Cabal Points]`); the tail is recognized and
|
|
4
|
+
// discarded — only the pts figure is consumed.
|
|
5
|
+
const FIRST_LINE = /^(.+)\s-\s\[\s*(\d+)\s*pts?\s*(?:,[^\]]*)?\]\s*$/i;
|
|
6
|
+
const ROSTER_HEADER = /^#\s*\+\+\s*Army Roster\s*\+\+\s*\[\s*(\d+)\s*pts?\s*(?:,[^\]]*)?\]\s*$/i;
|
|
7
|
+
const SECTION_HEADER = /^##\s*(.+?)(?:\s*\[\s*(\d+)\s*pts?\s*(?:,[^\]]*)?\])?\s*$/;
|
|
8
|
+
const UNIT_LINE = /^(.+?)\s*\[\s*(\d+)\s*pts?\s*(?:,[^\]]*)?\](?:\s*:\s*(.*))?$/i;
|
|
9
|
+
const BULLET = /^\s*•\s*(\d+)x\s+(.+?)(?:\s*\[\s*(\d+)\s*pts?\s*(?:,[^\]]*)?\])?(?:\s*:\s*(.*))?\s*$/u;
|
|
7
10
|
function newUnit(name, displayed_pts) {
|
|
8
11
|
return {
|
|
9
12
|
raw_name: name,
|
|
@@ -74,7 +77,10 @@ export const newRecruitSimpleAdapter = {
|
|
|
74
77
|
return false;
|
|
75
78
|
if (!FIRST_LINE.test(firstNonBlank))
|
|
76
79
|
return false;
|
|
77
|
-
|
|
80
|
+
// Some exports omit the `# ++ Army Roster ++` line and open straight with
|
|
81
|
+
// a `## Section` heading — accept either marker.
|
|
82
|
+
return (/^#\s*\+\+\s*Army Roster\s*\+\+/m.test(decoded) ||
|
|
83
|
+
/^##\s+/m.test(decoded));
|
|
78
84
|
},
|
|
79
85
|
parse(decoded) {
|
|
80
86
|
if (typeof decoded !== "string") {
|
|
@@ -134,16 +140,23 @@ export const newRecruitSimpleAdapter = {
|
|
|
134
140
|
continue;
|
|
135
141
|
}
|
|
136
142
|
if (section === "configuration") {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
143
|
+
// Some exports list units directly after Configuration with no units
|
|
144
|
+
// section heading; a `Name [N pts]` line ends the configuration block.
|
|
145
|
+
if (UNIT_LINE.test(line)) {
|
|
146
|
+
section = "units";
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const idx = line.indexOf(":");
|
|
150
|
+
if (idx > 0) {
|
|
151
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
152
|
+
const value = line.slice(idx + 1).trim();
|
|
153
|
+
if (key === "battle size")
|
|
154
|
+
battle_size_raw = value;
|
|
155
|
+
else if (key === "detachment")
|
|
156
|
+
detachment_raw_name = value;
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
145
159
|
}
|
|
146
|
-
continue;
|
|
147
160
|
}
|
|
148
161
|
// Unit section. A bullet line extends the *current* unit.
|
|
149
162
|
const bulletMatch = BULLET.exec(raw);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"newrecruit-simple.js","sourceRoot":"","sources":["../../src/import/newrecruit-simple.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * NewRecruit \"simple\" markdown-ish text adapter.\n *\n * Shape:\n * ```\n * <breadcrumb> - <faction> - <list name> - [N pts]\n *\n * # ++ Army Roster ++ [N pts]\n * ## Configuration\n * Battle Size: <Label>\n * Detachment: <Name>\n * Show/Hide Options: ...\n *\n * ## <Section> [N pts]\n * <Unit> [N pts]: <wargear>\n * <Unit> [N pts]:\n * • <count>x <ModelType>[ [N pts]]: <wargear>\n * ```\n *\n * Enhancements are inlined in the wargear list as `<Name> [N pts]` — the only\n * wargear token wearing a `[…]` pts suffix. `Warlord` and the detachment\n * \"<X> Character\" keyword are also stripped from the list and set as flags.\n * Per-model-type breakdowns under `•` lines are collapsed onto the parent unit.\n *\n * @packageDocumentation\n */\nimport type { FormatAdapter } from \"./adapter.js\";\nimport type { ParsedRoster, ParsedUnit, ParsedWargear } from \"./types.js\";\nimport { classifyWargearList, splitWargearList } from \"./newrecruit-text.js\";\n\nconst FIRST_LINE = /^(.+)\\s-\\s\\[\\s*(\\d+)\\s*pts?\\s*\\]\\s*$/i;\nconst ROSTER_HEADER = /^#\\s*\\+\\+\\s*Army Roster\\s*\\+\\+\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\]\\s*$/i;\nconst SECTION_HEADER = /^##\\s*(.+?)(?:\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\])?\\s*$/;\nconst UNIT_LINE = /^(.+?)\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\](?:\\s*:\\s*(.*))?$/i;\nconst BULLET =\n /^\\s*•\\s*(\\d+)x\\s+(.+?)(?:\\s*\\[\\s*(\\d+)\\s*pts?\\s*\\])?(?:\\s*:\\s*(.*))?\\s*$/u;\n\ninterface UnitBuilder {\n raw_name: string;\n is_character: boolean;\n is_warlord: boolean;\n enhancement_raw_name: string | null;\n enhancement_pts: number;\n displayed_pts: number | null;\n model_count: number;\n /** Aggregated wargear, keyed by name. Counts sum across `• Nx ModelType` breakdowns. */\n wargear: Map<string, number>;\n}\n\nfunction newUnit(name: string, displayed_pts: number | null): UnitBuilder {\n return {\n raw_name: name,\n is_character: false,\n is_warlord: false,\n enhancement_raw_name: null,\n enhancement_pts: 0,\n displayed_pts,\n model_count: 1,\n wargear: new Map(),\n };\n}\n\nfunction addWargear(unit: UnitBuilder, items: ParsedWargear[]): void {\n for (const { raw_name, count } of items) {\n unit.wargear.set(raw_name, (unit.wargear.get(raw_name) ?? 0) + count);\n }\n}\n\nfunction applyTokens(unit: UnitBuilder, tokensCsv: string, multiplier = 1): void {\n const tokens = splitWargearList(tokensCsv);\n const cls = classifyWargearList(tokens);\n if (cls.is_warlord) unit.is_warlord = true;\n if (cls.is_character) unit.is_character = true;\n if (cls.enhancement_raw_name && unit.enhancement_raw_name === null) {\n unit.enhancement_raw_name = cls.enhancement_raw_name;\n unit.enhancement_pts = cls.enhancement_points ?? 0;\n }\n const scaled = cls.wargear.map((w) => ({\n raw_name: w.raw_name,\n count: w.count * multiplier,\n }));\n addWargear(unit, scaled);\n}\n\nfunction finishUnit(unit: UnitBuilder): ParsedUnit {\n const points =\n unit.displayed_pts === null ? null : unit.displayed_pts - unit.enhancement_pts;\n return {\n raw_name: unit.raw_name,\n is_character: unit.is_character,\n model_count: unit.model_count,\n points,\n is_warlord: unit.is_warlord,\n enhancement_raw_name: unit.enhancement_raw_name,\n enhancement_points: unit.enhancement_raw_name === null ? null : unit.enhancement_pts,\n wargear: [...unit.wargear].map(([raw_name, count]) => ({ raw_name, count })),\n };\n}\n\nfunction parseFirstLine(line: string): { name: string; faction: string | null; declared_limit: number | null } | null {\n const m = FIRST_LINE.exec(line);\n if (!m) return null;\n const declared_limit = Number.parseInt(m[2], 10);\n const parts = m[1].split(\" - \").map((s) => s.trim()).filter((s) => s.length > 0);\n if (parts.length === 0) return null;\n const list_name = parts[parts.length - 1];\n const faction = parts.length >= 2 ? parts[parts.length - 2] : null;\n return { name: list_name, faction, declared_limit };\n}\n\ntype Section = \"preamble\" | \"configuration\" | \"units\";\n\nexport const newRecruitSimpleAdapter: FormatAdapter = {\n id: \"newrecruit-simple\",\n\n matches(decoded: unknown): boolean {\n if (typeof decoded !== \"string\") return false;\n const lines = decoded.split(/\\r?\\n/);\n const firstNonBlank = lines.find((l) => l.trim().length > 0);\n if (!firstNonBlank) return false;\n if (!FIRST_LINE.test(firstNonBlank)) return false;\n return /^#\\s*\\+\\+\\s*Army Roster\\s*\\+\\+/m.test(decoded);\n },\n\n parse(decoded: unknown): ParsedRoster {\n if (typeof decoded !== \"string\") {\n throw new Error(\"newrecruit-simple: input is not a string\");\n }\n const lines = decoded.split(/\\r?\\n/);\n\n let name = \"Imported roster\";\n let faction_raw_name: string | null = null;\n let declared_limit: number | null = null;\n let total_reported: number | null = null;\n let detachment_raw_name: string | null = null;\n let battle_size_raw: string | null = null;\n const units: ParsedUnit[] = [];\n let current: UnitBuilder | null = null;\n let multi_force = false;\n let section: Section = \"preamble\";\n const enhancementPts: number[] = [];\n\n const finalize = (): void => {\n if (current) {\n enhancementPts.push(current.enhancement_pts);\n units.push(finishUnit(current));\n current = null;\n }\n };\n\n for (let i = 0; i < lines.length; i += 1) {\n const raw = lines[i];\n const line = raw.trim();\n if (!line) continue;\n\n // First non-blank line carries `<breadcrumb> - <faction> - <list> - [N pts]`.\n if (section === \"preamble\" && name === \"Imported roster\") {\n const first = parseFirstLine(line);\n if (first) {\n name = first.name;\n faction_raw_name = first.faction;\n declared_limit = first.declared_limit;\n continue;\n }\n }\n\n const rosterMatch = ROSTER_HEADER.exec(line);\n if (rosterMatch) {\n total_reported = Number.parseInt(rosterMatch[1], 10);\n continue;\n }\n\n const sectionMatch = SECTION_HEADER.exec(line);\n if (sectionMatch) {\n finalize();\n const heading = sectionMatch[1].trim().toLowerCase();\n if (heading === \"configuration\") {\n section = \"configuration\";\n } else {\n section = \"units\";\n if (heading.includes(\"allied\")) multi_force = true;\n }\n continue;\n }\n\n if (section === \"configuration\") {\n const idx = line.indexOf(\":\");\n if (idx > 0) {\n const key = line.slice(0, idx).trim().toLowerCase();\n const value = line.slice(idx + 1).trim();\n if (key === \"battle size\") battle_size_raw = value;\n else if (key === \"detachment\") detachment_raw_name = value;\n }\n continue;\n }\n\n // Unit section. A bullet line extends the *current* unit.\n const bulletMatch = BULLET.exec(raw);\n if (bulletMatch && current) {\n const count = Number.parseInt(bulletMatch[1], 10);\n // Bullets may add to the unit's model count beyond the implicit 1 we\n // set when we created it from the unit header.\n if (current.wargear.size === 0 && current.model_count === 1) {\n // First bullet: replace the implicit single-model assumption.\n current.model_count = count;\n } else {\n current.model_count += count;\n }\n if (bulletMatch[4]) applyTokens(current, bulletMatch[4], count);\n continue;\n }\n\n const unitMatch = UNIT_LINE.exec(line);\n if (unitMatch) {\n finalize();\n const unitName = unitMatch[1].trim();\n const pts = Number.parseInt(unitMatch[2], 10);\n current = newUnit(unitName, pts);\n const inlineWargear = unitMatch[3]?.trim() ?? \"\";\n if (inlineWargear.length > 0) {\n applyTokens(current, inlineWargear, 1);\n }\n // Leave model_count at the default 1. If `•` bullet lines follow, the\n // bullet handler resets model_count to the (summed) bullet counts.\n continue;\n }\n }\n finalize();\n\n let total_computed = 0;\n for (let i = 0; i < units.length; i += 1) {\n total_computed += units[i].points ?? 0;\n total_computed += enhancementPts[i] ?? 0;\n }\n\n return {\n name,\n generated_by: null,\n faction_raw_name,\n detachment_raw_name,\n battle_size_raw,\n declared_limit,\n total_reported,\n total_computed,\n units,\n multi_force,\n };\n },\n};\n"]}
|
|
1
|
+
{"version":3,"file":"newrecruit-simple.js","sourceRoot":"","sources":["../../src/import/newrecruit-simple.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7E,2EAA2E;AAC3E,wEAAwE;AACxE,+CAA+C;AAC/C,MAAM,UAAU,GAAG,mDAAmD,CAAC;AACvE,MAAM,aAAa,GACjB,0EAA0E,CAAC;AAC7E,MAAM,cAAc,GAAG,2DAA2D,CAAC;AACnF,MAAM,SAAS,GAAG,+DAA+D,CAAC;AAClF,MAAM,MAAM,GACV,uFAAuF,CAAC;AAc1F,SAAS,OAAO,CAAC,IAAY,EAAE,aAA4B;IACzD,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,YAAY,EAAE,KAAK;QACnB,UAAU,EAAE,KAAK;QACjB,oBAAoB,EAAE,IAAI;QAC1B,eAAe,EAAE,CAAC;QAClB,aAAa;QACb,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,IAAI,GAAG,EAAE;KACnB,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,IAAiB,EAAE,KAAsB;IAC3D,KAAK,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,KAAK,EAAE,CAAC;QACxC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,IAAiB,EAAE,SAAiB,EAAE,UAAU,GAAG,CAAC;IACvE,MAAM,MAAM,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,GAAG,CAAC,UAAU;QAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;IAC3C,IAAI,GAAG,CAAC,YAAY;QAAE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;IAC/C,IAAI,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,oBAAoB,KAAK,IAAI,EAAE,CAAC;QACnE,IAAI,CAAC,oBAAoB,GAAG,GAAG,CAAC,oBAAoB,CAAC;QACrD,IAAI,CAAC,eAAe,GAAG,GAAG,CAAC,kBAAkB,IAAI,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrC,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,KAAK,EAAE,CAAC,CAAC,KAAK,GAAG,UAAU;KAC5B,CAAC,CAAC,CAAC;IACJ,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,UAAU,CAAC,IAAiB;IACnC,MAAM,MAAM,GACV,IAAI,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC;IACjF,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,MAAM;QACN,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;QAC/C,kBAAkB,EAAE,IAAI,CAAC,oBAAoB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe;QACpF,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;KAC7E,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AACtD,CAAC;AAID,MAAM,CAAC,MAAM,uBAAuB,GAAkB;IACpD,EAAE,EAAE,mBAAmB;IAEvB,OAAO,CAAC,OAAgB;QACtB,IAAI,OAAO,OAAO,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACrC,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,aAAa;YAAE,OAAO,KAAK,CAAC;QACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC;YAAE,OAAO,KAAK,CAAC;QAClD,0EAA0E;QAC1E,iDAAiD;QACjD,OAAO,CACL,iCAAiC,CAAC,IAAI,CAAC,OAAO,CAAC;YAC/C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CACxB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAgB;QACpB,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAErC,IAAI,IAAI,GAAG,iBAAiB,CAAC;QAC7B,IAAI,gBAAgB,GAAkB,IAAI,CAAC;QAC3C,IAAI,cAAc,GAAkB,IAAI,CAAC;QACzC,IAAI,cAAc,GAAkB,IAAI,CAAC;QACzC,IAAI,mBAAmB,GAAkB,IAAI,CAAC;QAC9C,IAAI,eAAe,GAAkB,IAAI,CAAC;QAC1C,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,IAAI,OAAO,GAAuB,IAAI,CAAC;QACvC,IAAI,WAAW,GAAG,KAAK,CAAC;QACxB,IAAI,OAAO,GAAY,UAAU,CAAC;QAClC,MAAM,cAAc,GAAa,EAAE,CAAC;QAEpC,MAAM,QAAQ,GAAG,GAAS,EAAE;YAC1B,IAAI,OAAO,EAAE,CAAC;gBACZ,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;gBAC7C,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;gBAChC,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QAEF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACxB,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,8EAA8E;YAC9E,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBACzD,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;gBACnC,IAAI,KAAK,EAAE,CAAC;oBACV,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;oBAClB,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC;oBACjC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;oBACtC,SAAS;gBACX,CAAC;YACH,CAAC;YAED,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,WAAW,EAAE,CAAC;gBAChB,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrD,SAAS;YACX,CAAC;YAED,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/C,IAAI,YAAY,EAAE,CAAC;gBACjB,QAAQ,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;gBACrD,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;oBAChC,OAAO,GAAG,eAAe,CAAC;gBAC5B,CAAC;qBAAM,CAAC;oBACN,OAAO,GAAG,OAAO,CAAC;oBAClB,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;wBAAE,WAAW,GAAG,IAAI,CAAC;gBACrD,CAAC;gBACD,SAAS;YACX,CAAC;YAED,IAAI,OAAO,KAAK,eAAe,EAAE,CAAC;gBAChC,qEAAqE;gBACrE,uEAAuE;gBACvE,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,OAAO,GAAG,OAAO,CAAC;gBACpB,CAAC;qBAAM,CAAC;oBACN,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;oBAC9B,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;wBACZ,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;wBACpD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;wBACzC,IAAI,GAAG,KAAK,aAAa;4BAAE,eAAe,GAAG,KAAK,CAAC;6BAC9C,IAAI,GAAG,KAAK,YAAY;4BAAE,mBAAmB,GAAG,KAAK,CAAC;oBAC7D,CAAC;oBACD,SAAS;gBACX,CAAC;YACH,CAAC;YAED,0DAA0D;YAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,WAAW,IAAI,OAAO,EAAE,CAAC;gBAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAClD,qEAAqE;gBACrE,+CAA+C;gBAC/C,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,OAAO,CAAC,WAAW,KAAK,CAAC,EAAE,CAAC;oBAC5D,8DAA8D;oBAC9D,OAAO,CAAC,WAAW,GAAG,KAAK,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,WAAW,IAAI,KAAK,CAAC;gBAC/B,CAAC;gBACD,IAAI,WAAW,CAAC,CAAC,CAAC;oBAAE,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBAChE,SAAS;YACX,CAAC;YAED,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvC,IAAI,SAAS,EAAE,CAAC;gBACd,QAAQ,EAAE,CAAC;gBACX,MAAM,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACrC,MAAM,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC9C,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;gBACjC,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBACjD,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC7B,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;gBACzC,CAAC;gBACD,sEAAsE;gBACtE,mEAAmE;gBACnE,SAAS;YACX,CAAC;QACH,CAAC;QACD,QAAQ,EAAE,CAAC;QAEX,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;YACvC,cAAc,IAAI,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;QAED,OAAO;YACL,IAAI;YACJ,YAAY,EAAE,IAAI;YAClB,gBAAgB;YAChB,mBAAmB;YACnB,eAAe;YACf,cAAc;YACd,cAAc;YACd,cAAc;YACd,KAAK;YACL,WAAW;SACZ,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["/**\n * NewRecruit \"simple\" markdown-ish text adapter.\n *\n * Shape:\n * ```\n * <breadcrumb> - <faction> - <list name> - [N pts]\n *\n * # ++ Army Roster ++ [N pts]\n * ## Configuration\n * Battle Size: <Label>\n * Detachment: <Name>\n * Show/Hide Options: ...\n *\n * ## <Section> [N pts]\n * <Unit> [N pts]: <wargear>\n * <Unit> [N pts]:\n * • <count>x <ModelType>[ [N pts]]: <wargear>\n * ```\n *\n * Enhancements are inlined in the wargear list as `<Name> [N pts]` — the only\n * wargear token wearing a `[…]` pts suffix. `Warlord` and the detachment\n * \"<X> Character\" keyword are also stripped from the list and set as flags.\n * Per-model-type breakdowns under `•` lines are collapsed onto the parent unit.\n *\n * @packageDocumentation\n */\nimport type { FormatAdapter } from \"./adapter.js\";\nimport type { ParsedRoster, ParsedUnit, ParsedWargear } from \"./types.js\";\nimport { classifyWargearList, splitWargearList } from \"./newrecruit-text.js\";\n\n// Point brackets may carry comma-separated faction resources after the pts\n// figure (e.g. `[4485pts, 29Cabal Points]`); the tail is recognized and\n// discarded — only the pts figure is consumed.\nconst FIRST_LINE = /^(.+)\\s-\\s\\[\\s*(\\d+)\\s*pts?\\s*(?:,[^\\]]*)?\\]\\s*$/i;\nconst ROSTER_HEADER =\n /^#\\s*\\+\\+\\s*Army Roster\\s*\\+\\+\\s*\\[\\s*(\\d+)\\s*pts?\\s*(?:,[^\\]]*)?\\]\\s*$/i;\nconst SECTION_HEADER = /^##\\s*(.+?)(?:\\s*\\[\\s*(\\d+)\\s*pts?\\s*(?:,[^\\]]*)?\\])?\\s*$/;\nconst UNIT_LINE = /^(.+?)\\s*\\[\\s*(\\d+)\\s*pts?\\s*(?:,[^\\]]*)?\\](?:\\s*:\\s*(.*))?$/i;\nconst BULLET =\n /^\\s*•\\s*(\\d+)x\\s+(.+?)(?:\\s*\\[\\s*(\\d+)\\s*pts?\\s*(?:,[^\\]]*)?\\])?(?:\\s*:\\s*(.*))?\\s*$/u;\n\ninterface UnitBuilder {\n raw_name: string;\n is_character: boolean;\n is_warlord: boolean;\n enhancement_raw_name: string | null;\n enhancement_pts: number;\n displayed_pts: number | null;\n model_count: number;\n /** Aggregated wargear, keyed by name. Counts sum across `• Nx ModelType` breakdowns. */\n wargear: Map<string, number>;\n}\n\nfunction newUnit(name: string, displayed_pts: number | null): UnitBuilder {\n return {\n raw_name: name,\n is_character: false,\n is_warlord: false,\n enhancement_raw_name: null,\n enhancement_pts: 0,\n displayed_pts,\n model_count: 1,\n wargear: new Map(),\n };\n}\n\nfunction addWargear(unit: UnitBuilder, items: ParsedWargear[]): void {\n for (const { raw_name, count } of items) {\n unit.wargear.set(raw_name, (unit.wargear.get(raw_name) ?? 0) + count);\n }\n}\n\nfunction applyTokens(unit: UnitBuilder, tokensCsv: string, multiplier = 1): void {\n const tokens = splitWargearList(tokensCsv);\n const cls = classifyWargearList(tokens);\n if (cls.is_warlord) unit.is_warlord = true;\n if (cls.is_character) unit.is_character = true;\n if (cls.enhancement_raw_name && unit.enhancement_raw_name === null) {\n unit.enhancement_raw_name = cls.enhancement_raw_name;\n unit.enhancement_pts = cls.enhancement_points ?? 0;\n }\n const scaled = cls.wargear.map((w) => ({\n raw_name: w.raw_name,\n count: w.count * multiplier,\n }));\n addWargear(unit, scaled);\n}\n\nfunction finishUnit(unit: UnitBuilder): ParsedUnit {\n const points =\n unit.displayed_pts === null ? null : unit.displayed_pts - unit.enhancement_pts;\n return {\n raw_name: unit.raw_name,\n is_character: unit.is_character,\n model_count: unit.model_count,\n points,\n is_warlord: unit.is_warlord,\n enhancement_raw_name: unit.enhancement_raw_name,\n enhancement_points: unit.enhancement_raw_name === null ? null : unit.enhancement_pts,\n wargear: [...unit.wargear].map(([raw_name, count]) => ({ raw_name, count })),\n };\n}\n\nfunction parseFirstLine(line: string): { name: string; faction: string | null; declared_limit: number | null } | null {\n const m = FIRST_LINE.exec(line);\n if (!m) return null;\n const declared_limit = Number.parseInt(m[2], 10);\n const parts = m[1].split(\" - \").map((s) => s.trim()).filter((s) => s.length > 0);\n if (parts.length === 0) return null;\n const list_name = parts[parts.length - 1];\n const faction = parts.length >= 2 ? parts[parts.length - 2] : null;\n return { name: list_name, faction, declared_limit };\n}\n\ntype Section = \"preamble\" | \"configuration\" | \"units\";\n\nexport const newRecruitSimpleAdapter: FormatAdapter = {\n id: \"newrecruit-simple\",\n\n matches(decoded: unknown): boolean {\n if (typeof decoded !== \"string\") return false;\n const lines = decoded.split(/\\r?\\n/);\n const firstNonBlank = lines.find((l) => l.trim().length > 0);\n if (!firstNonBlank) return false;\n if (!FIRST_LINE.test(firstNonBlank)) return false;\n // Some exports omit the `# ++ Army Roster ++` line and open straight with\n // a `## Section` heading — accept either marker.\n return (\n /^#\\s*\\+\\+\\s*Army Roster\\s*\\+\\+/m.test(decoded) ||\n /^##\\s+/m.test(decoded)\n );\n },\n\n parse(decoded: unknown): ParsedRoster {\n if (typeof decoded !== \"string\") {\n throw new Error(\"newrecruit-simple: input is not a string\");\n }\n const lines = decoded.split(/\\r?\\n/);\n\n let name = \"Imported roster\";\n let faction_raw_name: string | null = null;\n let declared_limit: number | null = null;\n let total_reported: number | null = null;\n let detachment_raw_name: string | null = null;\n let battle_size_raw: string | null = null;\n const units: ParsedUnit[] = [];\n let current: UnitBuilder | null = null;\n let multi_force = false;\n let section: Section = \"preamble\";\n const enhancementPts: number[] = [];\n\n const finalize = (): void => {\n if (current) {\n enhancementPts.push(current.enhancement_pts);\n units.push(finishUnit(current));\n current = null;\n }\n };\n\n for (let i = 0; i < lines.length; i += 1) {\n const raw = lines[i];\n const line = raw.trim();\n if (!line) continue;\n\n // First non-blank line carries `<breadcrumb> - <faction> - <list> - [N pts]`.\n if (section === \"preamble\" && name === \"Imported roster\") {\n const first = parseFirstLine(line);\n if (first) {\n name = first.name;\n faction_raw_name = first.faction;\n declared_limit = first.declared_limit;\n continue;\n }\n }\n\n const rosterMatch = ROSTER_HEADER.exec(line);\n if (rosterMatch) {\n total_reported = Number.parseInt(rosterMatch[1], 10);\n continue;\n }\n\n const sectionMatch = SECTION_HEADER.exec(line);\n if (sectionMatch) {\n finalize();\n const heading = sectionMatch[1].trim().toLowerCase();\n if (heading === \"configuration\") {\n section = \"configuration\";\n } else {\n section = \"units\";\n if (heading.includes(\"allied\")) multi_force = true;\n }\n continue;\n }\n\n if (section === \"configuration\") {\n // Some exports list units directly after Configuration with no units\n // section heading; a `Name [N pts]` line ends the configuration block.\n if (UNIT_LINE.test(line)) {\n section = \"units\";\n } else {\n const idx = line.indexOf(\":\");\n if (idx > 0) {\n const key = line.slice(0, idx).trim().toLowerCase();\n const value = line.slice(idx + 1).trim();\n if (key === \"battle size\") battle_size_raw = value;\n else if (key === \"detachment\") detachment_raw_name = value;\n }\n continue;\n }\n }\n\n // Unit section. A bullet line extends the *current* unit.\n const bulletMatch = BULLET.exec(raw);\n if (bulletMatch && current) {\n const count = Number.parseInt(bulletMatch[1], 10);\n // Bullets may add to the unit's model count beyond the implicit 1 we\n // set when we created it from the unit header.\n if (current.wargear.size === 0 && current.model_count === 1) {\n // First bullet: replace the implicit single-model assumption.\n current.model_count = count;\n } else {\n current.model_count += count;\n }\n if (bulletMatch[4]) applyTokens(current, bulletMatch[4], count);\n continue;\n }\n\n const unitMatch = UNIT_LINE.exec(line);\n if (unitMatch) {\n finalize();\n const unitName = unitMatch[1].trim();\n const pts = Number.parseInt(unitMatch[2], 10);\n current = newUnit(unitName, pts);\n const inlineWargear = unitMatch[3]?.trim() ?? \"\";\n if (inlineWargear.length > 0) {\n applyTokens(current, inlineWargear, 1);\n }\n // Leave model_count at the default 1. If `•` bullet lines follow, the\n // bullet handler resets model_count to the (summed) bullet counts.\n continue;\n }\n }\n finalize();\n\n let total_computed = 0;\n for (let i = 0; i < units.length; i += 1) {\n total_computed += units[i].points ?? 0;\n total_computed += enhancementPts[i] ?? 0;\n }\n\n return {\n name,\n generated_by: null,\n faction_raw_name,\n detachment_raw_name,\n battle_size_raw,\n declared_limit,\n total_reported,\n total_computed,\n units,\n multi_force,\n };\n },\n};\n"]}
|
package/dist/import/types.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface RosterUnit {
|
|
|
64
64
|
}
|
|
65
65
|
/** Identifier for the adapter that produced this roster. New format adapters
|
|
66
66
|
* extend this union; `roster.schema.json` keeps the canonical enum. */
|
|
67
|
-
export type RosterFormat = "listforge" | "newrecruit-json" | "newrecruit-wtc-compact" | "newrecruit-wtc-full" | "newrecruit-simple" | "rosterizer" | "gw";
|
|
67
|
+
export type RosterFormat = "listforge" | "listforge-text" | "newrecruit-json" | "newrecruit-wtc-compact" | "newrecruit-wtc-full" | "newrecruit-simple" | "rosterizer" | "gw";
|
|
68
68
|
/** Provenance of the imported list. */
|
|
69
69
|
export interface RosterSource {
|
|
70
70
|
format: RosterFormat;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/import/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/import/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC;AAEtD,yDAAyD;AACzD,MAAM,MAAM,WAAW,GACnB,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,GACnB,wBAAwB,GACxB,uBAAuB,GACvB,sBAAsB,GACtB,iBAAiB,GACjB,4BAA4B,GAC5B,aAAa,GACb,eAAe,CAAC;AAMpB,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,8DAA8D;IAC9D,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAED,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,mEAAmE;AACnE,MAAM,WAAW,sBAAsB;IACrC,aAAa,EAAE,WAAW,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,WAAW,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,mFAAmF;IACnF,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,iBAAiB,EAAE,sBAAsB,GAAG,IAAI,CAAC;CAClD;AAED;uEACuE;AACvE,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,gBAAgB,GAChB,iBAAiB,GACjB,wBAAwB,GACxB,qBAAqB,GACrB,mBAAmB,GACnB,YAAY,GACZ,IAAI,CAAC;AAET,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,mCAAmC;AACnC,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,qEAAqE;AACrE,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,0EAA0E;AAC1E,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,YAAY,EAAE,cAAc,CAAC;IAC7B,WAAW,EAAE,WAAW,CAAC;CAC1B;AAMD,uDAAuD;AACvD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,6CAA6C;AAC7C,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,mFAAmF;IACnF,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,8CAA8C;IAC9C,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,yEAAyE;IACzE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,8DAA8D;IAC9D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,sDAAsD;IACtD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,qEAAqE;IACrE,WAAW,EAAE,OAAO,CAAC;CACtB"}
|
package/dist/import/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/import/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG","sourcesContent":["/**\n * Types for the army-list importer.\n *\n * Two layers live here:\n * - The **output** types ({@link Roster} and friends) mirror\n * `schemas/core/roster.schema.json` field-for-field. They are hand-authored\n * rather than generated so importer work isn't gated on the Rust→typify codegen\n * round-trip; the AJV validator (against the real schema) is the source of truth\n * for conformance.\n * - The **intermediate** type ({@link ParsedRoster}) is format-agnostic: a parser\n * adapter lowers a source payload to this shape (raw names + counts only, no\n * resolved ids), and {@link resolve} turns it into a {@link Roster}.\n *\n * Nothing here ever carries reproduced rules or ability text — only permitted\n * facts (names, counts, points, keywords, entity ids).\n *\n * @packageDocumentation\n */\n\n/** A 40kdc battle size (mirrors the shared `battle-size` def). */\nexport type BattleSize = \"incursion\" | \"strike-force\";\n\n/** Diagnostic warning codes emitted during an import. */\nexport type WarningCode =\n | \"faction-unresolved\"\n | \"unit-unresolved\"\n | \"weapon-unresolved\"\n | \"enhancement-unresolved\"\n | \"detachment-unresolved\"\n | \"battle-size-unmapped\"\n | \"points-mismatch\"\n | \"leader-attachment-inferred\"\n | \"multi-force\"\n | \"unknown-field\";\n\n// ---------------------------------------------------------------------------\n// Output types (mirror roster.schema.json)\n// ---------------------------------------------------------------------------\n\n/** A near-match suggestion offered when resolution fails. */\nexport interface Candidate {\n id: string;\n name: string;\n}\n\n/**\n * A reference to a 40kdc entity that may or may not have resolved. Retains the\n * source's raw name so the import is lossless even on a miss.\n */\nexport interface ResolvedRef {\n /** Resolved entity id, or null when no match was found. */\n id: string | null;\n /** The display name exactly as it appeared in the source payload. */\n raw_name: string;\n /** True iff {@link id} is non-null. */\n resolved: boolean;\n /** Up to 5 best-guess alternatives when resolution failed. */\n candidates: Candidate[];\n}\n\n/** A weapon/wargear selection on a unit. */\nexport interface RosterWargear {\n ref: ResolvedRef;\n count: number;\n}\n\n/** An inferred, always-provisional leader→bodyguard attachment. */\nexport interface RosterLeaderAttachment {\n bodyguard_ref: ResolvedRef;\n provisional: boolean;\n}\n\n/** One unit entry in a roster. */\nexport interface RosterUnit {\n ref: ResolvedRef;\n model_count: number;\n /** Base unit cost (without the enhancement). */\n points: number | null;\n is_warlord: boolean;\n enhancement: ResolvedRef | null;\n /** Points cost of the enhancement when the source reported one; null otherwise. */\n enhancement_points: number | null;\n wargear: RosterWargear[];\n leader_attachment: RosterLeaderAttachment | null;\n}\n\n/** Identifier for the adapter that produced this roster. New format adapters\n * extend this union; `roster.schema.json` keeps the canonical enum. */\nexport type RosterFormat =\n | \"listforge\"\n | \"newrecruit-json\"\n | \"newrecruit-wtc-compact\"\n | \"newrecruit-wtc-full\"\n | \"newrecruit-simple\"\n | \"rosterizer\"\n | \"gw\";\n\n/** Provenance of the imported list. */\nexport interface RosterSource {\n format: RosterFormat;\n generated_by: string | null;\n}\n\n/** Point totals; reported and computed are kept distinct, never reconciled. */\nexport interface RosterPoints {\n declared_limit: number | null;\n total_reported: number | null;\n total_computed: number;\n}\n\n/** A single diagnostic warning. */\nexport interface Warning {\n code: WarningCode;\n message: string;\n raw_name: string | null;\n}\n\n/** A summary of what resolved and what did not during the import. */\nexport interface Diagnostics {\n resolved_units: number;\n unresolved_units: number;\n resolved_weapons: number;\n unresolved_weapons: number;\n warnings: Warning[];\n}\n\n/** Reference to the game edition + dataslate (mirrors game-version-ref). */\nexport interface GameVersionRef {\n edition: string;\n dataslate: string;\n}\n\n/** A fully-resolved army list. Validates against `roster.schema.json`. */\nexport interface Roster {\n name: string;\n source: RosterSource;\n faction_id: string | null;\n detachment_id: string | null;\n battle_size: BattleSize | null;\n points: RosterPoints;\n units: RosterUnit[];\n game_version: GameVersionRef;\n diagnostics: Diagnostics;\n}\n\n// ---------------------------------------------------------------------------\n// Intermediate types (format-agnostic; produced by a parser adapter)\n// ---------------------------------------------------------------------------\n\n/** A weapon/wargear selection before id resolution. */\nexport interface ParsedWargear {\n raw_name: string;\n count: number;\n}\n\n/** A unit selection before id resolution. */\nexport interface ParsedUnit {\n raw_name: string;\n /** True when the source classifies this as a character/leader-capable model. */\n is_character: boolean;\n model_count: number;\n /** Base unit cost (without the enhancement). */\n points: number | null;\n is_warlord: boolean;\n enhancement_raw_name: string | null;\n /** Points cost of the enhancement when the source reported one; null otherwise. */\n enhancement_points: number | null;\n wargear: ParsedWargear[];\n}\n\n/**\n * The format-agnostic intermediate. A {@link FormatAdapter} produces this from a\n * decoded source payload; {@link resolve} consumes it. Contains only raw display\n * names and counts — never reproduced rules text.\n */\nexport interface ParsedRoster {\n name: string;\n generated_by: string | null;\n /** Raw faction name from the source (e.g. \"Grey Knights\"). */\n faction_raw_name: string | null;\n /** Raw detachment name (e.g. \"Banishers\"). */\n detachment_raw_name: string | null;\n /** Raw battle-size label (e.g. \"2. Strike Force (2000 Point limit)\"). */\n battle_size_raw: string | null;\n /** Points limit parsed from the battle-size label, if any. */\n declared_limit: number | null;\n /** Total points reported by the source cost block. */\n total_reported: number | null;\n /** Points summed from every cost line in the source tree. */\n total_computed: number;\n units: ParsedUnit[];\n /** True when the source contained more than one distinct faction. */\n multi_force: boolean;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/import/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG","sourcesContent":["/**\n * Types for the army-list importer.\n *\n * Two layers live here:\n * - The **output** types ({@link Roster} and friends) mirror\n * `schemas/core/roster.schema.json` field-for-field. They are hand-authored\n * rather than generated so importer work isn't gated on the Rust→typify codegen\n * round-trip; the AJV validator (against the real schema) is the source of truth\n * for conformance.\n * - The **intermediate** type ({@link ParsedRoster}) is format-agnostic: a parser\n * adapter lowers a source payload to this shape (raw names + counts only, no\n * resolved ids), and {@link resolve} turns it into a {@link Roster}.\n *\n * Nothing here ever carries reproduced rules or ability text — only permitted\n * facts (names, counts, points, keywords, entity ids).\n *\n * @packageDocumentation\n */\n\n/** A 40kdc battle size (mirrors the shared `battle-size` def). */\nexport type BattleSize = \"incursion\" | \"strike-force\";\n\n/** Diagnostic warning codes emitted during an import. */\nexport type WarningCode =\n | \"faction-unresolved\"\n | \"unit-unresolved\"\n | \"weapon-unresolved\"\n | \"enhancement-unresolved\"\n | \"detachment-unresolved\"\n | \"battle-size-unmapped\"\n | \"points-mismatch\"\n | \"leader-attachment-inferred\"\n | \"multi-force\"\n | \"unknown-field\";\n\n// ---------------------------------------------------------------------------\n// Output types (mirror roster.schema.json)\n// ---------------------------------------------------------------------------\n\n/** A near-match suggestion offered when resolution fails. */\nexport interface Candidate {\n id: string;\n name: string;\n}\n\n/**\n * A reference to a 40kdc entity that may or may not have resolved. Retains the\n * source's raw name so the import is lossless even on a miss.\n */\nexport interface ResolvedRef {\n /** Resolved entity id, or null when no match was found. */\n id: string | null;\n /** The display name exactly as it appeared in the source payload. */\n raw_name: string;\n /** True iff {@link id} is non-null. */\n resolved: boolean;\n /** Up to 5 best-guess alternatives when resolution failed. */\n candidates: Candidate[];\n}\n\n/** A weapon/wargear selection on a unit. */\nexport interface RosterWargear {\n ref: ResolvedRef;\n count: number;\n}\n\n/** An inferred, always-provisional leader→bodyguard attachment. */\nexport interface RosterLeaderAttachment {\n bodyguard_ref: ResolvedRef;\n provisional: boolean;\n}\n\n/** One unit entry in a roster. */\nexport interface RosterUnit {\n ref: ResolvedRef;\n model_count: number;\n /** Base unit cost (without the enhancement). */\n points: number | null;\n is_warlord: boolean;\n enhancement: ResolvedRef | null;\n /** Points cost of the enhancement when the source reported one; null otherwise. */\n enhancement_points: number | null;\n wargear: RosterWargear[];\n leader_attachment: RosterLeaderAttachment | null;\n}\n\n/** Identifier for the adapter that produced this roster. New format adapters\n * extend this union; `roster.schema.json` keeps the canonical enum. */\nexport type RosterFormat =\n | \"listforge\"\n | \"listforge-text\"\n | \"newrecruit-json\"\n | \"newrecruit-wtc-compact\"\n | \"newrecruit-wtc-full\"\n | \"newrecruit-simple\"\n | \"rosterizer\"\n | \"gw\";\n\n/** Provenance of the imported list. */\nexport interface RosterSource {\n format: RosterFormat;\n generated_by: string | null;\n}\n\n/** Point totals; reported and computed are kept distinct, never reconciled. */\nexport interface RosterPoints {\n declared_limit: number | null;\n total_reported: number | null;\n total_computed: number;\n}\n\n/** A single diagnostic warning. */\nexport interface Warning {\n code: WarningCode;\n message: string;\n raw_name: string | null;\n}\n\n/** A summary of what resolved and what did not during the import. */\nexport interface Diagnostics {\n resolved_units: number;\n unresolved_units: number;\n resolved_weapons: number;\n unresolved_weapons: number;\n warnings: Warning[];\n}\n\n/** Reference to the game edition + dataslate (mirrors game-version-ref). */\nexport interface GameVersionRef {\n edition: string;\n dataslate: string;\n}\n\n/** A fully-resolved army list. Validates against `roster.schema.json`. */\nexport interface Roster {\n name: string;\n source: RosterSource;\n faction_id: string | null;\n detachment_id: string | null;\n battle_size: BattleSize | null;\n points: RosterPoints;\n units: RosterUnit[];\n game_version: GameVersionRef;\n diagnostics: Diagnostics;\n}\n\n// ---------------------------------------------------------------------------\n// Intermediate types (format-agnostic; produced by a parser adapter)\n// ---------------------------------------------------------------------------\n\n/** A weapon/wargear selection before id resolution. */\nexport interface ParsedWargear {\n raw_name: string;\n count: number;\n}\n\n/** A unit selection before id resolution. */\nexport interface ParsedUnit {\n raw_name: string;\n /** True when the source classifies this as a character/leader-capable model. */\n is_character: boolean;\n model_count: number;\n /** Base unit cost (without the enhancement). */\n points: number | null;\n is_warlord: boolean;\n enhancement_raw_name: string | null;\n /** Points cost of the enhancement when the source reported one; null otherwise. */\n enhancement_points: number | null;\n wargear: ParsedWargear[];\n}\n\n/**\n * The format-agnostic intermediate. A {@link FormatAdapter} produces this from a\n * decoded source payload; {@link resolve} consumes it. Contains only raw display\n * names and counts — never reproduced rules text.\n */\nexport interface ParsedRoster {\n name: string;\n generated_by: string | null;\n /** Raw faction name from the source (e.g. \"Grey Knights\"). */\n faction_raw_name: string | null;\n /** Raw detachment name (e.g. \"Banishers\"). */\n detachment_raw_name: string | null;\n /** Raw battle-size label (e.g. \"2. Strike Force (2000 Point limit)\"). */\n battle_size_raw: string | null;\n /** Points limit parsed from the battle-size label, if any. */\n declared_limit: number | null;\n /** Total points reported by the source cost block. */\n total_reported: number | null;\n /** Points summed from every cost line in the source tree. */\n total_computed: number;\n units: ParsedUnit[];\n /** True when the source contained more than one distinct faction. */\n multi_force: boolean;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,6 @@ export type { AbilityScope } from "./generated.js";
|
|
|
6
6
|
export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, TerrainResolveError, solveCentroid, solveCentroidTriangulated, solveCentroidAttached, TerrainSolveError, keystoneMeasurements, BOARD_INCHES, TerrainKeystoneError, } from "./terrain/index.js";
|
|
7
7
|
export type { ResolvedPiece, ResolvedVec2, BoardEdge, FeatureRef, Keystone, KeystoneMeasurement, DimensionLine, SolveInput, TriangulationLine, TriangulateInput, AttachLine, AttachPiece, AttachInput, } from "./terrain/index.js";
|
|
8
8
|
export * from "./scoring/index.js";
|
|
9
|
-
export { createValidator, findSchemaFiles, listSchemaIds, SCHEMAS_ROOT, } from "./schema-loader.js";
|
|
10
9
|
export { importListForge, importNewRecruit, importRoster, tryImportRoster, decodeListForge, } from "./import/index.js";
|
|
11
10
|
export { exportRoster, newRecruitJsonSerializer, newRecruitSimpleSerializer, newRecruitWtcCompactSerializer, newRecruitWtcFullSerializer, rosterJsonSerializer, rosterizerSerializer, } from "./export/index.js";
|
|
12
11
|
export type { ExportFormat, RosterSerializer } from "./export/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAGhC,cAAc,gBAAgB,CAAC;AAI/B,cAAc,sBAAsB,CAAC;AAKrC,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnD,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAK5B,cAAc,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,iBAAiB,CAAC;AAGhC,cAAc,gBAAgB,CAAC;AAI/B,cAAc,sBAAsB,CAAC;AAKrC,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIrD,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnD,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,SAAS,EACT,UAAU,EACV,QAAQ,EACR,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,iBAAiB,EACjB,gBAAgB,EAChB,UAAU,EACV,WAAW,EACX,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAK5B,cAAc,oBAAoB,CAAC;AAUnC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,YAAY,EACZ,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACxE,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EACV,aAAa,EACb,YAAY,EACZ,mBAAmB,EACnB,YAAY,EACZ,MAAM,EACN,UAAU,EACV,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,WAAW,EACX,SAAS,EACT,WAAW,EACX,OAAO,EACP,WAAW,EACX,YAAY,EACZ,UAAU,EACV,aAAa,GACd,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -13,9 +13,10 @@ export { resolveLayout, polygonCentroid, footprintVertices, orientedOffsets, Ter
|
|
|
13
13
|
// track per-round, per-player scoring. Mirrored by the Rust `wh40kdc::scoring`
|
|
14
14
|
// module and pinned by the `conformance/scoring` corpus.
|
|
15
15
|
export * from "./scoring/index.js";
|
|
16
|
-
// Schema access + AJV validation
|
|
17
|
-
//
|
|
18
|
-
|
|
16
|
+
// Schema access + AJV validation lives behind the `./validate` subpath export
|
|
17
|
+
// (`@alpaca-software/40kdc-data/validate`), NOT the root barrel: it reads
|
|
18
|
+
// schema files from disk at module load (node:fs/node:url), which breaks
|
|
19
|
+
// browser bundles. The root barrel stays universal (Node + browser).
|
|
19
20
|
// Army-list importer (ListForge → resolved 40kdc roster). Types are curated
|
|
20
21
|
// rather than re-exported wholesale to avoid name clashes with generated types
|
|
21
22
|
// (e.g. BattleSize, LeaderAttachment).
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,cAAc,iBAAiB,CAAC;AAEhC,mDAAmD;AACnD,cAAc,gBAAgB,CAAC;AAE/B,0EAA0E;AAC1E,6EAA6E;AAC7E,cAAc,sBAAsB,CAAC;AAWrC,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAiB5B,6EAA6E;AAC7E,+EAA+E;AAC/E,yDAAyD;AACzD,cAAc,oBAAoB,CAAC;AAEnC,8EAA8E;AAC9E,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,cAAc,iBAAiB,CAAC;AAEhC,mDAAmD;AACnD,cAAc,gBAAgB,CAAC;AAE/B,0EAA0E;AAC1E,6EAA6E;AAC7E,cAAc,sBAAsB,CAAC;AAWrC,+EAA+E;AAC/E,2EAA2E;AAC3E,8EAA8E;AAC9E,OAAO,EACL,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAiB5B,6EAA6E;AAC7E,+EAA+E;AAC/E,yDAAyD;AACzD,cAAc,oBAAoB,CAAC;AAEnC,8EAA8E;AAC9E,0EAA0E;AAC1E,yEAAyE;AACzE,qEAAqE;AAErE,4EAA4E;AAC5E,+EAA+E;AAC/E,uCAAuC;AACvC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,+EAA+E;AAC/E,OAAO,EACL,YAAY,EACZ,wBAAwB,EACxB,0BAA0B,EAC1B,8BAA8B,EAC9B,2BAA2B,EAC3B,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC","sourcesContent":["// The linked, typed dataset — the primary entry point.\nexport * from \"./data/index.js\";\n\n// Generated types for every entity in the dataset.\nexport * from \"./generated.js\";\n\n// Plain-English translation of structured data (scoring-card awards + the\n// shared Ability-DSL condition humanizer). Cross-impl pinned by conformance.\nexport * from \"./translate/index.js\";\n\n// `ScoringTrigger` is emitted by both ./generated.js (schema-derived) and\n// ./translate (hand-authored). They are structurally identical; disambiguate the\n// two wildcard re-exports in favour of the generated, schema-canonical type.\nexport type { ScoringTrigger } from \"./generated.js\";\n\n// `AbilityScope` likewise: ./translate/effect.ts hand-authors a looser view\n// for the describer; prefer the generated, schema-canonical type.\nexport type { AbilityScope } from \"./generated.js\";\n\n// Terrain geometry resolver (template-anchored layout → board-space vertices).\n// Curated (not wildcard) so its internal type aliases don't clash with the\n// generated Footprint/TerrainTemplate/TerrainLayout types. Cross-impl pinned.\nexport {\n resolveLayout,\n polygonCentroid,\n footprintVertices,\n orientedOffsets,\n TerrainResolveError,\n solveCentroid,\n solveCentroidTriangulated,\n solveCentroidAttached,\n TerrainSolveError,\n keystoneMeasurements,\n BOARD_INCHES,\n TerrainKeystoneError,\n} from \"./terrain/index.js\";\nexport type {\n ResolvedPiece,\n ResolvedVec2,\n BoardEdge,\n FeatureRef,\n Keystone,\n KeystoneMeasurement,\n DimensionLine,\n SolveInput,\n TriangulationLine,\n TriangulateInput,\n AttachLine,\n AttachPiece,\n AttachInput,\n} from \"./terrain/index.js\";\n\n// Card-driven secondary-mission scoring: compute VP from asserted awards and\n// track per-round, per-player scoring. Mirrored by the Rust `wh40kdc::scoring`\n// module and pinned by the `conformance/scoring` corpus.\nexport * from \"./scoring/index.js\";\n\n// Schema access + AJV validation lives behind the `./validate` subpath export\n// (`@alpaca-software/40kdc-data/validate`), NOT the root barrel: it reads\n// schema files from disk at module load (node:fs/node:url), which breaks\n// browser bundles. The root barrel stays universal (Node + browser).\n\n// Army-list importer (ListForge → resolved 40kdc roster). Types are curated\n// rather than re-exported wholesale to avoid name clashes with generated types\n// (e.g. BattleSize, LeaderAttachment).\nexport {\n importListForge,\n importNewRecruit,\n importRoster,\n tryImportRoster,\n decodeListForge,\n} from \"./import/index.js\";\n\n// Army-list exporter (Roster → text or JSON for any of the supported formats).\nexport {\n exportRoster,\n newRecruitJsonSerializer,\n newRecruitSimpleSerializer,\n newRecruitWtcCompactSerializer,\n newRecruitWtcFullSerializer,\n rosterJsonSerializer,\n rosterizerSerializer,\n} from \"./export/index.js\";\nexport type { ExportFormat, RosterSerializer } from \"./export/index.js\";\nexport type { FormatAdapter } from \"./import/index.js\";\nexport type {\n ImportOptions,\n ImportResult,\n ImportFailureReason,\n AdapterTrial,\n Roster,\n RosterUnit,\n RosterWargear,\n RosterSource,\n RosterFormat,\n RosterPoints,\n RosterLeaderAttachment,\n ResolvedRef,\n Candidate,\n Diagnostics,\n Warning,\n WarningCode,\n ParsedRoster,\n ParsedUnit,\n ParsedWargear,\n} from \"./import/index.js\";\n"]}
|