squiffy-compiler 6.0.0-alpha.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/README.md +3 -0
- package/dist/compiler.d.ts +51 -0
- package/dist/compiler.js +542 -0
- package/dist/compiler.test.d.ts +1 -0
- package/dist/compiler.test.js +73 -0
- package/dist/external-files.d.ts +5 -0
- package/dist/external-files.js +21 -0
- package/dist/index.template.html +39 -0
- package/dist/packager.d.ts +1 -0
- package/dist/packager.js +78 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +15 -0
- package/dist/squiffy.d.ts +1 -0
- package/dist/squiffy.js +29 -0
- package/dist/squiffy.runtime.d.ts +34 -0
- package/dist/squiffy.template.d.ts +29 -0
- package/dist/squiffy.template.js +598 -0
- package/dist/style.template.css +52 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/examples/attributes/attributes.squiffy +81 -0
- package/examples/clearscreen/clearscreen.squiffy +15 -0
- package/examples/continue/continue.squiffy +18 -0
- package/examples/helloworld/helloworld.squiffy +1 -0
- package/examples/import/file2.squiffy +8 -0
- package/examples/import/test.js +3 -0
- package/examples/import/test.squiffy +5 -0
- package/examples/input/input.squiffy +22 -0
- package/examples/last/last.squiffy +32 -0
- package/examples/master/master.squiffy +35 -0
- package/examples/replace/replace.squiffy +27 -0
- package/examples/rotate/rotate.squiffy +25 -0
- package/examples/sectiontrack/sectiontrack.squiffy +16 -0
- package/examples/start/start.squiffy +7 -0
- package/examples/test/example.squiffy +52 -0
- package/examples/textprocessor/textprocessor.squiffy +21 -0
- package/examples/transitions/transitions.squiffy +53 -0
- package/examples/turncount/turncount.squiffy +41 -0
- package/examples/warnings/warnings.squiffy +23 -0
- package/examples/warnings/warnings2.squiffy +3 -0
- package/package.json +46 -0
- package/src/__snapshots__/compiler.test.ts.snap +716 -0
- package/src/compiler.test.ts +86 -0
- package/src/compiler.ts +546 -0
- package/src/external-files.ts +22 -0
- package/src/index.template.html +39 -0
- package/src/packager.ts +97 -0
- package/src/server.ts +19 -0
- package/src/squiffy.runtime.ts +670 -0
- package/src/squiffy.ts +36 -0
- package/src/style.template.css +52 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +22 -0
- package/tsconfig.runtime.json +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export interface Output {
|
|
2
|
+
story: OutputStory;
|
|
3
|
+
js: string[][];
|
|
4
|
+
}
|
|
5
|
+
interface OutputStory {
|
|
6
|
+
start: string;
|
|
7
|
+
id: string | null;
|
|
8
|
+
sections: Record<string, OutputSection>;
|
|
9
|
+
}
|
|
10
|
+
interface OutputSection {
|
|
11
|
+
text?: string;
|
|
12
|
+
clear?: boolean;
|
|
13
|
+
attributes?: string[];
|
|
14
|
+
jsIndex?: number;
|
|
15
|
+
passages?: Record<string, OutputPassage>;
|
|
16
|
+
passageCount?: number;
|
|
17
|
+
}
|
|
18
|
+
interface OutputPassage {
|
|
19
|
+
text?: string;
|
|
20
|
+
clear?: boolean;
|
|
21
|
+
attributes?: string[];
|
|
22
|
+
jsIndex?: number;
|
|
23
|
+
}
|
|
24
|
+
interface CompilerSettings {
|
|
25
|
+
scriptBaseFilename: string;
|
|
26
|
+
script: string;
|
|
27
|
+
onWarning?: (message: string) => void;
|
|
28
|
+
externalFiles?: ExternalFiles;
|
|
29
|
+
}
|
|
30
|
+
interface ExternalFiles {
|
|
31
|
+
getMatchingFilenames(pattern: string): Promise<string[]>;
|
|
32
|
+
getContent(filename: string): Promise<string>;
|
|
33
|
+
getLocalFilename(filename: string): string;
|
|
34
|
+
}
|
|
35
|
+
interface UiInfo {
|
|
36
|
+
title: string;
|
|
37
|
+
externalScripts: string[];
|
|
38
|
+
externalStylesheets: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface CompileSuccess {
|
|
41
|
+
success: true;
|
|
42
|
+
output: Output;
|
|
43
|
+
getJs: () => Promise<string>;
|
|
44
|
+
getUiInfo: () => UiInfo;
|
|
45
|
+
}
|
|
46
|
+
export interface CompileError {
|
|
47
|
+
success: false;
|
|
48
|
+
errors: string[];
|
|
49
|
+
}
|
|
50
|
+
export declare function compile(settings: CompilerSettings): Promise<CompileSuccess | CompileError>;
|
|
51
|
+
export {};
|
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import * as marked from 'marked';
|
|
2
|
+
import { SQUIFFY_VERSION } from './version.js';
|
|
3
|
+
export async function compile(settings) {
|
|
4
|
+
const story = new Story(settings.scriptBaseFilename);
|
|
5
|
+
const errors = [];
|
|
6
|
+
async function getJs(storyData) {
|
|
7
|
+
const outputJs = [];
|
|
8
|
+
outputJs.push(`// Created with Squiffy ${SQUIFFY_VERSION}`);
|
|
9
|
+
outputJs.push('// https://github.com/textadventures/squiffy');
|
|
10
|
+
outputJs.push('export const story = {};');
|
|
11
|
+
outputJs.push(`story.id = ${JSON.stringify(storyData.story.id, null, 4)};`);
|
|
12
|
+
outputJs.push(`story.start = ${JSON.stringify(storyData.story.start, null, 4)};`);
|
|
13
|
+
outputJs.push(`story.sections = ${JSON.stringify(storyData.story.sections, null, 4)};`);
|
|
14
|
+
outputJs.push('story.js = [');
|
|
15
|
+
for (const js of storyData.js) {
|
|
16
|
+
writeJs(outputJs, 1, js);
|
|
17
|
+
}
|
|
18
|
+
outputJs.push('];');
|
|
19
|
+
return outputJs.join('\n');
|
|
20
|
+
}
|
|
21
|
+
async function getStoryData() {
|
|
22
|
+
if (!story.start) {
|
|
23
|
+
story.start = Object.keys(story.sections)[0];
|
|
24
|
+
}
|
|
25
|
+
const output = {
|
|
26
|
+
story: {
|
|
27
|
+
start: story.start,
|
|
28
|
+
id: story.id,
|
|
29
|
+
sections: {},
|
|
30
|
+
},
|
|
31
|
+
js: [],
|
|
32
|
+
};
|
|
33
|
+
for (const sectionName of Object.keys(story.sections)) {
|
|
34
|
+
const section = story.sections[sectionName];
|
|
35
|
+
const outputSection = {};
|
|
36
|
+
output.story.sections[sectionName] = outputSection;
|
|
37
|
+
if (section.clear) {
|
|
38
|
+
outputSection.clear = true;
|
|
39
|
+
}
|
|
40
|
+
outputSection.text = await processText(section.text.join('\n'), section, null);
|
|
41
|
+
if (section.attributes.length > 0) {
|
|
42
|
+
outputSection.attributes = section.attributes;
|
|
43
|
+
}
|
|
44
|
+
if (section.js.length > 0) {
|
|
45
|
+
output.js.push(section.js);
|
|
46
|
+
outputSection.jsIndex = output.js.length - 1;
|
|
47
|
+
}
|
|
48
|
+
if ('@last' in section.passages) {
|
|
49
|
+
var passageCount = 0;
|
|
50
|
+
for (const passageName of Object.keys(section.passages)) {
|
|
51
|
+
if (passageName.length > 0 && passageName?.substring(0, 1) !== '@') {
|
|
52
|
+
passageCount++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
outputSection.passageCount = passageCount;
|
|
56
|
+
}
|
|
57
|
+
if (Object.keys(section.passages).length == 0)
|
|
58
|
+
continue;
|
|
59
|
+
outputSection.passages = {};
|
|
60
|
+
for (const passageName of Object.keys(section.passages)) {
|
|
61
|
+
const passage = section.passages[passageName];
|
|
62
|
+
const outputPassage = {};
|
|
63
|
+
outputSection.passages[passageName] = outputPassage;
|
|
64
|
+
if (passage.clear) {
|
|
65
|
+
outputPassage.clear = true;
|
|
66
|
+
}
|
|
67
|
+
outputPassage.text = await processText(passage.text.join('\n'), section, passage);
|
|
68
|
+
if (passage.attributes.length > 0) {
|
|
69
|
+
outputPassage.attributes = passage.attributes;
|
|
70
|
+
}
|
|
71
|
+
if (passage.js.length > 0) {
|
|
72
|
+
output.js.push(passage.js);
|
|
73
|
+
outputPassage.jsIndex = output.js.length - 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return output;
|
|
78
|
+
}
|
|
79
|
+
;
|
|
80
|
+
const regexes = {
|
|
81
|
+
section: /^\[\[(.*)\]\]:$/,
|
|
82
|
+
passage: /^\[(.*)\]:$/,
|
|
83
|
+
title: /^@title (.*)$/,
|
|
84
|
+
import: /^@import (.*)$/,
|
|
85
|
+
start: /^@start (.*)$/,
|
|
86
|
+
attributes: /^@set (.*)$/,
|
|
87
|
+
unset: /^@unset (.*)$/,
|
|
88
|
+
inc: /^@inc (\S+)(?: (\d+))?$/,
|
|
89
|
+
dec: /^@dec (\S+)(?: (\d+))?$/,
|
|
90
|
+
replace: /^@replace (.*$)/,
|
|
91
|
+
js: /^(\t| {4})(.*)$/,
|
|
92
|
+
continue: /^\+\+\+(.*)$/,
|
|
93
|
+
};
|
|
94
|
+
async function processFileText(inputText, inputFilename, isFirst) {
|
|
95
|
+
var inputLines = inputText.replace(/\r/g, '').split('\n');
|
|
96
|
+
var lineCount = 0;
|
|
97
|
+
var autoSectionCount = 0;
|
|
98
|
+
var section = null;
|
|
99
|
+
var passage = null; // annotated differently to section, as a workaround for TypeScript "Property does not exist on type never"
|
|
100
|
+
var textStarted = false;
|
|
101
|
+
var ensureThisSectionExists = () => {
|
|
102
|
+
return ensureSectionExists(section, isFirst, inputFilename, lineCount);
|
|
103
|
+
};
|
|
104
|
+
const secondPass = [];
|
|
105
|
+
for (const line of inputLines) {
|
|
106
|
+
var stripLine = line.trim();
|
|
107
|
+
lineCount++;
|
|
108
|
+
var match = {};
|
|
109
|
+
for (const key of Object.keys(regexes)) {
|
|
110
|
+
const regex = regexes[key];
|
|
111
|
+
match[key] = key == 'js' ? regex.exec(line) : regex.exec(stripLine);
|
|
112
|
+
}
|
|
113
|
+
if (match.section) {
|
|
114
|
+
section = story.addSection(match.section[1], inputFilename, lineCount);
|
|
115
|
+
passage = null;
|
|
116
|
+
textStarted = false;
|
|
117
|
+
}
|
|
118
|
+
else if (match.passage) {
|
|
119
|
+
if (!section) {
|
|
120
|
+
errors.push(`ERROR: ${inputFilename} line ${lineCount}: Can\'t add passage "${match.passage[1]}" as no section has been created.`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
section = ensureThisSectionExists();
|
|
124
|
+
passage = section.addPassage(match.passage[1], lineCount);
|
|
125
|
+
textStarted = false;
|
|
126
|
+
}
|
|
127
|
+
else if (match.continue) {
|
|
128
|
+
section = ensureThisSectionExists();
|
|
129
|
+
autoSectionCount++;
|
|
130
|
+
var autoSectionName = `_continue${autoSectionCount}`;
|
|
131
|
+
section.addText(`[[${match.continue[1]}]](${autoSectionName})`);
|
|
132
|
+
section = story.addSection(autoSectionName, inputFilename, lineCount);
|
|
133
|
+
passage = null;
|
|
134
|
+
textStarted = false;
|
|
135
|
+
}
|
|
136
|
+
else if (stripLine == '@clear') {
|
|
137
|
+
if (!passage) {
|
|
138
|
+
section = ensureThisSectionExists();
|
|
139
|
+
section.clear = true;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
passage.clear = true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (match.title) {
|
|
146
|
+
story.title = match.title[1];
|
|
147
|
+
}
|
|
148
|
+
else if (match.start) {
|
|
149
|
+
story.start = match.start[1];
|
|
150
|
+
}
|
|
151
|
+
else if (match.import && settings.externalFiles) {
|
|
152
|
+
var importFilenames = await settings.externalFiles.getMatchingFilenames(match.import[1]);
|
|
153
|
+
for (const importFilename of importFilenames) {
|
|
154
|
+
if (importFilename.endsWith('.squiffy')) {
|
|
155
|
+
const content = await settings.externalFiles.getContent(importFilename);
|
|
156
|
+
var success = await processFileText(content, importFilename, false);
|
|
157
|
+
if (!success)
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
else if (importFilename.endsWith('.js')) {
|
|
161
|
+
story.scripts.push(settings.externalFiles.getLocalFilename(importFilename));
|
|
162
|
+
}
|
|
163
|
+
else if (importFilename.endsWith('.css')) {
|
|
164
|
+
story.stylesheets.push(settings.externalFiles.getLocalFilename(importFilename));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else if (match.attributes) {
|
|
169
|
+
section = ensureThisSectionExists();
|
|
170
|
+
section = addAttribute(match.attributes[1], section, passage, isFirst, inputFilename, lineCount);
|
|
171
|
+
}
|
|
172
|
+
else if (match.unset) {
|
|
173
|
+
section = ensureThisSectionExists();
|
|
174
|
+
section = addAttribute('not ' + match.unset[1], section, passage, isFirst, inputFilename, lineCount);
|
|
175
|
+
}
|
|
176
|
+
else if (match.inc) {
|
|
177
|
+
section = ensureThisSectionExists();
|
|
178
|
+
section = addAttribute(match.inc[1] + '+=' + (match.inc[2] === undefined ? '1' : match.inc[2]), section, passage, isFirst, inputFilename, lineCount);
|
|
179
|
+
}
|
|
180
|
+
else if (match.dec) {
|
|
181
|
+
section = ensureThisSectionExists();
|
|
182
|
+
section = addAttribute(match.dec[1] + '-=' + (match.dec[2] === undefined ? '1' : match.dec[2]), section, passage, isFirst, inputFilename, lineCount);
|
|
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
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else if (!textStarted && match.js) {
|
|
198
|
+
if (!passage) {
|
|
199
|
+
section = ensureThisSectionExists();
|
|
200
|
+
section.addJS(match.js[2]);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
passage.addJS(match.js[2]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else if (textStarted || stripLine.length > 0) {
|
|
207
|
+
if (!passage) {
|
|
208
|
+
section = ensureThisSectionExists();
|
|
209
|
+
if (section) {
|
|
210
|
+
section.addText(line);
|
|
211
|
+
textStarted = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
passage.addText(line);
|
|
216
|
+
textStarted = true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const fn of secondPass) {
|
|
221
|
+
await fn();
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
;
|
|
226
|
+
function ensureSectionExists(section, isFirst, inputFilename, lineCount) {
|
|
227
|
+
if (!section && isFirst) {
|
|
228
|
+
section = story.addSection('_default', inputFilename, lineCount);
|
|
229
|
+
}
|
|
230
|
+
return section;
|
|
231
|
+
}
|
|
232
|
+
;
|
|
233
|
+
function addAttribute(attribute, section, passage, isFirst, inputFilename, lineCount) {
|
|
234
|
+
if (!passage) {
|
|
235
|
+
section = ensureSectionExists(section, isFirst, inputFilename, lineCount);
|
|
236
|
+
section.addAttribute(attribute);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
passage.addAttribute(attribute);
|
|
240
|
+
}
|
|
241
|
+
return section;
|
|
242
|
+
}
|
|
243
|
+
;
|
|
244
|
+
async function processText(input, section, passage) {
|
|
245
|
+
// namedSectionLinkRegex matches:
|
|
246
|
+
// open [[
|
|
247
|
+
// any text - the link text
|
|
248
|
+
// closing ]]
|
|
249
|
+
// open bracket
|
|
250
|
+
// any text - the name of the section
|
|
251
|
+
// closing bracket
|
|
252
|
+
var namedSectionLinkRegex = /\[\[([^\]]*?)\]\]\((.*?)\)/g;
|
|
253
|
+
var links = allMatchesForGroup(input, namedSectionLinkRegex, 2);
|
|
254
|
+
checkSectionLinks(links, section, passage);
|
|
255
|
+
input = input.replace(namedSectionLinkRegex, '<a class="squiffy-link link-section" data-section="$2" role="link" tabindex="0">$1</a>');
|
|
256
|
+
// namedPassageLinkRegex matches:
|
|
257
|
+
// open [
|
|
258
|
+
// any text - the link text
|
|
259
|
+
// closing ]
|
|
260
|
+
// open bracket, but not http(s):// after it
|
|
261
|
+
// any text - the name of the passage
|
|
262
|
+
// closing bracket
|
|
263
|
+
var namedPassageLinkRegex = /\[([^\]]*?)\]\(((?!https?:\/\/).*?)\)/g;
|
|
264
|
+
links = allMatchesForGroup(input, namedPassageLinkRegex, 2);
|
|
265
|
+
checkPassageLinks(links, section, passage);
|
|
266
|
+
input = input.replace(namedPassageLinkRegex, '<a class="squiffy-link link-passage" data-passage="$2" role="link" tabindex="0">$1</a>');
|
|
267
|
+
// unnamedSectionLinkRegex matches:
|
|
268
|
+
// open [[
|
|
269
|
+
// any text - the link text
|
|
270
|
+
// closing ]]
|
|
271
|
+
var unnamedSectionLinkRegex = /\[\[(.*?)\]\]/g;
|
|
272
|
+
links = allMatchesForGroup(input, unnamedSectionLinkRegex, 1);
|
|
273
|
+
checkSectionLinks(links, section, passage);
|
|
274
|
+
input = input.replace(unnamedSectionLinkRegex, '<a class="squiffy-link link-section" data-section="$1" role="link" tabindex="0">$1</a>');
|
|
275
|
+
// unnamedPassageLinkRegex matches:
|
|
276
|
+
// open [
|
|
277
|
+
// any text - the link text
|
|
278
|
+
// closing ]
|
|
279
|
+
// no bracket after
|
|
280
|
+
var unnamedPassageLinkRegex = /\[(.*?)\]([^\(]|$)/g;
|
|
281
|
+
links = allMatchesForGroup(input, unnamedPassageLinkRegex, 1);
|
|
282
|
+
checkPassageLinks(links, section, passage);
|
|
283
|
+
input = input.replace(unnamedPassageLinkRegex, '<a class="squiffy-link link-passage" data-passage="$1" role="link" tabindex="0">$1</a>$2');
|
|
284
|
+
return (await marked.parse(input)).trim();
|
|
285
|
+
}
|
|
286
|
+
;
|
|
287
|
+
function allMatchesForGroup(input, regex, groupNumber) {
|
|
288
|
+
var result = [];
|
|
289
|
+
var match;
|
|
290
|
+
while (!!(match = regex.exec(input))) {
|
|
291
|
+
result.push(match[groupNumber]);
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
;
|
|
296
|
+
function checkSectionLinks(links, section, passage) {
|
|
297
|
+
var badLinks = links.filter(m => !linkDestinationExists(m, story.sections));
|
|
298
|
+
showBadLinksWarning(badLinks, 'section', '[[', ']]', section, passage);
|
|
299
|
+
}
|
|
300
|
+
;
|
|
301
|
+
function checkPassageLinks(links, section, passage) {
|
|
302
|
+
var badLinks = links.filter(m => !linkDestinationExists(m, section.passages));
|
|
303
|
+
showBadLinksWarning(badLinks, 'passage', '[', ']', section, passage);
|
|
304
|
+
}
|
|
305
|
+
;
|
|
306
|
+
function linkDestinationExists(link, keys) {
|
|
307
|
+
// Link destination data may look like:
|
|
308
|
+
// passageName
|
|
309
|
+
// passageName, my_attribute=2
|
|
310
|
+
// passageName, @replace 1=new text, some_attribute=5
|
|
311
|
+
// @replace 2=some words
|
|
312
|
+
// We're only interested in checking if the named passage or section exists.
|
|
313
|
+
var linkDestination = link.split(',')[0];
|
|
314
|
+
if (linkDestination.substring(0, 1) == '@') {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return Object.keys(keys).includes(linkDestination);
|
|
318
|
+
}
|
|
319
|
+
;
|
|
320
|
+
function showBadLinksWarning(badLinks, linkTo, before, after, section, passage) {
|
|
321
|
+
if (!settings.onWarning)
|
|
322
|
+
return;
|
|
323
|
+
for (const badLink of badLinks) {
|
|
324
|
+
var warning;
|
|
325
|
+
if (!passage) {
|
|
326
|
+
warning = `${section.filename} line ${section.line}: In section '${section.name}'`;
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
warning = `${section.filename} line ${passage.line}: In section '${section.name}', passage '${passage.name}'`;
|
|
330
|
+
}
|
|
331
|
+
settings.onWarning(`WARNING: ${warning} there is a link to a ${linkTo} called ${before}${badLink}${after}, which doesn't exist`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
;
|
|
335
|
+
function writeJs(outputJsFile, tabCount, js) {
|
|
336
|
+
var tabs = new Array(tabCount + 1).join('\t');
|
|
337
|
+
outputJsFile.push(`${tabs}function() {\n`);
|
|
338
|
+
for (const jsLine of js) {
|
|
339
|
+
outputJsFile.push(`${tabs}\t${jsLine}\n`);
|
|
340
|
+
}
|
|
341
|
+
outputJsFile.push(`${tabs}},\n`);
|
|
342
|
+
}
|
|
343
|
+
;
|
|
344
|
+
const success = await processFileText(settings.script, settings.scriptBaseFilename, true);
|
|
345
|
+
if (success) {
|
|
346
|
+
const storyData = await getStoryData();
|
|
347
|
+
return {
|
|
348
|
+
success: true,
|
|
349
|
+
output: storyData,
|
|
350
|
+
getJs: () => {
|
|
351
|
+
return getJs(storyData);
|
|
352
|
+
},
|
|
353
|
+
getUiInfo: () => {
|
|
354
|
+
return {
|
|
355
|
+
title: story.title,
|
|
356
|
+
externalScripts: story.scripts,
|
|
357
|
+
externalStylesheets: story.stylesheets,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
return {
|
|
364
|
+
success: false,
|
|
365
|
+
errors: errors
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
class Story {
|
|
370
|
+
constructor(inputFilename) {
|
|
371
|
+
Object.defineProperty(this, "sections", {
|
|
372
|
+
enumerable: true,
|
|
373
|
+
configurable: true,
|
|
374
|
+
writable: true,
|
|
375
|
+
value: {}
|
|
376
|
+
});
|
|
377
|
+
Object.defineProperty(this, "title", {
|
|
378
|
+
enumerable: true,
|
|
379
|
+
configurable: true,
|
|
380
|
+
writable: true,
|
|
381
|
+
value: ''
|
|
382
|
+
});
|
|
383
|
+
Object.defineProperty(this, "scripts", {
|
|
384
|
+
enumerable: true,
|
|
385
|
+
configurable: true,
|
|
386
|
+
writable: true,
|
|
387
|
+
value: []
|
|
388
|
+
});
|
|
389
|
+
Object.defineProperty(this, "stylesheets", {
|
|
390
|
+
enumerable: true,
|
|
391
|
+
configurable: true,
|
|
392
|
+
writable: true,
|
|
393
|
+
value: []
|
|
394
|
+
});
|
|
395
|
+
Object.defineProperty(this, "start", {
|
|
396
|
+
enumerable: true,
|
|
397
|
+
configurable: true,
|
|
398
|
+
writable: true,
|
|
399
|
+
value: ''
|
|
400
|
+
});
|
|
401
|
+
Object.defineProperty(this, "id", {
|
|
402
|
+
enumerable: true,
|
|
403
|
+
configurable: true,
|
|
404
|
+
writable: true,
|
|
405
|
+
value: null
|
|
406
|
+
});
|
|
407
|
+
this.id = inputFilename || null;
|
|
408
|
+
}
|
|
409
|
+
addSection(name, filename, line) {
|
|
410
|
+
const section = new Section(name, filename, line);
|
|
411
|
+
this.sections[name] = section;
|
|
412
|
+
return section;
|
|
413
|
+
}
|
|
414
|
+
;
|
|
415
|
+
}
|
|
416
|
+
class Section {
|
|
417
|
+
constructor(name, filename, line) {
|
|
418
|
+
Object.defineProperty(this, "name", {
|
|
419
|
+
enumerable: true,
|
|
420
|
+
configurable: true,
|
|
421
|
+
writable: true,
|
|
422
|
+
value: void 0
|
|
423
|
+
});
|
|
424
|
+
Object.defineProperty(this, "filename", {
|
|
425
|
+
enumerable: true,
|
|
426
|
+
configurable: true,
|
|
427
|
+
writable: true,
|
|
428
|
+
value: void 0
|
|
429
|
+
});
|
|
430
|
+
Object.defineProperty(this, "line", {
|
|
431
|
+
enumerable: true,
|
|
432
|
+
configurable: true,
|
|
433
|
+
writable: true,
|
|
434
|
+
value: void 0
|
|
435
|
+
});
|
|
436
|
+
Object.defineProperty(this, "text", {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
configurable: true,
|
|
439
|
+
writable: true,
|
|
440
|
+
value: []
|
|
441
|
+
});
|
|
442
|
+
Object.defineProperty(this, "passages", {
|
|
443
|
+
enumerable: true,
|
|
444
|
+
configurable: true,
|
|
445
|
+
writable: true,
|
|
446
|
+
value: {}
|
|
447
|
+
});
|
|
448
|
+
Object.defineProperty(this, "js", {
|
|
449
|
+
enumerable: true,
|
|
450
|
+
configurable: true,
|
|
451
|
+
writable: true,
|
|
452
|
+
value: []
|
|
453
|
+
});
|
|
454
|
+
Object.defineProperty(this, "clear", {
|
|
455
|
+
enumerable: true,
|
|
456
|
+
configurable: true,
|
|
457
|
+
writable: true,
|
|
458
|
+
value: false
|
|
459
|
+
});
|
|
460
|
+
Object.defineProperty(this, "attributes", {
|
|
461
|
+
enumerable: true,
|
|
462
|
+
configurable: true,
|
|
463
|
+
writable: true,
|
|
464
|
+
value: []
|
|
465
|
+
});
|
|
466
|
+
this.name = name;
|
|
467
|
+
this.filename = filename;
|
|
468
|
+
this.line = line;
|
|
469
|
+
}
|
|
470
|
+
addPassage(name, line) {
|
|
471
|
+
var passage = new Passage(name, line);
|
|
472
|
+
this.passages[name] = passage;
|
|
473
|
+
return passage;
|
|
474
|
+
}
|
|
475
|
+
;
|
|
476
|
+
addText(text) {
|
|
477
|
+
this.text.push(text);
|
|
478
|
+
}
|
|
479
|
+
;
|
|
480
|
+
addJS(text) {
|
|
481
|
+
this.js.push(text);
|
|
482
|
+
}
|
|
483
|
+
;
|
|
484
|
+
addAttribute(text) {
|
|
485
|
+
this.attributes.push(text);
|
|
486
|
+
}
|
|
487
|
+
;
|
|
488
|
+
}
|
|
489
|
+
class Passage {
|
|
490
|
+
constructor(name, line) {
|
|
491
|
+
Object.defineProperty(this, "name", {
|
|
492
|
+
enumerable: true,
|
|
493
|
+
configurable: true,
|
|
494
|
+
writable: true,
|
|
495
|
+
value: void 0
|
|
496
|
+
});
|
|
497
|
+
Object.defineProperty(this, "line", {
|
|
498
|
+
enumerable: true,
|
|
499
|
+
configurable: true,
|
|
500
|
+
writable: true,
|
|
501
|
+
value: void 0
|
|
502
|
+
});
|
|
503
|
+
Object.defineProperty(this, "text", {
|
|
504
|
+
enumerable: true,
|
|
505
|
+
configurable: true,
|
|
506
|
+
writable: true,
|
|
507
|
+
value: []
|
|
508
|
+
});
|
|
509
|
+
Object.defineProperty(this, "js", {
|
|
510
|
+
enumerable: true,
|
|
511
|
+
configurable: true,
|
|
512
|
+
writable: true,
|
|
513
|
+
value: []
|
|
514
|
+
});
|
|
515
|
+
Object.defineProperty(this, "clear", {
|
|
516
|
+
enumerable: true,
|
|
517
|
+
configurable: true,
|
|
518
|
+
writable: true,
|
|
519
|
+
value: false
|
|
520
|
+
});
|
|
521
|
+
Object.defineProperty(this, "attributes", {
|
|
522
|
+
enumerable: true,
|
|
523
|
+
configurable: true,
|
|
524
|
+
writable: true,
|
|
525
|
+
value: []
|
|
526
|
+
});
|
|
527
|
+
this.name = name;
|
|
528
|
+
this.line = line;
|
|
529
|
+
}
|
|
530
|
+
addText(text) {
|
|
531
|
+
this.text.push(text);
|
|
532
|
+
}
|
|
533
|
+
;
|
|
534
|
+
addJS(text) {
|
|
535
|
+
this.js.push(text);
|
|
536
|
+
}
|
|
537
|
+
;
|
|
538
|
+
addAttribute(text) {
|
|
539
|
+
this.attributes.push(text);
|
|
540
|
+
}
|
|
541
|
+
;
|
|
542
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { compile } from './compiler.js';
|
|
5
|
+
import { externalFiles } from './external-files.js';
|
|
6
|
+
function assertSuccess(obj) {
|
|
7
|
+
if (!obj || typeof obj !== 'object' || !('success' in obj) || !obj.success) {
|
|
8
|
+
throw new Error('Expected success');
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
test('"Hello world" should compile', async () => {
|
|
12
|
+
const result = await compile({
|
|
13
|
+
scriptBaseFilename: "filename.squiffy",
|
|
14
|
+
script: "hello world",
|
|
15
|
+
});
|
|
16
|
+
assertSuccess(result);
|
|
17
|
+
expect(result.output.story.start).toBe("_default");
|
|
18
|
+
expect(Object.keys(result.output.story.sections).length).toBe(1);
|
|
19
|
+
expect(result.output.story.sections._default.text).toBe("<p>hello world</p>");
|
|
20
|
+
});
|
|
21
|
+
const examples = [
|
|
22
|
+
"attributes/attributes.squiffy",
|
|
23
|
+
"clearscreen/clearscreen.squiffy",
|
|
24
|
+
"continue/continue.squiffy",
|
|
25
|
+
"helloworld/helloworld.squiffy",
|
|
26
|
+
"import/test.squiffy",
|
|
27
|
+
"last/last.squiffy",
|
|
28
|
+
"master/master.squiffy",
|
|
29
|
+
"replace/replace.squiffy",
|
|
30
|
+
"rotate/rotate.squiffy",
|
|
31
|
+
"sectiontrack/sectiontrack.squiffy",
|
|
32
|
+
"start/start.squiffy",
|
|
33
|
+
"test/example.squiffy",
|
|
34
|
+
"textprocessor/textprocessor.squiffy",
|
|
35
|
+
"transitions/transitions.squiffy",
|
|
36
|
+
"turncount/turncount.squiffy",
|
|
37
|
+
];
|
|
38
|
+
for (const example of examples) {
|
|
39
|
+
test(example, async () => {
|
|
40
|
+
const script = fs.readFileSync(`examples/${example}`, 'utf8');
|
|
41
|
+
const filename = path.basename(example);
|
|
42
|
+
const warnings = [];
|
|
43
|
+
const result = await compile({
|
|
44
|
+
scriptBaseFilename: filename,
|
|
45
|
+
script: script,
|
|
46
|
+
onWarning: (message) => {
|
|
47
|
+
console.warn(message);
|
|
48
|
+
warnings.push(message);
|
|
49
|
+
},
|
|
50
|
+
externalFiles: externalFiles(`examples/${example}`)
|
|
51
|
+
});
|
|
52
|
+
assertSuccess(result);
|
|
53
|
+
expect(result.output).toMatchSnapshot();
|
|
54
|
+
expect(warnings.length).toBe(0);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const warningExamples = [
|
|
58
|
+
"warnings/warnings.squiffy",
|
|
59
|
+
"warnings/warnings2.squiffy",
|
|
60
|
+
];
|
|
61
|
+
for (const example of warningExamples) {
|
|
62
|
+
test(example, async () => {
|
|
63
|
+
const script = fs.readFileSync(`examples/${example}`, 'utf8');
|
|
64
|
+
const filename = path.basename(example);
|
|
65
|
+
const warnings = [];
|
|
66
|
+
await compile({
|
|
67
|
+
scriptBaseFilename: filename,
|
|
68
|
+
script: script,
|
|
69
|
+
onWarning: (message) => warnings.push(message)
|
|
70
|
+
});
|
|
71
|
+
expect(warnings).toMatchSnapshot();
|
|
72
|
+
});
|
|
73
|
+
}
|