@rohal12/twee-ts 1.12.0 → 1.13.0
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/bin/twee-ts.js +66 -49
- package/dist/bin/twee-ts.js.map +1 -1
- package/dist/{chunk-TYLUAJTM.js → chunk-ORMESTR7.js} +67 -50
- package/dist/chunk-ORMESTR7.js.map +1 -0
- package/dist/index.cjs +97 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -19
- package/dist/index.d.ts +66 -19
- package/dist/index.js +94 -50
- package/dist/index.js.map +1 -1
- package/dist/plugins/rollup.js +1 -1
- package/dist/plugins/vite.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TYLUAJTM.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -54,17 +54,23 @@ interface WatchOptions extends CompileToFileOptions {
|
|
|
54
54
|
/** Called on build errors. */
|
|
55
55
|
onError?: (error: Error) => void;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
level: 'warning'
|
|
57
|
+
type Diagnostic = {
|
|
58
|
+
level: 'warning';
|
|
59
59
|
message: string;
|
|
60
60
|
file?: string;
|
|
61
61
|
line?: number;
|
|
62
|
-
}
|
|
62
|
+
} | {
|
|
63
|
+
level: 'error';
|
|
64
|
+
message: string;
|
|
65
|
+
file?: string;
|
|
66
|
+
line?: number;
|
|
67
|
+
fatal?: boolean;
|
|
68
|
+
};
|
|
63
69
|
interface CompileResult {
|
|
64
70
|
/** The compiled output string (HTML, Twee, JSON, etc.). */
|
|
65
71
|
output: string;
|
|
66
|
-
/** The parsed story model. */
|
|
67
|
-
story:
|
|
72
|
+
/** The parsed story model (read-only after compilation). */
|
|
73
|
+
story: ReadonlyStory;
|
|
68
74
|
/** The format used for compilation (undefined for non-HTML modes). */
|
|
69
75
|
format?: StoryFormatInfo;
|
|
70
76
|
/** Collected diagnostics. */
|
|
@@ -94,6 +100,9 @@ interface Passage {
|
|
|
94
100
|
metadata?: PassageMetadata;
|
|
95
101
|
source?: SourceLocation;
|
|
96
102
|
}
|
|
103
|
+
type IFID = string & {
|
|
104
|
+
readonly __brand: 'IFID';
|
|
105
|
+
};
|
|
97
106
|
interface Twine1Metadata {
|
|
98
107
|
settings: Map<string, string>;
|
|
99
108
|
}
|
|
@@ -108,12 +117,25 @@ interface Twine2Metadata {
|
|
|
108
117
|
}
|
|
109
118
|
interface Story {
|
|
110
119
|
name: string;
|
|
111
|
-
ifid:
|
|
120
|
+
ifid: IFID;
|
|
112
121
|
passages: Passage[];
|
|
113
|
-
legacyIFID:
|
|
122
|
+
legacyIFID: IFID;
|
|
114
123
|
twine1: Twine1Metadata;
|
|
115
124
|
twine2: Twine2Metadata;
|
|
116
125
|
}
|
|
126
|
+
type ReadonlyPassage = Readonly<Passage> & {
|
|
127
|
+
readonly tags: readonly string[];
|
|
128
|
+
};
|
|
129
|
+
type ReadonlyStory = Readonly<Omit<Story, 'passages' | 'twine1' | 'twine2'>> & {
|
|
130
|
+
readonly passages: readonly ReadonlyPassage[];
|
|
131
|
+
readonly twine1: Readonly<Omit<Twine1Metadata, 'settings'>> & {
|
|
132
|
+
readonly settings: ReadonlyMap<string, string>;
|
|
133
|
+
};
|
|
134
|
+
readonly twine2: Readonly<Omit<Twine2Metadata, 'options' | 'tagColors'>> & {
|
|
135
|
+
readonly options: ReadonlyMap<string, boolean>;
|
|
136
|
+
readonly tagColors: ReadonlyMap<string, string>;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
117
139
|
interface StoryFormatInfo {
|
|
118
140
|
id: string;
|
|
119
141
|
filename: string;
|
|
@@ -138,15 +160,16 @@ interface Twine2FormatJSON {
|
|
|
138
160
|
url?: string;
|
|
139
161
|
license?: string;
|
|
140
162
|
}
|
|
141
|
-
declare const
|
|
142
|
-
Error
|
|
143
|
-
EOF
|
|
144
|
-
Header
|
|
145
|
-
Name
|
|
146
|
-
Tags
|
|
147
|
-
Metadata
|
|
148
|
-
Content
|
|
149
|
-
}
|
|
163
|
+
declare const ItemType: {
|
|
164
|
+
readonly Error: 0;
|
|
165
|
+
readonly EOF: 1;
|
|
166
|
+
readonly Header: 2;
|
|
167
|
+
readonly Name: 3;
|
|
168
|
+
readonly Tags: 4;
|
|
169
|
+
readonly Metadata: 5;
|
|
170
|
+
readonly Content: 6;
|
|
171
|
+
};
|
|
172
|
+
type ItemType = (typeof ItemType)[keyof typeof ItemType];
|
|
150
173
|
interface LexerItem {
|
|
151
174
|
type: ItemType;
|
|
152
175
|
line: number;
|
|
@@ -273,7 +296,29 @@ interface BrokenLink {
|
|
|
273
296
|
* expect(map.deadEnds).not.toContain('Start');
|
|
274
297
|
* ```
|
|
275
298
|
*/
|
|
276
|
-
declare function storyInspect(story:
|
|
299
|
+
declare function storyInspect(story: ReadonlyStory): StoryMap;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Story model + StoryData JSON marshal/unmarshal.
|
|
303
|
+
* Ported from story.go + storydata.go.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Builder that separates the mutable construction phase from the immutable consumption phase.
|
|
308
|
+
* During construction, the internal `Story` is mutable via `add()` and direct access.
|
|
309
|
+
* After `build()`, the story is returned as `ReadonlyStory`.
|
|
310
|
+
*/
|
|
311
|
+
declare class StoryBuilder {
|
|
312
|
+
/** The mutable story, accessible during construction for loader functions. */
|
|
313
|
+
readonly story: Story;
|
|
314
|
+
constructor();
|
|
315
|
+
/** Add a passage, handling special passages (StoryData, StoryTitle, etc.). */
|
|
316
|
+
add(passage: Passage, diagnostics: Diagnostic[]): void;
|
|
317
|
+
/** Check if a passage name exists. */
|
|
318
|
+
has(name: string): boolean;
|
|
319
|
+
/** Finalize and return the story as read-only. */
|
|
320
|
+
build(): ReadonlyStory;
|
|
321
|
+
}
|
|
277
322
|
|
|
278
323
|
interface DecompileResult {
|
|
279
324
|
story: Story;
|
|
@@ -401,7 +446,9 @@ declare function parseSemver(v: string): [number, number, number] | null;
|
|
|
401
446
|
declare function semverCompare(a: [number, number, number], b: [number, number, number]): number;
|
|
402
447
|
|
|
403
448
|
/** Generate a new IFID (UUID v4, uppercase). */
|
|
404
|
-
declare function generateIFID():
|
|
449
|
+
declare function generateIFID(): IFID;
|
|
450
|
+
/** Validate and brand an IFID string. Throws on invalid input. */
|
|
451
|
+
declare function createIFID(value: string): IFID;
|
|
405
452
|
/**
|
|
406
453
|
* Validate an IFID string.
|
|
407
454
|
* Accepts both bare UUIDs and UUID://...// wrapped format.
|
|
@@ -451,4 +498,4 @@ declare function getCacheSize(): {
|
|
|
451
498
|
count: number;
|
|
452
499
|
};
|
|
453
500
|
|
|
454
|
-
export { type BrokenLink, CONFIG_FILENAME, type CachedFormatEntry, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type DecompileResult, type Diagnostic, type FileCacheEntry, type InlineSource, ItemType, type LexerItem, type LintResult, type OutputMode, type Passage, type PassageMetadata, type SFAIndex, type SFAIndexEntry, type SourceInput, type SourceLocation, type Story, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, type WordCountMethod, applyTagAliases, clearCachedFormats, compile, compileIncremental, compileToFile, decompileHTML, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, formatLintReport, generateIFID, getCacheDir, getCacheSize, getFormatSearchDirs, lint, listCachedFormats, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
|
501
|
+
export { type BrokenLink, CONFIG_FILENAME, type CachedFormatEntry, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type DecompileResult, type Diagnostic, type FileCacheEntry, type IFID, type InlineSource, ItemType, type LexerItem, type LintResult, type OutputMode, type Passage, type PassageMetadata, type ReadonlyPassage, type ReadonlyStory, type SFAIndex, type SFAIndexEntry, type SourceInput, type SourceLocation, type Story, StoryBuilder, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, type WordCountMethod, applyTagAliases, clearCachedFormats, compile, compileIncremental, compileToFile, createIFID, decompileHTML, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, formatLintReport, generateIFID, getCacheDir, getCacheSize, getFormatSearchDirs, lint, listCachedFormats, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
package/dist/index.d.ts
CHANGED
|
@@ -54,17 +54,23 @@ interface WatchOptions extends CompileToFileOptions {
|
|
|
54
54
|
/** Called on build errors. */
|
|
55
55
|
onError?: (error: Error) => void;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
level: 'warning'
|
|
57
|
+
type Diagnostic = {
|
|
58
|
+
level: 'warning';
|
|
59
59
|
message: string;
|
|
60
60
|
file?: string;
|
|
61
61
|
line?: number;
|
|
62
|
-
}
|
|
62
|
+
} | {
|
|
63
|
+
level: 'error';
|
|
64
|
+
message: string;
|
|
65
|
+
file?: string;
|
|
66
|
+
line?: number;
|
|
67
|
+
fatal?: boolean;
|
|
68
|
+
};
|
|
63
69
|
interface CompileResult {
|
|
64
70
|
/** The compiled output string (HTML, Twee, JSON, etc.). */
|
|
65
71
|
output: string;
|
|
66
|
-
/** The parsed story model. */
|
|
67
|
-
story:
|
|
72
|
+
/** The parsed story model (read-only after compilation). */
|
|
73
|
+
story: ReadonlyStory;
|
|
68
74
|
/** The format used for compilation (undefined for non-HTML modes). */
|
|
69
75
|
format?: StoryFormatInfo;
|
|
70
76
|
/** Collected diagnostics. */
|
|
@@ -94,6 +100,9 @@ interface Passage {
|
|
|
94
100
|
metadata?: PassageMetadata;
|
|
95
101
|
source?: SourceLocation;
|
|
96
102
|
}
|
|
103
|
+
type IFID = string & {
|
|
104
|
+
readonly __brand: 'IFID';
|
|
105
|
+
};
|
|
97
106
|
interface Twine1Metadata {
|
|
98
107
|
settings: Map<string, string>;
|
|
99
108
|
}
|
|
@@ -108,12 +117,25 @@ interface Twine2Metadata {
|
|
|
108
117
|
}
|
|
109
118
|
interface Story {
|
|
110
119
|
name: string;
|
|
111
|
-
ifid:
|
|
120
|
+
ifid: IFID;
|
|
112
121
|
passages: Passage[];
|
|
113
|
-
legacyIFID:
|
|
122
|
+
legacyIFID: IFID;
|
|
114
123
|
twine1: Twine1Metadata;
|
|
115
124
|
twine2: Twine2Metadata;
|
|
116
125
|
}
|
|
126
|
+
type ReadonlyPassage = Readonly<Passage> & {
|
|
127
|
+
readonly tags: readonly string[];
|
|
128
|
+
};
|
|
129
|
+
type ReadonlyStory = Readonly<Omit<Story, 'passages' | 'twine1' | 'twine2'>> & {
|
|
130
|
+
readonly passages: readonly ReadonlyPassage[];
|
|
131
|
+
readonly twine1: Readonly<Omit<Twine1Metadata, 'settings'>> & {
|
|
132
|
+
readonly settings: ReadonlyMap<string, string>;
|
|
133
|
+
};
|
|
134
|
+
readonly twine2: Readonly<Omit<Twine2Metadata, 'options' | 'tagColors'>> & {
|
|
135
|
+
readonly options: ReadonlyMap<string, boolean>;
|
|
136
|
+
readonly tagColors: ReadonlyMap<string, string>;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
117
139
|
interface StoryFormatInfo {
|
|
118
140
|
id: string;
|
|
119
141
|
filename: string;
|
|
@@ -138,15 +160,16 @@ interface Twine2FormatJSON {
|
|
|
138
160
|
url?: string;
|
|
139
161
|
license?: string;
|
|
140
162
|
}
|
|
141
|
-
declare const
|
|
142
|
-
Error
|
|
143
|
-
EOF
|
|
144
|
-
Header
|
|
145
|
-
Name
|
|
146
|
-
Tags
|
|
147
|
-
Metadata
|
|
148
|
-
Content
|
|
149
|
-
}
|
|
163
|
+
declare const ItemType: {
|
|
164
|
+
readonly Error: 0;
|
|
165
|
+
readonly EOF: 1;
|
|
166
|
+
readonly Header: 2;
|
|
167
|
+
readonly Name: 3;
|
|
168
|
+
readonly Tags: 4;
|
|
169
|
+
readonly Metadata: 5;
|
|
170
|
+
readonly Content: 6;
|
|
171
|
+
};
|
|
172
|
+
type ItemType = (typeof ItemType)[keyof typeof ItemType];
|
|
150
173
|
interface LexerItem {
|
|
151
174
|
type: ItemType;
|
|
152
175
|
line: number;
|
|
@@ -273,7 +296,29 @@ interface BrokenLink {
|
|
|
273
296
|
* expect(map.deadEnds).not.toContain('Start');
|
|
274
297
|
* ```
|
|
275
298
|
*/
|
|
276
|
-
declare function storyInspect(story:
|
|
299
|
+
declare function storyInspect(story: ReadonlyStory): StoryMap;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Story model + StoryData JSON marshal/unmarshal.
|
|
303
|
+
* Ported from story.go + storydata.go.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Builder that separates the mutable construction phase from the immutable consumption phase.
|
|
308
|
+
* During construction, the internal `Story` is mutable via `add()` and direct access.
|
|
309
|
+
* After `build()`, the story is returned as `ReadonlyStory`.
|
|
310
|
+
*/
|
|
311
|
+
declare class StoryBuilder {
|
|
312
|
+
/** The mutable story, accessible during construction for loader functions. */
|
|
313
|
+
readonly story: Story;
|
|
314
|
+
constructor();
|
|
315
|
+
/** Add a passage, handling special passages (StoryData, StoryTitle, etc.). */
|
|
316
|
+
add(passage: Passage, diagnostics: Diagnostic[]): void;
|
|
317
|
+
/** Check if a passage name exists. */
|
|
318
|
+
has(name: string): boolean;
|
|
319
|
+
/** Finalize and return the story as read-only. */
|
|
320
|
+
build(): ReadonlyStory;
|
|
321
|
+
}
|
|
277
322
|
|
|
278
323
|
interface DecompileResult {
|
|
279
324
|
story: Story;
|
|
@@ -401,7 +446,9 @@ declare function parseSemver(v: string): [number, number, number] | null;
|
|
|
401
446
|
declare function semverCompare(a: [number, number, number], b: [number, number, number]): number;
|
|
402
447
|
|
|
403
448
|
/** Generate a new IFID (UUID v4, uppercase). */
|
|
404
|
-
declare function generateIFID():
|
|
449
|
+
declare function generateIFID(): IFID;
|
|
450
|
+
/** Validate and brand an IFID string. Throws on invalid input. */
|
|
451
|
+
declare function createIFID(value: string): IFID;
|
|
405
452
|
/**
|
|
406
453
|
* Validate an IFID string.
|
|
407
454
|
* Accepts both bare UUIDs and UUID://...// wrapped format.
|
|
@@ -451,4 +498,4 @@ declare function getCacheSize(): {
|
|
|
451
498
|
count: number;
|
|
452
499
|
};
|
|
453
500
|
|
|
454
|
-
export { type BrokenLink, CONFIG_FILENAME, type CachedFormatEntry, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type DecompileResult, type Diagnostic, type FileCacheEntry, type InlineSource, ItemType, type LexerItem, type LintResult, type OutputMode, type Passage, type PassageMetadata, type SFAIndex, type SFAIndexEntry, type SourceInput, type SourceLocation, type Story, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, type WordCountMethod, applyTagAliases, clearCachedFormats, compile, compileIncremental, compileToFile, decompileHTML, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, formatLintReport, generateIFID, getCacheDir, getCacheSize, getFormatSearchDirs, lint, listCachedFormats, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
|
501
|
+
export { type BrokenLink, CONFIG_FILENAME, type CachedFormatEntry, type CompileOptions, type CompileResult, type CompileStats, type CompileToFileOptions, type DecompileResult, type Diagnostic, type FileCacheEntry, type IFID, type InlineSource, ItemType, type LexerItem, type LintResult, type OutputMode, type Passage, type PassageMetadata, type ReadonlyPassage, type ReadonlyStory, type SFAIndex, type SFAIndexEntry, type SourceInput, type SourceLocation, type Story, StoryBuilder, type StoryFormatInfo, type StoryMap, TweeLexer, type TweeTsConfig, TweeTsError, type WatchOptions, type WordCountMethod, applyTagAliases, clearCachedFormats, compile, compileIncremental, compileToFile, createIFID, decompileHTML, discoverCachedFormats, discoverFormats, fetchAndCacheFormat, fetchDirectFormat, formatLintReport, generateIFID, getCacheDir, getCacheSize, getFormatSearchDirs, lint, listCachedFormats, loadConfig, loadConfigFile, parseFormatJSON, parseSemver, parseTwee, resolveRemoteFormat, scaffoldConfig, semverCompare, storyInspect, tweeLexer, validateConfig, validateIFID, watch };
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,11 @@ import { randomUUID } from "crypto";
|
|
|
6
6
|
function generateIFID() {
|
|
7
7
|
return randomUUID().toUpperCase();
|
|
8
8
|
}
|
|
9
|
+
function createIFID(value) {
|
|
10
|
+
const err = validateIFID(value);
|
|
11
|
+
if (err) throw new Error(`Invalid IFID: ${err}`);
|
|
12
|
+
return value.toUpperCase();
|
|
13
|
+
}
|
|
9
14
|
function validateIFID(ifid) {
|
|
10
15
|
let uuid = ifid;
|
|
11
16
|
switch (ifid.length) {
|
|
@@ -487,6 +492,25 @@ function getStoryStats(story, wordCountMethod = "tweego") {
|
|
|
487
492
|
}
|
|
488
493
|
return { passages: story.passages.length, storyPassages, words };
|
|
489
494
|
}
|
|
495
|
+
var StoryBuilder = class {
|
|
496
|
+
/** The mutable story, accessible during construction for loader functions. */
|
|
497
|
+
story;
|
|
498
|
+
constructor() {
|
|
499
|
+
this.story = createStory();
|
|
500
|
+
}
|
|
501
|
+
/** Add a passage, handling special passages (StoryData, StoryTitle, etc.). */
|
|
502
|
+
add(passage, diagnostics) {
|
|
503
|
+
storyAdd(this.story, passage, diagnostics);
|
|
504
|
+
}
|
|
505
|
+
/** Check if a passage name exists. */
|
|
506
|
+
has(name) {
|
|
507
|
+
return storyHas(this.story, name);
|
|
508
|
+
}
|
|
509
|
+
/** Finalize and return the story as read-only. */
|
|
510
|
+
build() {
|
|
511
|
+
return this.story;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
490
514
|
|
|
491
515
|
// src/filesystem.ts
|
|
492
516
|
import { readdirSync, statSync, watch as fsWatch } from "fs";
|
|
@@ -910,6 +934,17 @@ function readFormatSource(format) {
|
|
|
910
934
|
import { basename } from "path";
|
|
911
935
|
import { statSync as statSync3 } from "fs";
|
|
912
936
|
|
|
937
|
+
// src/types.ts
|
|
938
|
+
var ItemType = {
|
|
939
|
+
Error: 0,
|
|
940
|
+
EOF: 1,
|
|
941
|
+
Header: 2,
|
|
942
|
+
Name: 3,
|
|
943
|
+
Tags: 4,
|
|
944
|
+
Metadata: 5,
|
|
945
|
+
Content: 6
|
|
946
|
+
};
|
|
947
|
+
|
|
913
948
|
// src/lexer.ts
|
|
914
949
|
var EOF = -1;
|
|
915
950
|
var HEADER_DELIM = "::";
|
|
@@ -942,10 +977,16 @@ var LexerContext = class {
|
|
|
942
977
|
throw new Error("backup would leave pos < start");
|
|
943
978
|
}
|
|
944
979
|
}
|
|
980
|
+
// Line-counting invariant: next() increments this.line for each newline during
|
|
981
|
+
// character-by-character scanning. However, Content items are produced by jumping
|
|
982
|
+
// this.pos directly (e.g. via indexOf) without calling next(), so those newlines
|
|
983
|
+
// haven't been counted yet. emit() re-counts newlines in [start, pos) for Content
|
|
984
|
+
// items to compensate. This is safe because this.start is reset to this.pos after
|
|
985
|
+
// every emit(), so no range is ever counted twice.
|
|
945
986
|
emit(type) {
|
|
946
987
|
const val = this.input.slice(this.start, this.pos);
|
|
947
988
|
this.items.push({ type, line: this.line, pos: this.start, val });
|
|
948
|
-
if (type ===
|
|
989
|
+
if (type === ItemType.Content) {
|
|
949
990
|
for (let i = this.start; i < this.pos; i++) {
|
|
950
991
|
if (this.input.charCodeAt(i) === 10) this.line++;
|
|
951
992
|
}
|
|
@@ -966,7 +1007,7 @@ var LexerContext = class {
|
|
|
966
1007
|
}
|
|
967
1008
|
errorf(message) {
|
|
968
1009
|
this.items.push({
|
|
969
|
-
type:
|
|
1010
|
+
type: ItemType.Error,
|
|
970
1011
|
line: this.line,
|
|
971
1012
|
pos: this.start,
|
|
972
1013
|
val: message
|
|
@@ -1008,7 +1049,7 @@ function lexProlog(ctx) {
|
|
|
1008
1049
|
ctx.ignore();
|
|
1009
1050
|
return lexHeaderDelim;
|
|
1010
1051
|
}
|
|
1011
|
-
ctx.emit(
|
|
1052
|
+
ctx.emit(ItemType.EOF);
|
|
1012
1053
|
return null;
|
|
1013
1054
|
}
|
|
1014
1055
|
function lexContent(ctx) {
|
|
@@ -1018,19 +1059,19 @@ function lexContent(ctx) {
|
|
|
1018
1059
|
const i = ctx.input.indexOf(NEWLINE_HEADER_DELIM, ctx.pos);
|
|
1019
1060
|
if (i > -1) {
|
|
1020
1061
|
ctx.pos = i + 1;
|
|
1021
|
-
ctx.emit(
|
|
1062
|
+
ctx.emit(ItemType.Content);
|
|
1022
1063
|
return lexHeaderDelim;
|
|
1023
1064
|
}
|
|
1024
1065
|
ctx.pos = ctx.input.length;
|
|
1025
1066
|
if (ctx.pos > ctx.start) {
|
|
1026
|
-
ctx.emit(
|
|
1067
|
+
ctx.emit(ItemType.Content);
|
|
1027
1068
|
}
|
|
1028
|
-
ctx.emit(
|
|
1069
|
+
ctx.emit(ItemType.EOF);
|
|
1029
1070
|
return null;
|
|
1030
1071
|
}
|
|
1031
1072
|
function lexHeaderDelim(ctx) {
|
|
1032
1073
|
ctx.pos += HEADER_DELIM.length;
|
|
1033
|
-
ctx.emit(
|
|
1074
|
+
ctx.emit(ItemType.Header);
|
|
1034
1075
|
return lexName;
|
|
1035
1076
|
}
|
|
1036
1077
|
function lexName(ctx) {
|
|
@@ -1059,7 +1100,7 @@ function lexName(ctx) {
|
|
|
1059
1100
|
break outer;
|
|
1060
1101
|
}
|
|
1061
1102
|
}
|
|
1062
|
-
ctx.emit(
|
|
1103
|
+
ctx.emit(ItemType.Name);
|
|
1063
1104
|
switch (r) {
|
|
1064
1105
|
case 91:
|
|
1065
1106
|
return lexTags;
|
|
@@ -1076,7 +1117,7 @@ function lexName(ctx) {
|
|
|
1076
1117
|
ctx.ignore();
|
|
1077
1118
|
return lexContent;
|
|
1078
1119
|
}
|
|
1079
|
-
ctx.emit(
|
|
1120
|
+
ctx.emit(ItemType.EOF);
|
|
1080
1121
|
return null;
|
|
1081
1122
|
}
|
|
1082
1123
|
function lexNextOptionalBlock(ctx) {
|
|
@@ -1099,7 +1140,7 @@ function lexNextOptionalBlock(ctx) {
|
|
|
1099
1140
|
ctx.ignore();
|
|
1100
1141
|
return lexContent;
|
|
1101
1142
|
case EOF:
|
|
1102
|
-
ctx.emit(
|
|
1143
|
+
ctx.emit(ItemType.EOF);
|
|
1103
1144
|
return null;
|
|
1104
1145
|
}
|
|
1105
1146
|
return ctx.errorf(`illegal character '${String.fromCharCode(r)}' amid the optional blocks`);
|
|
@@ -1119,7 +1160,7 @@ function lexTags(ctx) {
|
|
|
1119
1160
|
if (r === 10) ctx.backup();
|
|
1120
1161
|
return ctx.errorf("unterminated tag block");
|
|
1121
1162
|
case 93:
|
|
1122
|
-
if (ctx.pos > ctx.start) ctx.emit(
|
|
1163
|
+
if (ctx.pos > ctx.start) ctx.emit(ItemType.Tags);
|
|
1123
1164
|
return lexNextOptionalBlock;
|
|
1124
1165
|
case 91:
|
|
1125
1166
|
return ctx.errorf(`unexpected left square bracket '['`);
|
|
@@ -1152,7 +1193,7 @@ function lexMetadata(ctx) {
|
|
|
1152
1193
|
case 125:
|
|
1153
1194
|
depth--;
|
|
1154
1195
|
if (depth === 0) {
|
|
1155
|
-
if (ctx.pos > ctx.start) ctx.emit(
|
|
1196
|
+
if (ctx.pos > ctx.start) ctx.emit(ItemType.Metadata);
|
|
1156
1197
|
return lexNextOptionalBlock;
|
|
1157
1198
|
}
|
|
1158
1199
|
break;
|
|
@@ -1182,7 +1223,7 @@ var TweeLexer = class {
|
|
|
1182
1223
|
nextItem() {
|
|
1183
1224
|
const result = this.gen.next();
|
|
1184
1225
|
if (result.done) {
|
|
1185
|
-
return { item: { type:
|
|
1226
|
+
return { item: { type: ItemType.EOF, line: 0, pos: 0, val: "" }, done: true };
|
|
1186
1227
|
}
|
|
1187
1228
|
return { item: result.value, done: false };
|
|
1188
1229
|
}
|
|
@@ -1219,10 +1260,10 @@ function parseTwee(source, options = {}) {
|
|
|
1219
1260
|
const passages = [];
|
|
1220
1261
|
let current = null;
|
|
1221
1262
|
let pCount = 0;
|
|
1222
|
-
let lastType =
|
|
1263
|
+
let lastType = ItemType.EOF;
|
|
1223
1264
|
for (const item of tweeLexer(source)) {
|
|
1224
1265
|
switch (item.type) {
|
|
1225
|
-
case
|
|
1266
|
+
case ItemType.Error:
|
|
1226
1267
|
diagnostics.push({
|
|
1227
1268
|
level: "error",
|
|
1228
1269
|
message: `line ${item.line}: Malformed twee source; ${item.val}.`,
|
|
@@ -1230,19 +1271,19 @@ function parseTwee(source, options = {}) {
|
|
|
1230
1271
|
line: item.line
|
|
1231
1272
|
});
|
|
1232
1273
|
return { passages, diagnostics };
|
|
1233
|
-
case
|
|
1274
|
+
case ItemType.EOF:
|
|
1234
1275
|
if (pCount > 0 && current) {
|
|
1235
1276
|
passages.push(current);
|
|
1236
1277
|
}
|
|
1237
1278
|
return { passages, diagnostics };
|
|
1238
|
-
case
|
|
1279
|
+
case ItemType.Header:
|
|
1239
1280
|
pCount++;
|
|
1240
1281
|
if (pCount > 1 && current) {
|
|
1241
1282
|
passages.push(current);
|
|
1242
1283
|
}
|
|
1243
1284
|
current = { name: "", tags: [], text: "", source: { file: filename, line: item.line } };
|
|
1244
1285
|
break;
|
|
1245
|
-
case
|
|
1286
|
+
case ItemType.Name: {
|
|
1246
1287
|
if (!current) break;
|
|
1247
1288
|
const name = tweeUnescape(item.val).trim();
|
|
1248
1289
|
if (name.length === 0) {
|
|
@@ -1257,9 +1298,9 @@ function parseTwee(source, options = {}) {
|
|
|
1257
1298
|
current.name = name;
|
|
1258
1299
|
break;
|
|
1259
1300
|
}
|
|
1260
|
-
case
|
|
1301
|
+
case ItemType.Tags: {
|
|
1261
1302
|
if (!current) break;
|
|
1262
|
-
if (lastType !==
|
|
1303
|
+
if (lastType !== ItemType.Name) {
|
|
1263
1304
|
diagnostics.push({
|
|
1264
1305
|
level: "error",
|
|
1265
1306
|
message: `line ${item.line}: Malformed twee source; optional tags block must immediately follow the passage name.`,
|
|
@@ -1272,9 +1313,9 @@ function parseTwee(source, options = {}) {
|
|
|
1272
1313
|
current.tags = tweeUnescape(inner).split(/\s+/).filter(Boolean);
|
|
1273
1314
|
break;
|
|
1274
1315
|
}
|
|
1275
|
-
case
|
|
1316
|
+
case ItemType.Metadata: {
|
|
1276
1317
|
if (!current) break;
|
|
1277
|
-
if (lastType !==
|
|
1318
|
+
if (lastType !== ItemType.Name && lastType !== ItemType.Tags) {
|
|
1278
1319
|
diagnostics.push({
|
|
1279
1320
|
level: "error",
|
|
1280
1321
|
message: `line ${item.line}: Malformed twee source; optional metadata block must immediately follow the passage name or tags block.`,
|
|
@@ -1306,7 +1347,7 @@ function parseTwee(source, options = {}) {
|
|
|
1306
1347
|
}
|
|
1307
1348
|
break;
|
|
1308
1349
|
}
|
|
1309
|
-
case
|
|
1350
|
+
case ItemType.Content: {
|
|
1310
1351
|
if (!current) break;
|
|
1311
1352
|
if (trim) {
|
|
1312
1353
|
current.text = item.val.trim();
|
|
@@ -4436,21 +4477,20 @@ var VERSION = "1.2.0";
|
|
|
4436
4477
|
|
|
4437
4478
|
// src/output-twine2.ts
|
|
4438
4479
|
var CREATOR_NAME = "Twee-ts";
|
|
4439
|
-
function toTwine2Archive(story, startName,
|
|
4440
|
-
return getTwine2DataChunk(story, startName,
|
|
4480
|
+
function toTwine2Archive(story, startName, options) {
|
|
4481
|
+
return getTwine2DataChunk(story, startName, options) + "\n";
|
|
4441
4482
|
}
|
|
4442
|
-
function toTwine2HTML(story, format, startName,
|
|
4483
|
+
function toTwine2HTML(story, format, startName, options) {
|
|
4443
4484
|
let template = readFormatSource(format);
|
|
4444
4485
|
if (template.includes("{{STORY_NAME}}")) {
|
|
4445
4486
|
template = template.replaceAll("{{STORY_NAME}}", htmlEscape(story.name));
|
|
4446
4487
|
}
|
|
4447
4488
|
if (template.includes("{{STORY_DATA}}")) {
|
|
4448
|
-
template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName,
|
|
4489
|
+
template = template.replace("{{STORY_DATA}}", getTwine2DataChunk(story, startName, options));
|
|
4449
4490
|
}
|
|
4450
4491
|
return template;
|
|
4451
4492
|
}
|
|
4452
|
-
function getTwine2DataChunk(story, startName,
|
|
4453
|
-
ensureIFID(story, diagnostics);
|
|
4493
|
+
function getTwine2DataChunk(story, startName, options) {
|
|
4454
4494
|
const parts = [];
|
|
4455
4495
|
let startID = "";
|
|
4456
4496
|
let pid = 0;
|
|
@@ -4519,23 +4559,6 @@ function getTwine2DataChunk(story, startName, diagnostics, options) {
|
|
|
4519
4559
|
const wrapper = `<!-- UUID://${htmlCommentSanitize(story.ifid)}// --><tw-storydata name="${attrEscape(story.name)}" startnode="${attrEscape(startID)}" creator="${attrEscape(CREATOR_NAME)}" creator-version="${attrEscape(VERSION)}" ifid="${attrEscape(story.ifid)}" zoom="${attrEscape(zoom)}" format="${attrEscape(story.twine2.format)}" format-version="${attrEscape(story.twine2.formatVersion)}" options="${attrEscape(optionsStr)}" tags="${attrEscape(story.twine2.tags)}" hidden>`;
|
|
4520
4560
|
return wrapper + parts.join("") + "</tw-storydata>";
|
|
4521
4561
|
}
|
|
4522
|
-
function ensureIFID(story, diagnostics) {
|
|
4523
|
-
if (story.ifid !== "") return;
|
|
4524
|
-
if (story.legacyIFID !== "") {
|
|
4525
|
-
story.ifid = story.legacyIFID;
|
|
4526
|
-
diagnostics.push({
|
|
4527
|
-
level: "warning",
|
|
4528
|
-
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
4529
|
-
});
|
|
4530
|
-
} else {
|
|
4531
|
-
const ifid = generateIFID();
|
|
4532
|
-
story.ifid = ifid;
|
|
4533
|
-
diagnostics.push({
|
|
4534
|
-
level: "error",
|
|
4535
|
-
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
4536
|
-
});
|
|
4537
|
-
}
|
|
4538
|
-
}
|
|
4539
4562
|
|
|
4540
4563
|
// src/output-twine1.ts
|
|
4541
4564
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -4546,7 +4569,7 @@ function toTwine1Archive(story, _startName) {
|
|
|
4546
4569
|
return `<div id="storeArea" data-size="${count}">${data}</div>
|
|
4547
4570
|
`;
|
|
4548
4571
|
}
|
|
4549
|
-
function toTwine1HTML(story, format, startName
|
|
4572
|
+
function toTwine1HTML(story, format, startName) {
|
|
4550
4573
|
const formatDir = dirname(format.filename);
|
|
4551
4574
|
const parentDir = dirname(formatDir);
|
|
4552
4575
|
let template = readFormatSource(format);
|
|
@@ -5103,6 +5126,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5103
5126
|
if (testMode) {
|
|
5104
5127
|
story.twine2.options.set("debug", true);
|
|
5105
5128
|
}
|
|
5129
|
+
ensureIFID(story, diagnostics);
|
|
5106
5130
|
let output;
|
|
5107
5131
|
switch (outputMode) {
|
|
5108
5132
|
case "twee3":
|
|
@@ -5110,7 +5134,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5110
5134
|
output = toTwee(story, outputMode);
|
|
5111
5135
|
break;
|
|
5112
5136
|
case "twine2-archive":
|
|
5113
|
-
output = toTwine2Archive(story, startName,
|
|
5137
|
+
output = toTwine2Archive(story, startName, { sourceInfo });
|
|
5114
5138
|
break;
|
|
5115
5139
|
case "twine1-archive":
|
|
5116
5140
|
output = toTwine1Archive(story, startName);
|
|
@@ -5129,7 +5153,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5129
5153
|
throw new TweeTsError("No story format available for HTML output.", diagnostics);
|
|
5130
5154
|
}
|
|
5131
5155
|
if (format.isTwine2) {
|
|
5132
|
-
output = toTwine2HTML(story, format, startName,
|
|
5156
|
+
output = toTwine2HTML(story, format, startName, { sourceInfo });
|
|
5133
5157
|
} else {
|
|
5134
5158
|
if (story.name === "" && !storyHas(story, "StoryTitle")) {
|
|
5135
5159
|
diagnostics.push({
|
|
@@ -5137,7 +5161,7 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5137
5161
|
message: 'Special passage "StoryTitle" not found.'
|
|
5138
5162
|
});
|
|
5139
5163
|
}
|
|
5140
|
-
output = toTwine1HTML(story, format, startName
|
|
5164
|
+
output = toTwine1HTML(story, format, startName);
|
|
5141
5165
|
}
|
|
5142
5166
|
const modulePaths = options.modules ? getFilenames(options.modules) : [];
|
|
5143
5167
|
output = modifyHead(output, modulePaths, options.headFile, diagnostics);
|
|
@@ -5154,6 +5178,23 @@ async function buildOutput(options, cache, changedFiles) {
|
|
|
5154
5178
|
};
|
|
5155
5179
|
return { output, story, format, diagnostics, stats };
|
|
5156
5180
|
}
|
|
5181
|
+
function ensureIFID(story, diagnostics) {
|
|
5182
|
+
if (story.ifid !== "") return;
|
|
5183
|
+
if (story.legacyIFID !== "") {
|
|
5184
|
+
story.ifid = story.legacyIFID;
|
|
5185
|
+
diagnostics.push({
|
|
5186
|
+
level: "warning",
|
|
5187
|
+
message: 'Story IFID not found; reusing "ifid" entry from the "StorySettings" special passage.'
|
|
5188
|
+
});
|
|
5189
|
+
} else {
|
|
5190
|
+
const ifid = generateIFID();
|
|
5191
|
+
story.ifid = ifid;
|
|
5192
|
+
diagnostics.push({
|
|
5193
|
+
level: "error",
|
|
5194
|
+
message: `Story IFID not found. Add an IFID to your story: {"ifid":"${ifid}"}`
|
|
5195
|
+
});
|
|
5196
|
+
}
|
|
5197
|
+
}
|
|
5157
5198
|
function storyToJSON(story) {
|
|
5158
5199
|
const scripts = [];
|
|
5159
5200
|
const stylesheets = [];
|
|
@@ -5474,6 +5515,8 @@ function scaffoldConfig() {
|
|
|
5474
5515
|
}
|
|
5475
5516
|
export {
|
|
5476
5517
|
CONFIG_FILENAME,
|
|
5518
|
+
ItemType,
|
|
5519
|
+
StoryBuilder,
|
|
5477
5520
|
TweeLexer,
|
|
5478
5521
|
TweeTsError,
|
|
5479
5522
|
applyTagAliases,
|
|
@@ -5481,6 +5524,7 @@ export {
|
|
|
5481
5524
|
compile,
|
|
5482
5525
|
compileIncremental,
|
|
5483
5526
|
compileToFile,
|
|
5527
|
+
createIFID,
|
|
5484
5528
|
decompileHTML,
|
|
5485
5529
|
discoverCachedFormats,
|
|
5486
5530
|
discoverFormats,
|