squiffy-compiler 6.0.0-alpha.1 → 6.0.0-alpha.17
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/LICENSE +22 -0
- package/dist/compiler.d.ts +4 -3
- package/dist/compiler.js +155 -113
- package/package.json +10 -11
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014-2024 Alex Warren, textadventures.co.uk and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/dist/compiler.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export declare const SQUIFFY_VERSION = "6.0.0-alpha.0";
|
|
2
1
|
export interface Output {
|
|
3
2
|
story: OutputStory;
|
|
4
3
|
js: string[][];
|
|
@@ -6,6 +5,7 @@ export interface Output {
|
|
|
6
5
|
interface OutputStory {
|
|
7
6
|
start: string;
|
|
8
7
|
id: string | null;
|
|
8
|
+
uiJsIndex?: number;
|
|
9
9
|
sections: Record<string, OutputSection>;
|
|
10
10
|
}
|
|
11
11
|
interface OutputSection {
|
|
@@ -23,10 +23,11 @@ interface OutputPassage {
|
|
|
23
23
|
jsIndex?: number;
|
|
24
24
|
}
|
|
25
25
|
interface CompilerSettings {
|
|
26
|
-
scriptBaseFilename
|
|
26
|
+
scriptBaseFilename?: string;
|
|
27
27
|
script: string;
|
|
28
28
|
onWarning?: (message: string) => void;
|
|
29
29
|
externalFiles?: ExternalFiles;
|
|
30
|
+
globalJs?: boolean;
|
|
30
31
|
}
|
|
31
32
|
interface ExternalFiles {
|
|
32
33
|
getMatchingFilenames(pattern: string): Promise<string[]>;
|
|
@@ -41,7 +42,7 @@ interface UiInfo {
|
|
|
41
42
|
export interface CompileSuccess {
|
|
42
43
|
success: true;
|
|
43
44
|
output: Output;
|
|
44
|
-
getJs: () => Promise<string>;
|
|
45
|
+
getJs: (excludeHeader?: boolean) => Promise<string>;
|
|
45
46
|
getUiInfo: () => UiInfo;
|
|
46
47
|
}
|
|
47
48
|
export interface CompileError {
|
package/dist/compiler.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import pkg from "../package.json" with { type: "json" };
|
|
2
|
+
const version = pkg.version;
|
|
3
3
|
export async function compile(settings) {
|
|
4
4
|
const story = new Story(settings.scriptBaseFilename);
|
|
5
5
|
const errors = [];
|
|
6
|
-
|
|
6
|
+
let autoSectionCount = 0;
|
|
7
|
+
async function getJs(storyData, excludeHeader) {
|
|
7
8
|
const outputJs = [];
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
if (!excludeHeader) {
|
|
10
|
+
outputJs.push(`// Created with Squiffy ${version}`);
|
|
11
|
+
outputJs.push("// https://github.com/textadventures/squiffy");
|
|
12
|
+
}
|
|
13
|
+
if (settings.globalJs) {
|
|
14
|
+
outputJs.push("var story = {};");
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
outputJs.push("export const story = {};");
|
|
18
|
+
}
|
|
11
19
|
outputJs.push(`story.id = ${JSON.stringify(storyData.story.id, null, 4)};`);
|
|
20
|
+
if (storyData.story.uiJsIndex !== undefined) {
|
|
21
|
+
outputJs.push(`story.uiJsIndex = ${storyData.story.uiJsIndex};`);
|
|
22
|
+
}
|
|
12
23
|
outputJs.push(`story.start = ${JSON.stringify(storyData.story.start, null, 4)};`);
|
|
13
24
|
outputJs.push(`story.sections = ${JSON.stringify(storyData.story.sections, null, 4)};`);
|
|
14
|
-
outputJs.push(
|
|
25
|
+
outputJs.push("story.js = [");
|
|
15
26
|
for (const js of storyData.js) {
|
|
16
27
|
writeJs(outputJs, 1, js);
|
|
17
28
|
}
|
|
18
|
-
outputJs.push(
|
|
19
|
-
return outputJs.join(
|
|
29
|
+
outputJs.push("];");
|
|
30
|
+
return outputJs.join("\n");
|
|
20
31
|
}
|
|
21
32
|
async function getStoryData() {
|
|
22
33
|
if (!story.start) {
|
|
@@ -30,6 +41,10 @@ export async function compile(settings) {
|
|
|
30
41
|
},
|
|
31
42
|
js: [],
|
|
32
43
|
};
|
|
44
|
+
if (story.uiJs.length > 0) {
|
|
45
|
+
output.js.push(story.uiJs);
|
|
46
|
+
output.story.uiJsIndex = output.js.length - 1;
|
|
47
|
+
}
|
|
33
48
|
for (const sectionName of Object.keys(story.sections)) {
|
|
34
49
|
const section = story.sections[sectionName];
|
|
35
50
|
const outputSection = {};
|
|
@@ -37,7 +52,7 @@ export async function compile(settings) {
|
|
|
37
52
|
if (section.clear) {
|
|
38
53
|
outputSection.clear = true;
|
|
39
54
|
}
|
|
40
|
-
outputSection.text = await processText(section.text.join(
|
|
55
|
+
outputSection.text = await processText(section.text.join("\n"), section, null);
|
|
41
56
|
if (section.attributes.length > 0) {
|
|
42
57
|
outputSection.attributes = section.attributes;
|
|
43
58
|
}
|
|
@@ -45,10 +60,10 @@ export async function compile(settings) {
|
|
|
45
60
|
output.js.push(section.js);
|
|
46
61
|
outputSection.jsIndex = output.js.length - 1;
|
|
47
62
|
}
|
|
48
|
-
if (
|
|
49
|
-
|
|
63
|
+
if ("@last" in section.passages) {
|
|
64
|
+
let passageCount = 0;
|
|
50
65
|
for (const passageName of Object.keys(section.passages)) {
|
|
51
|
-
if (passageName.length > 0 && passageName?.substring(0, 1) !==
|
|
66
|
+
if (passageName.length > 0 && passageName?.substring(0, 1) !== "@") {
|
|
52
67
|
passageCount++;
|
|
53
68
|
}
|
|
54
69
|
}
|
|
@@ -64,7 +79,7 @@ export async function compile(settings) {
|
|
|
64
79
|
if (passage.clear) {
|
|
65
80
|
outputPassage.clear = true;
|
|
66
81
|
}
|
|
67
|
-
outputPassage.text = await processText(passage.text.join(
|
|
82
|
+
outputPassage.text = await processText(passage.text.join("\n"), section, passage);
|
|
68
83
|
if (passage.attributes.length > 0) {
|
|
69
84
|
outputPassage.attributes = passage.attributes;
|
|
70
85
|
}
|
|
@@ -76,7 +91,6 @@ export async function compile(settings) {
|
|
|
76
91
|
}
|
|
77
92
|
return output;
|
|
78
93
|
}
|
|
79
|
-
;
|
|
80
94
|
const regexes = {
|
|
81
95
|
section: /^\[\[(.*)\]\]:$/,
|
|
82
96
|
passage: /^\[(.*)\]:$/,
|
|
@@ -87,28 +101,38 @@ export async function compile(settings) {
|
|
|
87
101
|
unset: /^@unset (.*)$/,
|
|
88
102
|
inc: /^@inc (\S+)(?: (\d+))?$/,
|
|
89
103
|
dec: /^@dec (\S+)(?: (\d+))?$/,
|
|
90
|
-
replace: /^@replace (.*$)/,
|
|
91
104
|
js: /^(\t| {4})(.*)$/,
|
|
92
105
|
continue: /^\+\+\+(.*)$/,
|
|
106
|
+
ui: /^@ui (.*)$/,
|
|
93
107
|
};
|
|
94
108
|
async function processFileText(inputText, inputFilename, isFirst) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
109
|
+
const inputLines = inputText.replace(/\r/g, "").split("\n");
|
|
110
|
+
let lineCount = 0;
|
|
111
|
+
let section = null;
|
|
112
|
+
let passage = null; // annotated differently to section, as a workaround for TypeScript "Property does not exist on type never"
|
|
113
|
+
let textStarted = false;
|
|
114
|
+
let inUiBlock = false;
|
|
115
|
+
const ensureThisSectionExists = () => {
|
|
102
116
|
return ensureSectionExists(section, isFirst, inputFilename, lineCount);
|
|
103
117
|
};
|
|
104
|
-
const
|
|
118
|
+
const addAutoSection = () => {
|
|
119
|
+
autoSectionCount++;
|
|
120
|
+
const autoSectionName = `_continue${autoSectionCount}`;
|
|
121
|
+
section = story.addSection(autoSectionName, inputFilename, lineCount);
|
|
122
|
+
passage = null;
|
|
123
|
+
textStarted = false;
|
|
124
|
+
return autoSectionName;
|
|
125
|
+
};
|
|
105
126
|
for (const line of inputLines) {
|
|
106
|
-
|
|
127
|
+
const stripLine = line.trim();
|
|
107
128
|
lineCount++;
|
|
108
|
-
|
|
129
|
+
const match = {};
|
|
109
130
|
for (const key of Object.keys(regexes)) {
|
|
110
131
|
const regex = regexes[key];
|
|
111
|
-
|
|
132
|
+
const result = key == "js" ? regex.exec(line) : regex.exec(stripLine);
|
|
133
|
+
if (result) {
|
|
134
|
+
match[key] = result;
|
|
135
|
+
}
|
|
112
136
|
}
|
|
113
137
|
if (match.section) {
|
|
114
138
|
section = story.addSection(match.section[1], inputFilename, lineCount);
|
|
@@ -117,7 +141,7 @@ export async function compile(settings) {
|
|
|
117
141
|
}
|
|
118
142
|
else if (match.passage) {
|
|
119
143
|
if (!section) {
|
|
120
|
-
errors.push(`ERROR: ${inputFilename} line ${lineCount}: Can
|
|
144
|
+
errors.push(`ERROR: ${inputFilename} line ${lineCount}: Can't add passage "${match.passage[1]}" as no section has been created.`);
|
|
121
145
|
return false;
|
|
122
146
|
}
|
|
123
147
|
section = ensureThisSectionExists();
|
|
@@ -126,14 +150,29 @@ export async function compile(settings) {
|
|
|
126
150
|
}
|
|
127
151
|
else if (match.continue) {
|
|
128
152
|
section = ensureThisSectionExists();
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
passage = null;
|
|
134
|
-
textStarted = false;
|
|
153
|
+
const previousSection = section;
|
|
154
|
+
const autoSectionName = addAutoSection();
|
|
155
|
+
const text = match.continue[1] || "Continue...";
|
|
156
|
+
previousSection.addText(`[[${text}]](${autoSectionName})`);
|
|
135
157
|
}
|
|
136
|
-
else if (stripLine ==
|
|
158
|
+
else if (stripLine == "---") {
|
|
159
|
+
inUiBlock = false;
|
|
160
|
+
if (!section) {
|
|
161
|
+
// Just add the _default section if we haven't started yet
|
|
162
|
+
ensureThisSectionExists();
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
addAutoSection();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (stripLine == "@ui") {
|
|
169
|
+
inUiBlock = true;
|
|
170
|
+
}
|
|
171
|
+
else if (match.ui && settings.externalFiles) {
|
|
172
|
+
const content = await settings.externalFiles.getContent(match.ui[1]);
|
|
173
|
+
story.addUiJs(content);
|
|
174
|
+
}
|
|
175
|
+
else if (stripLine == "@clear") {
|
|
137
176
|
if (!passage) {
|
|
138
177
|
section = ensureThisSectionExists();
|
|
139
178
|
section.clear = true;
|
|
@@ -149,18 +188,18 @@ export async function compile(settings) {
|
|
|
149
188
|
story.start = match.start[1];
|
|
150
189
|
}
|
|
151
190
|
else if (match.import && settings.externalFiles) {
|
|
152
|
-
|
|
191
|
+
const importFilenames = await settings.externalFiles.getMatchingFilenames(match.import[1]);
|
|
153
192
|
for (const importFilename of importFilenames) {
|
|
154
|
-
if (importFilename.endsWith(
|
|
193
|
+
if (importFilename.endsWith(".squiffy")) {
|
|
155
194
|
const content = await settings.externalFiles.getContent(importFilename);
|
|
156
|
-
|
|
195
|
+
const success = await processFileText(content, importFilename, false);
|
|
157
196
|
if (!success)
|
|
158
197
|
return false;
|
|
159
198
|
}
|
|
160
|
-
else if (importFilename.endsWith(
|
|
199
|
+
else if (importFilename.endsWith(".js")) {
|
|
161
200
|
story.scripts.push(settings.externalFiles.getLocalFilename(importFilename));
|
|
162
201
|
}
|
|
163
|
-
else if (importFilename.endsWith(
|
|
202
|
+
else if (importFilename.endsWith(".css")) {
|
|
164
203
|
story.stylesheets.push(settings.externalFiles.getLocalFilename(importFilename));
|
|
165
204
|
}
|
|
166
205
|
}
|
|
@@ -171,31 +210,21 @@ export async function compile(settings) {
|
|
|
171
210
|
}
|
|
172
211
|
else if (match.unset) {
|
|
173
212
|
section = ensureThisSectionExists();
|
|
174
|
-
section = addAttribute(
|
|
213
|
+
section = addAttribute("not " + match.unset[1], section, passage, isFirst, inputFilename, lineCount);
|
|
175
214
|
}
|
|
176
215
|
else if (match.inc) {
|
|
177
216
|
section = ensureThisSectionExists();
|
|
178
|
-
section = addAttribute(match.inc[1] +
|
|
217
|
+
section = addAttribute(match.inc[1] + "+=" + (match.inc[2] === undefined ? "1" : match.inc[2]), section, passage, isFirst, inputFilename, lineCount);
|
|
179
218
|
}
|
|
180
219
|
else if (match.dec) {
|
|
181
220
|
section = ensureThisSectionExists();
|
|
182
|
-
section = addAttribute(match.dec[1] +
|
|
183
|
-
}
|
|
184
|
-
else if (match.replace) {
|
|
185
|
-
const thisSection = ensureThisSectionExists();
|
|
186
|
-
const thisPassage = passage;
|
|
187
|
-
var replaceAttribute = match.replace[1];
|
|
188
|
-
var attributeMatch = /^(.*?)=(.*)$/.exec(replaceAttribute);
|
|
189
|
-
secondPass.push(async () => {
|
|
190
|
-
// add this to secondPass functions, because processText might result in links to passages which have not been created yet
|
|
191
|
-
if (attributeMatch) {
|
|
192
|
-
replaceAttribute = attributeMatch[1] + '=' + await processText(attributeMatch[2], thisSection, null);
|
|
193
|
-
}
|
|
194
|
-
addAttribute('@replace ' + replaceAttribute, section, thisPassage, isFirst, inputFilename, lineCount);
|
|
195
|
-
});
|
|
221
|
+
section = addAttribute(match.dec[1] + "-=" + (match.dec[2] === undefined ? "1" : match.dec[2]), section, passage, isFirst, inputFilename, lineCount);
|
|
196
222
|
}
|
|
197
223
|
else if (!textStarted && match.js) {
|
|
198
|
-
if (
|
|
224
|
+
if (inUiBlock) {
|
|
225
|
+
story.addUiJs(match.js[2]);
|
|
226
|
+
}
|
|
227
|
+
else if (!passage) {
|
|
199
228
|
section = ensureThisSectionExists();
|
|
200
229
|
section.addJS(match.js[2]);
|
|
201
230
|
}
|
|
@@ -204,7 +233,10 @@ export async function compile(settings) {
|
|
|
204
233
|
}
|
|
205
234
|
}
|
|
206
235
|
else if (textStarted || stripLine.length > 0) {
|
|
207
|
-
if (
|
|
236
|
+
if (inUiBlock) {
|
|
237
|
+
errors.push(`ERROR: ${inputFilename} line ${lineCount}: Unexpected text in @ui block.`);
|
|
238
|
+
}
|
|
239
|
+
else if (!passage) {
|
|
208
240
|
section = ensureThisSectionExists();
|
|
209
241
|
if (section) {
|
|
210
242
|
section.addText(line);
|
|
@@ -217,19 +249,14 @@ export async function compile(settings) {
|
|
|
217
249
|
}
|
|
218
250
|
}
|
|
219
251
|
}
|
|
220
|
-
for (const fn of secondPass) {
|
|
221
|
-
await fn();
|
|
222
|
-
}
|
|
223
252
|
return true;
|
|
224
253
|
}
|
|
225
|
-
;
|
|
226
254
|
function ensureSectionExists(section, isFirst, inputFilename, lineCount) {
|
|
227
255
|
if (!section && isFirst) {
|
|
228
|
-
section = story.addSection(
|
|
256
|
+
section = story.addSection("_default", inputFilename, lineCount);
|
|
229
257
|
}
|
|
230
258
|
return section;
|
|
231
259
|
}
|
|
232
|
-
;
|
|
233
260
|
function addAttribute(attribute, section, passage, isFirst, inputFilename, lineCount) {
|
|
234
261
|
if (!passage) {
|
|
235
262
|
section = ensureSectionExists(section, isFirst, inputFilename, lineCount);
|
|
@@ -240,7 +267,24 @@ export async function compile(settings) {
|
|
|
240
267
|
}
|
|
241
268
|
return section;
|
|
242
269
|
}
|
|
243
|
-
|
|
270
|
+
function extractLinkFunctions(link) {
|
|
271
|
+
const fragments = link.split(",");
|
|
272
|
+
return {
|
|
273
|
+
target: fragments[0].trim(),
|
|
274
|
+
setters: fragments.slice(1).join(", ")
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function getAdditionalLinkParameters(link) {
|
|
278
|
+
const functions = extractLinkFunctions(link);
|
|
279
|
+
let additionalParameters = "";
|
|
280
|
+
if (functions.setters.length > 0) {
|
|
281
|
+
additionalParameters += ` set="${functions.setters}"`;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
target: functions.target,
|
|
285
|
+
additionalParameters
|
|
286
|
+
};
|
|
287
|
+
}
|
|
244
288
|
async function processText(input, section, passage) {
|
|
245
289
|
// namedSectionLinkRegex matches:
|
|
246
290
|
// open [[
|
|
@@ -249,10 +293,13 @@ export async function compile(settings) {
|
|
|
249
293
|
// open bracket
|
|
250
294
|
// any text - the name of the section
|
|
251
295
|
// closing bracket
|
|
252
|
-
|
|
253
|
-
|
|
296
|
+
const namedSectionLinkRegex = /\[\[([^\]]*?)\]\]\((.*?)\)/g;
|
|
297
|
+
let links = allMatchesForGroup(input, namedSectionLinkRegex, 2);
|
|
254
298
|
checkSectionLinks(links, section, passage);
|
|
255
|
-
input = input.replace(namedSectionLinkRegex,
|
|
299
|
+
input = input.replace(namedSectionLinkRegex, (_match, text /* $1 */, name /* $2 */) => {
|
|
300
|
+
const parsedName = getAdditionalLinkParameters(name);
|
|
301
|
+
return `{{section "${parsedName.target}" text="${text}"${parsedName.additionalParameters}}}`;
|
|
302
|
+
});
|
|
256
303
|
// namedPassageLinkRegex matches:
|
|
257
304
|
// open [
|
|
258
305
|
// any text - the link text
|
|
@@ -260,68 +307,61 @@ export async function compile(settings) {
|
|
|
260
307
|
// open bracket, but not http(s):// after it
|
|
261
308
|
// any text - the name of the passage
|
|
262
309
|
// closing bracket
|
|
263
|
-
|
|
310
|
+
const namedPassageLinkRegex = /\[([^\]]*?)\]\(((?!https?:\/\/).*?)\)/g;
|
|
264
311
|
links = allMatchesForGroup(input, namedPassageLinkRegex, 2);
|
|
265
312
|
checkPassageLinks(links, section, passage);
|
|
266
|
-
input = input.replace(namedPassageLinkRegex,
|
|
313
|
+
input = input.replace(namedPassageLinkRegex, (_match, text /* $1 */, name /* $2 */) => {
|
|
314
|
+
const parsedName = getAdditionalLinkParameters(name);
|
|
315
|
+
return `{{passage "${parsedName.target}" text="${text}"${parsedName.additionalParameters}}}`;
|
|
316
|
+
});
|
|
267
317
|
// unnamedSectionLinkRegex matches:
|
|
268
318
|
// open [[
|
|
269
319
|
// any text - the link text
|
|
270
320
|
// closing ]]
|
|
271
|
-
|
|
321
|
+
const unnamedSectionLinkRegex = /\[\[(.*?)\]\]/g;
|
|
272
322
|
links = allMatchesForGroup(input, unnamedSectionLinkRegex, 1);
|
|
273
323
|
checkSectionLinks(links, section, passage);
|
|
274
|
-
input = input.replace(unnamedSectionLinkRegex, '
|
|
324
|
+
input = input.replace(unnamedSectionLinkRegex, '{{section "$1"}}');
|
|
275
325
|
// unnamedPassageLinkRegex matches:
|
|
276
326
|
// open [
|
|
277
327
|
// any text - the link text
|
|
278
328
|
// closing ]
|
|
279
329
|
// no bracket after
|
|
280
|
-
|
|
330
|
+
const unnamedPassageLinkRegex = /\[(.*?)\]([^(]|$)/g;
|
|
281
331
|
links = allMatchesForGroup(input, unnamedPassageLinkRegex, 1);
|
|
282
332
|
checkPassageLinks(links, section, passage);
|
|
283
|
-
input = input.replace(unnamedPassageLinkRegex, '
|
|
284
|
-
return
|
|
333
|
+
input = input.replace(unnamedPassageLinkRegex, '{{passage "$1"}}$2');
|
|
334
|
+
return input;
|
|
285
335
|
}
|
|
286
|
-
;
|
|
287
336
|
function allMatchesForGroup(input, regex, groupNumber) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
while (
|
|
337
|
+
const result = [];
|
|
338
|
+
let match;
|
|
339
|
+
while ((match = regex.exec(input))) {
|
|
291
340
|
result.push(match[groupNumber]);
|
|
292
341
|
}
|
|
293
342
|
return result;
|
|
294
343
|
}
|
|
295
|
-
;
|
|
296
344
|
function checkSectionLinks(links, section, passage) {
|
|
297
|
-
|
|
298
|
-
showBadLinksWarning(badLinks,
|
|
345
|
+
const badLinks = links.filter(m => !linkDestinationExists(m, story.sections));
|
|
346
|
+
showBadLinksWarning(badLinks, "section", "[[", "]]", section, passage);
|
|
299
347
|
}
|
|
300
|
-
;
|
|
301
348
|
function checkPassageLinks(links, section, passage) {
|
|
302
|
-
|
|
303
|
-
showBadLinksWarning(badLinks,
|
|
349
|
+
const badLinks = links.filter(m => !linkDestinationExists(m, section.passages));
|
|
350
|
+
showBadLinksWarning(badLinks, "passage", "[", "]", section, passage);
|
|
304
351
|
}
|
|
305
|
-
;
|
|
306
352
|
function linkDestinationExists(link, keys) {
|
|
307
353
|
// Link destination data may look like:
|
|
308
354
|
// passageName
|
|
309
355
|
// passageName, my_attribute=2
|
|
310
|
-
// passageName, @replace 1=new text, some_attribute=5
|
|
311
|
-
// @replace 2=some words
|
|
312
356
|
// We're only interested in checking if the named passage or section exists.
|
|
313
|
-
|
|
314
|
-
if (linkDestination.substring(0, 1) == '@') {
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
357
|
+
const linkDestination = link.split(",")[0];
|
|
317
358
|
return Object.keys(keys).includes(linkDestination);
|
|
318
359
|
}
|
|
319
|
-
;
|
|
320
360
|
function showBadLinksWarning(badLinks, linkTo, before, after, section, passage) {
|
|
321
361
|
if (!settings.onWarning)
|
|
322
362
|
return;
|
|
323
363
|
for (const badLink of badLinks) {
|
|
324
|
-
|
|
364
|
+
let warning;
|
|
325
365
|
if (!passage) {
|
|
326
366
|
warning = `${section.filename} line ${section.line}: In section '${section.name}'`;
|
|
327
367
|
}
|
|
@@ -331,24 +371,25 @@ export async function compile(settings) {
|
|
|
331
371
|
settings.onWarning(`WARNING: ${warning} there is a link to a ${linkTo} called ${before}${badLink}${after}, which doesn't exist`);
|
|
332
372
|
}
|
|
333
373
|
}
|
|
334
|
-
;
|
|
335
374
|
function writeJs(outputJsFile, tabCount, js) {
|
|
336
|
-
|
|
337
|
-
outputJsFile.push(`${tabs}
|
|
375
|
+
const tabs = new Array(tabCount + 1).join("\t");
|
|
376
|
+
outputJsFile.push(`${tabs}(squiffy, get, set) => {`);
|
|
338
377
|
for (const jsLine of js) {
|
|
339
|
-
outputJsFile.push(`${tabs}\t${jsLine}
|
|
378
|
+
outputJsFile.push(`${tabs}\t${jsLine}`);
|
|
340
379
|
}
|
|
341
|
-
outputJsFile.push(`${tabs}}
|
|
380
|
+
outputJsFile.push(`${tabs}},`);
|
|
342
381
|
}
|
|
343
|
-
;
|
|
344
382
|
const success = await processFileText(settings.script, settings.scriptBaseFilename, true);
|
|
345
383
|
if (success) {
|
|
384
|
+
if (!Object.keys(story.sections).length) {
|
|
385
|
+
ensureSectionExists(null, true, settings.scriptBaseFilename, 0);
|
|
386
|
+
}
|
|
346
387
|
const storyData = await getStoryData();
|
|
347
388
|
return {
|
|
348
389
|
success: true,
|
|
349
390
|
output: storyData,
|
|
350
|
-
getJs: () => {
|
|
351
|
-
return getJs(storyData);
|
|
391
|
+
getJs: (excludeHeader) => {
|
|
392
|
+
return getJs(storyData, excludeHeader || false);
|
|
352
393
|
},
|
|
353
394
|
getUiInfo: () => {
|
|
354
395
|
return {
|
|
@@ -378,7 +419,7 @@ class Story {
|
|
|
378
419
|
enumerable: true,
|
|
379
420
|
configurable: true,
|
|
380
421
|
writable: true,
|
|
381
|
-
value:
|
|
422
|
+
value: ""
|
|
382
423
|
});
|
|
383
424
|
Object.defineProperty(this, "scripts", {
|
|
384
425
|
enumerable: true,
|
|
@@ -396,7 +437,7 @@ class Story {
|
|
|
396
437
|
enumerable: true,
|
|
397
438
|
configurable: true,
|
|
398
439
|
writable: true,
|
|
399
|
-
value:
|
|
440
|
+
value: ""
|
|
400
441
|
});
|
|
401
442
|
Object.defineProperty(this, "id", {
|
|
402
443
|
enumerable: true,
|
|
@@ -404,6 +445,12 @@ class Story {
|
|
|
404
445
|
writable: true,
|
|
405
446
|
value: null
|
|
406
447
|
});
|
|
448
|
+
Object.defineProperty(this, "uiJs", {
|
|
449
|
+
enumerable: true,
|
|
450
|
+
configurable: true,
|
|
451
|
+
writable: true,
|
|
452
|
+
value: []
|
|
453
|
+
});
|
|
407
454
|
this.id = inputFilename || null;
|
|
408
455
|
}
|
|
409
456
|
addSection(name, filename, line) {
|
|
@@ -411,7 +458,9 @@ class Story {
|
|
|
411
458
|
this.sections[name] = section;
|
|
412
459
|
return section;
|
|
413
460
|
}
|
|
414
|
-
|
|
461
|
+
addUiJs(text) {
|
|
462
|
+
this.uiJs.push(text);
|
|
463
|
+
}
|
|
415
464
|
}
|
|
416
465
|
class Section {
|
|
417
466
|
constructor(name, filename, line) {
|
|
@@ -468,23 +517,19 @@ class Section {
|
|
|
468
517
|
this.line = line;
|
|
469
518
|
}
|
|
470
519
|
addPassage(name, line) {
|
|
471
|
-
|
|
520
|
+
const passage = new Passage(name, line);
|
|
472
521
|
this.passages[name] = passage;
|
|
473
522
|
return passage;
|
|
474
523
|
}
|
|
475
|
-
;
|
|
476
524
|
addText(text) {
|
|
477
525
|
this.text.push(text);
|
|
478
526
|
}
|
|
479
|
-
;
|
|
480
527
|
addJS(text) {
|
|
481
528
|
this.js.push(text);
|
|
482
529
|
}
|
|
483
|
-
;
|
|
484
530
|
addAttribute(text) {
|
|
485
531
|
this.attributes.push(text);
|
|
486
532
|
}
|
|
487
|
-
;
|
|
488
533
|
}
|
|
489
534
|
class Passage {
|
|
490
535
|
constructor(name, line) {
|
|
@@ -530,13 +575,10 @@ class Passage {
|
|
|
530
575
|
addText(text) {
|
|
531
576
|
this.text.push(text);
|
|
532
577
|
}
|
|
533
|
-
;
|
|
534
578
|
addJS(text) {
|
|
535
579
|
this.js.push(text);
|
|
536
580
|
}
|
|
537
|
-
;
|
|
538
581
|
addAttribute(text) {
|
|
539
582
|
this.attributes.push(text);
|
|
540
583
|
}
|
|
541
|
-
;
|
|
542
584
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squiffy-compiler",
|
|
3
|
-
"version": "6.0.0-alpha.
|
|
3
|
+
"version": "6.0.0-alpha.17",
|
|
4
4
|
"description": "A tool for creating multiple-choice interactive stories",
|
|
5
|
-
"dependencies": {
|
|
6
|
-
"marked": "^13.0.2"
|
|
7
|
-
},
|
|
8
5
|
"author": "Alex Warren",
|
|
9
6
|
"contributors": [
|
|
10
7
|
"CrisisSDK",
|
|
@@ -18,19 +15,21 @@
|
|
|
18
15
|
"license": "MIT",
|
|
19
16
|
"preferGlobal": true,
|
|
20
17
|
"devDependencies": {
|
|
21
|
-
"@types/node": "^
|
|
22
|
-
"glob": "^11.0.
|
|
23
|
-
"typescript": "^5.
|
|
24
|
-
"vitest": "^2.
|
|
18
|
+
"@types/node": "^24.3.0",
|
|
19
|
+
"glob": "^11.0.3",
|
|
20
|
+
"typescript": "^5.9.2",
|
|
21
|
+
"vitest": "^3.2.4"
|
|
25
22
|
},
|
|
26
23
|
"scripts": {
|
|
27
|
-
"test": "vitest",
|
|
28
|
-
"build": "tsc"
|
|
24
|
+
"test": "vitest --run",
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
29
27
|
},
|
|
30
28
|
"main": "dist/compiler.js",
|
|
31
29
|
"types": "dist/compiler.d.ts",
|
|
32
30
|
"files": [
|
|
33
31
|
"dist"
|
|
34
32
|
],
|
|
35
|
-
"type": "module"
|
|
33
|
+
"type": "module",
|
|
34
|
+
"gitHead": "59e410271573c741dbc7c6031b7aab5619d9d1c7"
|
|
36
35
|
}
|