squiffy-compiler 6.0.0-alpha.2 → 6.0.0-alpha.20
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 +3 -2
- package/dist/compiler.js +147 -107
- 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.2";
|
|
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[]>;
|
package/dist/compiler.js
CHANGED
|
@@ -1,24 +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
|
+
let autoSectionCount = 0;
|
|
6
7
|
async function getJs(storyData, excludeHeader) {
|
|
7
8
|
const outputJs = [];
|
|
8
9
|
if (!excludeHeader) {
|
|
9
|
-
outputJs.push(`// Created with Squiffy ${
|
|
10
|
-
outputJs.push(
|
|
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 = {};");
|
|
11
18
|
}
|
|
12
|
-
outputJs.push('export const story = {};');
|
|
13
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
|
+
}
|
|
14
23
|
outputJs.push(`story.start = ${JSON.stringify(storyData.story.start, null, 4)};`);
|
|
15
24
|
outputJs.push(`story.sections = ${JSON.stringify(storyData.story.sections, null, 4)};`);
|
|
16
|
-
outputJs.push(
|
|
25
|
+
outputJs.push("story.js = [");
|
|
17
26
|
for (const js of storyData.js) {
|
|
18
27
|
writeJs(outputJs, 1, js);
|
|
19
28
|
}
|
|
20
|
-
outputJs.push(
|
|
21
|
-
return outputJs.join(
|
|
29
|
+
outputJs.push("];");
|
|
30
|
+
return outputJs.join("\n");
|
|
22
31
|
}
|
|
23
32
|
async function getStoryData() {
|
|
24
33
|
if (!story.start) {
|
|
@@ -32,6 +41,10 @@ export async function compile(settings) {
|
|
|
32
41
|
},
|
|
33
42
|
js: [],
|
|
34
43
|
};
|
|
44
|
+
if (story.uiJs.length > 0) {
|
|
45
|
+
output.js.push(story.uiJs);
|
|
46
|
+
output.story.uiJsIndex = output.js.length - 1;
|
|
47
|
+
}
|
|
35
48
|
for (const sectionName of Object.keys(story.sections)) {
|
|
36
49
|
const section = story.sections[sectionName];
|
|
37
50
|
const outputSection = {};
|
|
@@ -39,7 +52,7 @@ export async function compile(settings) {
|
|
|
39
52
|
if (section.clear) {
|
|
40
53
|
outputSection.clear = true;
|
|
41
54
|
}
|
|
42
|
-
outputSection.text = await processText(section.text.join(
|
|
55
|
+
outputSection.text = await processText(section.text.join("\n"), section, null);
|
|
43
56
|
if (section.attributes.length > 0) {
|
|
44
57
|
outputSection.attributes = section.attributes;
|
|
45
58
|
}
|
|
@@ -47,10 +60,10 @@ export async function compile(settings) {
|
|
|
47
60
|
output.js.push(section.js);
|
|
48
61
|
outputSection.jsIndex = output.js.length - 1;
|
|
49
62
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
63
|
+
if ("@last" in section.passages) {
|
|
64
|
+
let passageCount = 0;
|
|
52
65
|
for (const passageName of Object.keys(section.passages)) {
|
|
53
|
-
if (passageName.length > 0 && passageName?.substring(0, 1) !==
|
|
66
|
+
if (passageName.length > 0 && passageName?.substring(0, 1) !== "@") {
|
|
54
67
|
passageCount++;
|
|
55
68
|
}
|
|
56
69
|
}
|
|
@@ -66,7 +79,7 @@ export async function compile(settings) {
|
|
|
66
79
|
if (passage.clear) {
|
|
67
80
|
outputPassage.clear = true;
|
|
68
81
|
}
|
|
69
|
-
outputPassage.text = await processText(passage.text.join(
|
|
82
|
+
outputPassage.text = await processText(passage.text.join("\n"), section, passage);
|
|
70
83
|
if (passage.attributes.length > 0) {
|
|
71
84
|
outputPassage.attributes = passage.attributes;
|
|
72
85
|
}
|
|
@@ -78,7 +91,6 @@ export async function compile(settings) {
|
|
|
78
91
|
}
|
|
79
92
|
return output;
|
|
80
93
|
}
|
|
81
|
-
;
|
|
82
94
|
const regexes = {
|
|
83
95
|
section: /^\[\[(.*)\]\]:$/,
|
|
84
96
|
passage: /^\[(.*)\]:$/,
|
|
@@ -89,28 +101,38 @@ export async function compile(settings) {
|
|
|
89
101
|
unset: /^@unset (.*)$/,
|
|
90
102
|
inc: /^@inc (\S+)(?: (\d+))?$/,
|
|
91
103
|
dec: /^@dec (\S+)(?: (\d+))?$/,
|
|
92
|
-
replace: /^@replace (.*$)/,
|
|
93
104
|
js: /^(\t| {4})(.*)$/,
|
|
94
105
|
continue: /^\+\+\+(.*)$/,
|
|
106
|
+
ui: /^@ui (.*)$/,
|
|
95
107
|
};
|
|
96
108
|
async function processFileText(inputText, inputFilename, isFirst) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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 = () => {
|
|
104
116
|
return ensureSectionExists(section, isFirst, inputFilename, lineCount);
|
|
105
117
|
};
|
|
106
|
-
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
|
+
};
|
|
107
126
|
for (const line of inputLines) {
|
|
108
|
-
|
|
127
|
+
const stripLine = line.trim();
|
|
109
128
|
lineCount++;
|
|
110
|
-
|
|
129
|
+
const match = {};
|
|
111
130
|
for (const key of Object.keys(regexes)) {
|
|
112
131
|
const regex = regexes[key];
|
|
113
|
-
|
|
132
|
+
const result = key == "js" ? regex.exec(line) : regex.exec(stripLine);
|
|
133
|
+
if (result) {
|
|
134
|
+
match[key] = result;
|
|
135
|
+
}
|
|
114
136
|
}
|
|
115
137
|
if (match.section) {
|
|
116
138
|
section = story.addSection(match.section[1], inputFilename, lineCount);
|
|
@@ -119,7 +141,7 @@ export async function compile(settings) {
|
|
|
119
141
|
}
|
|
120
142
|
else if (match.passage) {
|
|
121
143
|
if (!section) {
|
|
122
|
-
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.`);
|
|
123
145
|
return false;
|
|
124
146
|
}
|
|
125
147
|
section = ensureThisSectionExists();
|
|
@@ -128,14 +150,29 @@ export async function compile(settings) {
|
|
|
128
150
|
}
|
|
129
151
|
else if (match.continue) {
|
|
130
152
|
section = ensureThisSectionExists();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
153
|
+
const previousSection = section;
|
|
154
|
+
const autoSectionName = addAutoSection();
|
|
155
|
+
const text = match.continue[1] || "Continue...";
|
|
156
|
+
previousSection.addText(`[[${text}]](${autoSectionName})`);
|
|
157
|
+
}
|
|
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;
|
|
137
170
|
}
|
|
138
|
-
else if (
|
|
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") {
|
|
139
176
|
if (!passage) {
|
|
140
177
|
section = ensureThisSectionExists();
|
|
141
178
|
section.clear = true;
|
|
@@ -151,18 +188,18 @@ export async function compile(settings) {
|
|
|
151
188
|
story.start = match.start[1];
|
|
152
189
|
}
|
|
153
190
|
else if (match.import && settings.externalFiles) {
|
|
154
|
-
|
|
191
|
+
const importFilenames = await settings.externalFiles.getMatchingFilenames(match.import[1]);
|
|
155
192
|
for (const importFilename of importFilenames) {
|
|
156
|
-
if (importFilename.endsWith(
|
|
193
|
+
if (importFilename.endsWith(".squiffy")) {
|
|
157
194
|
const content = await settings.externalFiles.getContent(importFilename);
|
|
158
|
-
|
|
195
|
+
const success = await processFileText(content, importFilename, false);
|
|
159
196
|
if (!success)
|
|
160
197
|
return false;
|
|
161
198
|
}
|
|
162
|
-
else if (importFilename.endsWith(
|
|
199
|
+
else if (importFilename.endsWith(".js")) {
|
|
163
200
|
story.scripts.push(settings.externalFiles.getLocalFilename(importFilename));
|
|
164
201
|
}
|
|
165
|
-
else if (importFilename.endsWith(
|
|
202
|
+
else if (importFilename.endsWith(".css")) {
|
|
166
203
|
story.stylesheets.push(settings.externalFiles.getLocalFilename(importFilename));
|
|
167
204
|
}
|
|
168
205
|
}
|
|
@@ -173,31 +210,21 @@ export async function compile(settings) {
|
|
|
173
210
|
}
|
|
174
211
|
else if (match.unset) {
|
|
175
212
|
section = ensureThisSectionExists();
|
|
176
|
-
section = addAttribute(
|
|
213
|
+
section = addAttribute("not " + match.unset[1], section, passage, isFirst, inputFilename, lineCount);
|
|
177
214
|
}
|
|
178
215
|
else if (match.inc) {
|
|
179
216
|
section = ensureThisSectionExists();
|
|
180
|
-
section = addAttribute(match.inc[1] +
|
|
217
|
+
section = addAttribute(match.inc[1] + "+=" + (match.inc[2] === undefined ? "1" : match.inc[2]), section, passage, isFirst, inputFilename, lineCount);
|
|
181
218
|
}
|
|
182
219
|
else if (match.dec) {
|
|
183
220
|
section = ensureThisSectionExists();
|
|
184
|
-
section = addAttribute(match.dec[1] +
|
|
185
|
-
}
|
|
186
|
-
else if (match.replace) {
|
|
187
|
-
const thisSection = ensureThisSectionExists();
|
|
188
|
-
const thisPassage = passage;
|
|
189
|
-
var replaceAttribute = match.replace[1];
|
|
190
|
-
var attributeMatch = /^(.*?)=(.*)$/.exec(replaceAttribute);
|
|
191
|
-
secondPass.push(async () => {
|
|
192
|
-
// add this to secondPass functions, because processText might result in links to passages which have not been created yet
|
|
193
|
-
if (attributeMatch) {
|
|
194
|
-
replaceAttribute = attributeMatch[1] + '=' + await processText(attributeMatch[2], thisSection, null);
|
|
195
|
-
}
|
|
196
|
-
addAttribute('@replace ' + replaceAttribute, section, thisPassage, isFirst, inputFilename, lineCount);
|
|
197
|
-
});
|
|
221
|
+
section = addAttribute(match.dec[1] + "-=" + (match.dec[2] === undefined ? "1" : match.dec[2]), section, passage, isFirst, inputFilename, lineCount);
|
|
198
222
|
}
|
|
199
223
|
else if (!textStarted && match.js) {
|
|
200
|
-
if (
|
|
224
|
+
if (inUiBlock) {
|
|
225
|
+
story.addUiJs(match.js[2]);
|
|
226
|
+
}
|
|
227
|
+
else if (!passage) {
|
|
201
228
|
section = ensureThisSectionExists();
|
|
202
229
|
section.addJS(match.js[2]);
|
|
203
230
|
}
|
|
@@ -206,7 +233,10 @@ export async function compile(settings) {
|
|
|
206
233
|
}
|
|
207
234
|
}
|
|
208
235
|
else if (textStarted || stripLine.length > 0) {
|
|
209
|
-
if (
|
|
236
|
+
if (inUiBlock) {
|
|
237
|
+
errors.push(`ERROR: ${inputFilename} line ${lineCount}: Unexpected text in @ui block.`);
|
|
238
|
+
}
|
|
239
|
+
else if (!passage) {
|
|
210
240
|
section = ensureThisSectionExists();
|
|
211
241
|
if (section) {
|
|
212
242
|
section.addText(line);
|
|
@@ -219,19 +249,14 @@ export async function compile(settings) {
|
|
|
219
249
|
}
|
|
220
250
|
}
|
|
221
251
|
}
|
|
222
|
-
for (const fn of secondPass) {
|
|
223
|
-
await fn();
|
|
224
|
-
}
|
|
225
252
|
return true;
|
|
226
253
|
}
|
|
227
|
-
;
|
|
228
254
|
function ensureSectionExists(section, isFirst, inputFilename, lineCount) {
|
|
229
255
|
if (!section && isFirst) {
|
|
230
|
-
section = story.addSection(
|
|
256
|
+
section = story.addSection("_default", inputFilename, lineCount);
|
|
231
257
|
}
|
|
232
258
|
return section;
|
|
233
259
|
}
|
|
234
|
-
;
|
|
235
260
|
function addAttribute(attribute, section, passage, isFirst, inputFilename, lineCount) {
|
|
236
261
|
if (!passage) {
|
|
237
262
|
section = ensureSectionExists(section, isFirst, inputFilename, lineCount);
|
|
@@ -242,7 +267,24 @@ export async function compile(settings) {
|
|
|
242
267
|
}
|
|
243
268
|
return section;
|
|
244
269
|
}
|
|
245
|
-
|
|
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
|
+
}
|
|
246
288
|
async function processText(input, section, passage) {
|
|
247
289
|
// namedSectionLinkRegex matches:
|
|
248
290
|
// open [[
|
|
@@ -251,10 +293,13 @@ export async function compile(settings) {
|
|
|
251
293
|
// open bracket
|
|
252
294
|
// any text - the name of the section
|
|
253
295
|
// closing bracket
|
|
254
|
-
|
|
255
|
-
|
|
296
|
+
const namedSectionLinkRegex = /\[\[([^\]]*?)\]\]\((.*?)\)/g;
|
|
297
|
+
let links = allMatchesForGroup(input, namedSectionLinkRegex, 2);
|
|
256
298
|
checkSectionLinks(links, section, passage);
|
|
257
|
-
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
|
+
});
|
|
258
303
|
// namedPassageLinkRegex matches:
|
|
259
304
|
// open [
|
|
260
305
|
// any text - the link text
|
|
@@ -262,68 +307,61 @@ export async function compile(settings) {
|
|
|
262
307
|
// open bracket, but not http(s):// after it
|
|
263
308
|
// any text - the name of the passage
|
|
264
309
|
// closing bracket
|
|
265
|
-
|
|
310
|
+
const namedPassageLinkRegex = /\[([^\]]*?)\]\(((?!https?:\/\/).*?)\)/g;
|
|
266
311
|
links = allMatchesForGroup(input, namedPassageLinkRegex, 2);
|
|
267
312
|
checkPassageLinks(links, section, passage);
|
|
268
|
-
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
|
+
});
|
|
269
317
|
// unnamedSectionLinkRegex matches:
|
|
270
318
|
// open [[
|
|
271
319
|
// any text - the link text
|
|
272
320
|
// closing ]]
|
|
273
|
-
|
|
321
|
+
const unnamedSectionLinkRegex = /\[\[(.*?)\]\]/g;
|
|
274
322
|
links = allMatchesForGroup(input, unnamedSectionLinkRegex, 1);
|
|
275
323
|
checkSectionLinks(links, section, passage);
|
|
276
|
-
input = input.replace(unnamedSectionLinkRegex, '
|
|
324
|
+
input = input.replace(unnamedSectionLinkRegex, '{{section "$1"}}');
|
|
277
325
|
// unnamedPassageLinkRegex matches:
|
|
278
326
|
// open [
|
|
279
327
|
// any text - the link text
|
|
280
328
|
// closing ]
|
|
281
329
|
// no bracket after
|
|
282
|
-
|
|
330
|
+
const unnamedPassageLinkRegex = /\[(.*?)\]([^(]|$)/g;
|
|
283
331
|
links = allMatchesForGroup(input, unnamedPassageLinkRegex, 1);
|
|
284
332
|
checkPassageLinks(links, section, passage);
|
|
285
|
-
input = input.replace(unnamedPassageLinkRegex, '
|
|
286
|
-
return
|
|
333
|
+
input = input.replace(unnamedPassageLinkRegex, '{{passage "$1"}}$2');
|
|
334
|
+
return input;
|
|
287
335
|
}
|
|
288
|
-
;
|
|
289
336
|
function allMatchesForGroup(input, regex, groupNumber) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
while (
|
|
337
|
+
const result = [];
|
|
338
|
+
let match;
|
|
339
|
+
while ((match = regex.exec(input))) {
|
|
293
340
|
result.push(match[groupNumber]);
|
|
294
341
|
}
|
|
295
342
|
return result;
|
|
296
343
|
}
|
|
297
|
-
;
|
|
298
344
|
function checkSectionLinks(links, section, passage) {
|
|
299
|
-
|
|
300
|
-
showBadLinksWarning(badLinks,
|
|
345
|
+
const badLinks = links.filter(m => !linkDestinationExists(m, story.sections));
|
|
346
|
+
showBadLinksWarning(badLinks, "section", "[[", "]]", section, passage);
|
|
301
347
|
}
|
|
302
|
-
;
|
|
303
348
|
function checkPassageLinks(links, section, passage) {
|
|
304
|
-
|
|
305
|
-
showBadLinksWarning(badLinks,
|
|
349
|
+
const badLinks = links.filter(m => !linkDestinationExists(m, section.passages));
|
|
350
|
+
showBadLinksWarning(badLinks, "passage", "[", "]", section, passage);
|
|
306
351
|
}
|
|
307
|
-
;
|
|
308
352
|
function linkDestinationExists(link, keys) {
|
|
309
353
|
// Link destination data may look like:
|
|
310
354
|
// passageName
|
|
311
355
|
// passageName, my_attribute=2
|
|
312
|
-
// passageName, @replace 1=new text, some_attribute=5
|
|
313
|
-
// @replace 2=some words
|
|
314
356
|
// We're only interested in checking if the named passage or section exists.
|
|
315
|
-
|
|
316
|
-
if (linkDestination.substring(0, 1) == '@') {
|
|
317
|
-
return true;
|
|
318
|
-
}
|
|
357
|
+
const linkDestination = link.split(",")[0];
|
|
319
358
|
return Object.keys(keys).includes(linkDestination);
|
|
320
359
|
}
|
|
321
|
-
;
|
|
322
360
|
function showBadLinksWarning(badLinks, linkTo, before, after, section, passage) {
|
|
323
361
|
if (!settings.onWarning)
|
|
324
362
|
return;
|
|
325
363
|
for (const badLink of badLinks) {
|
|
326
|
-
|
|
364
|
+
let warning;
|
|
327
365
|
if (!passage) {
|
|
328
366
|
warning = `${section.filename} line ${section.line}: In section '${section.name}'`;
|
|
329
367
|
}
|
|
@@ -333,18 +371,19 @@ export async function compile(settings) {
|
|
|
333
371
|
settings.onWarning(`WARNING: ${warning} there is a link to a ${linkTo} called ${before}${badLink}${after}, which doesn't exist`);
|
|
334
372
|
}
|
|
335
373
|
}
|
|
336
|
-
;
|
|
337
374
|
function writeJs(outputJsFile, tabCount, js) {
|
|
338
|
-
|
|
375
|
+
const tabs = new Array(tabCount + 1).join("\t");
|
|
339
376
|
outputJsFile.push(`${tabs}(squiffy, get, set) => {`);
|
|
340
377
|
for (const jsLine of js) {
|
|
341
378
|
outputJsFile.push(`${tabs}\t${jsLine}`);
|
|
342
379
|
}
|
|
343
380
|
outputJsFile.push(`${tabs}},`);
|
|
344
381
|
}
|
|
345
|
-
;
|
|
346
382
|
const success = await processFileText(settings.script, settings.scriptBaseFilename, true);
|
|
347
383
|
if (success) {
|
|
384
|
+
if (!Object.keys(story.sections).length) {
|
|
385
|
+
ensureSectionExists(null, true, settings.scriptBaseFilename, 0);
|
|
386
|
+
}
|
|
348
387
|
const storyData = await getStoryData();
|
|
349
388
|
return {
|
|
350
389
|
success: true,
|
|
@@ -380,7 +419,7 @@ class Story {
|
|
|
380
419
|
enumerable: true,
|
|
381
420
|
configurable: true,
|
|
382
421
|
writable: true,
|
|
383
|
-
value:
|
|
422
|
+
value: ""
|
|
384
423
|
});
|
|
385
424
|
Object.defineProperty(this, "scripts", {
|
|
386
425
|
enumerable: true,
|
|
@@ -398,7 +437,7 @@ class Story {
|
|
|
398
437
|
enumerable: true,
|
|
399
438
|
configurable: true,
|
|
400
439
|
writable: true,
|
|
401
|
-
value:
|
|
440
|
+
value: ""
|
|
402
441
|
});
|
|
403
442
|
Object.defineProperty(this, "id", {
|
|
404
443
|
enumerable: true,
|
|
@@ -406,6 +445,12 @@ class Story {
|
|
|
406
445
|
writable: true,
|
|
407
446
|
value: null
|
|
408
447
|
});
|
|
448
|
+
Object.defineProperty(this, "uiJs", {
|
|
449
|
+
enumerable: true,
|
|
450
|
+
configurable: true,
|
|
451
|
+
writable: true,
|
|
452
|
+
value: []
|
|
453
|
+
});
|
|
409
454
|
this.id = inputFilename || null;
|
|
410
455
|
}
|
|
411
456
|
addSection(name, filename, line) {
|
|
@@ -413,7 +458,9 @@ class Story {
|
|
|
413
458
|
this.sections[name] = section;
|
|
414
459
|
return section;
|
|
415
460
|
}
|
|
416
|
-
|
|
461
|
+
addUiJs(text) {
|
|
462
|
+
this.uiJs.push(text);
|
|
463
|
+
}
|
|
417
464
|
}
|
|
418
465
|
class Section {
|
|
419
466
|
constructor(name, filename, line) {
|
|
@@ -470,23 +517,19 @@ class Section {
|
|
|
470
517
|
this.line = line;
|
|
471
518
|
}
|
|
472
519
|
addPassage(name, line) {
|
|
473
|
-
|
|
520
|
+
const passage = new Passage(name, line);
|
|
474
521
|
this.passages[name] = passage;
|
|
475
522
|
return passage;
|
|
476
523
|
}
|
|
477
|
-
;
|
|
478
524
|
addText(text) {
|
|
479
525
|
this.text.push(text);
|
|
480
526
|
}
|
|
481
|
-
;
|
|
482
527
|
addJS(text) {
|
|
483
528
|
this.js.push(text);
|
|
484
529
|
}
|
|
485
|
-
;
|
|
486
530
|
addAttribute(text) {
|
|
487
531
|
this.attributes.push(text);
|
|
488
532
|
}
|
|
489
|
-
;
|
|
490
533
|
}
|
|
491
534
|
class Passage {
|
|
492
535
|
constructor(name, line) {
|
|
@@ -532,13 +575,10 @@ class Passage {
|
|
|
532
575
|
addText(text) {
|
|
533
576
|
this.text.push(text);
|
|
534
577
|
}
|
|
535
|
-
;
|
|
536
578
|
addJS(text) {
|
|
537
579
|
this.js.push(text);
|
|
538
580
|
}
|
|
539
|
-
;
|
|
540
581
|
addAttribute(text) {
|
|
541
582
|
this.attributes.push(text);
|
|
542
583
|
}
|
|
543
|
-
;
|
|
544
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.20",
|
|
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": "eee4b8836ed791b68d8d56b0d5071129e6214d2b"
|
|
36
35
|
}
|