extwee 1.6.2 → 2.0.3
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/.eslintrc.json +25 -24
- package/.github/workflows/nodejs.yml +24 -24
- package/.travis.yml +13 -13
- package/CODE_OF_CONDUCT.md +82 -0
- package/LICENSE +21 -21
- package/README.md +36 -205
- package/SECURITY.md +12 -0
- package/babel.config.json +22 -0
- package/bin/extwee.js +49 -0
- package/index.js +31 -26
- package/package.json +59 -45
- package/src/FileReader.js +33 -35
- package/src/HTMLParser.js +343 -206
- package/src/HTMLWriter.js +231 -172
- package/src/Passage.js +202 -24
- package/src/Story.js +490 -122
- package/src/StoryFormat.js +300 -28
- package/src/StoryFormatParser.js +142 -59
- package/src/TweeParser.js +161 -207
- package/src/TweeWriter.js +98 -96
- package/story-formats/chapbook-1.2.0/format.js +1 -0
- package/story-formats/chapbook-1.2.0/logo.svg +1 -0
- package/story-formats/harlowe-1.2.4/format.js +1 -0
- package/story-formats/harlowe-1.2.4/icon.svg +78 -0
- package/story-formats/harlowe-2.1.0/format.js +2 -0
- package/story-formats/harlowe-2.1.0/icon.svg +78 -0
- package/story-formats/harlowe-3.1.0/format.js +3 -0
- package/story-formats/harlowe-3.1.0/icon.svg +78 -0
- package/story-formats/paperthin-1.0.0/format.js +1 -0
- package/story-formats/paperthin-1.0.0/icon.svg +5 -0
- package/story-formats/snowman-1.4.0/format.js +1 -0
- package/story-formats/snowman-1.4.0/icon.svg +436 -0
- package/story-formats/snowman-2.0.2/format.js +1 -0
- package/story-formats/snowman-2.0.2/icon.svg +436 -0
- package/story-formats/sugarcube-1.0.35/LICENSE +23 -0
- package/story-formats/sugarcube-1.0.35/format.js +1 -0
- package/story-formats/sugarcube-1.0.35/icon.svg +56 -0
- package/story-formats/sugarcube-2.31.1/LICENSE +22 -0
- package/story-formats/sugarcube-2.31.1/format.js +1 -0
- package/story-formats/sugarcube-2.31.1/icon.svg +56 -0
- package/test/{HTMLWriter/example7.twee → CLI/example6.twee} +16 -16
- package/test/CLI/harlowe.js +3 -0
- package/test/CLI/input.html +47 -0
- package/test/CLI/test.twee +18 -0
- package/test/CLI/test2.html +47 -0
- package/test/CLI/tweeExample.twee +17 -0
- package/test/CLI/twineExample.html +15 -0
- package/test/CLI.test.js +30 -0
- package/test/FileReader.test.js +14 -0
- package/test/HTMLParser/Example1.html +53 -0
- package/test/HTMLParser/Tags.html +15 -0
- package/test/HTMLParser/lyingStartnode.html +15 -0
- package/test/HTMLParser/lyingTagColors.html +48 -0
- package/test/HTMLParser/missingCreator.html +11 -0
- package/test/HTMLParser/missingCreatorVersion.html +11 -0
- package/test/HTMLParser/missingFormat.html +11 -0
- package/test/HTMLParser/missingFormatVersion.html +11 -0
- package/test/HTMLParser/missingIFID.html +11 -0
- package/test/HTMLParser/missingName.html +33 -0
- package/test/HTMLParser/missingPID.html +15 -0
- package/test/HTMLParser/missingPassageName.html +15 -0
- package/test/HTMLParser/missingPassageTags.html +15 -0
- package/test/HTMLParser/missingPosition.html +15 -0
- package/test/HTMLParser/missingScript.html +14 -0
- package/test/HTMLParser/missingSize.html +35 -0
- package/test/HTMLParser/missingStartnode.html +11 -0
- package/test/HTMLParser/missingStyle.html +14 -0
- package/test/HTMLParser/missingZoom.html +11 -0
- package/test/HTMLParser/tagColors.html +31 -0
- package/test/HTMLParser/twineExample.html +15 -46
- package/test/HTMLParser/twineExample2.html +15 -0
- package/test/HTMLParser/twineExample3.html +15 -0
- package/test/HTMLParser.test.js +177 -0
- package/test/HTMLWriter/TestTags.html +42 -0
- package/test/HTMLWriter/{test10.html → creator.html} +8 -5
- package/test/HTMLWriter/example6.twee +16 -16
- package/test/HTMLWriter/{example.twee → missingStoryTitle.twee} +29 -29
- package/test/HTMLWriter/test11.html +123 -0
- package/test/HTMLWriter/test2.html +14 -11
- package/test/HTMLWriter/test3.html +7 -13
- package/test/HTMLWriter/test4.html +8 -5
- package/test/HTMLWriter/test6.html +7 -5
- package/test/HTMLWriter.test.js +289 -0
- package/test/Passage.test.js +104 -0
- package/test/Roundtrip/Example1.html +64 -0
- package/test/Roundtrip/example1.twee +21 -0
- package/test/Roundtrip/example2.twee +18 -0
- package/test/Roundtrip/harlowe.js +3 -0
- package/test/Roundtrip/round.html +50 -0
- package/test/Roundtrip.test.js +48 -0
- package/test/Story/startmeta.twee +29 -29
- package/test/Story/test.twee +25 -25
- package/test/Story.test.js +369 -0
- package/test/StoryFormat.test.js +152 -0
- package/test/StoryFormatParser/example.js +3 -0
- package/test/StoryFormatParser/{test2.js → example2.js} +3 -3
- package/test/StoryFormatParser/format_doublename.js +1 -0
- package/test/StoryFormatParser/harlowe.js +2 -2
- package/test/StoryFormatParser/missingAuthor.js +1 -0
- package/test/StoryFormatParser/missingDescription.js +1 -0
- package/test/StoryFormatParser/missingImage.js +1 -0
- package/test/StoryFormatParser/missingLicense.js +1 -0
- package/test/StoryFormatParser/missingName.js +1 -0
- package/test/StoryFormatParser/missingProofing.js +1 -0
- package/test/StoryFormatParser/missingSource.js +1 -0
- package/test/StoryFormatParser/missingURL.js +1 -0
- package/test/StoryFormatParser/missingVersion.js +1 -0
- package/test/StoryFormatParser/versionWrong.js +1 -0
- package/test/StoryFormatParser.test.js +91 -0
- package/test/TweeParser/emptytags.twee +2 -2
- package/test/TweeParser/example.twee +32 -29
- package/test/TweeParser/missing.twee +19 -0
- package/test/{HTMLWriter/example5.twee → TweeParser/multipleScriptPassages.twee} +19 -13
- package/test/{HTMLWriter/example4.twee → TweeParser/multipleStyleTag.twee} +19 -13
- package/test/TweeParser/multipletags.twee +10 -2
- package/test/TweeParser/noTitle.twee +2 -0
- package/test/TweeParser/notes.twee +16 -32
- package/test/TweeParser/pasagemetadataerror.twee +2 -2
- package/test/{HTMLWriter/example2.twee → TweeParser/scriptPassage.twee} +16 -13
- package/test/TweeParser/singletag.twee +13 -2
- package/test/TweeParser/startMetadata.twee +14 -0
- package/test/TweeParser/storydataerror.twee +25 -25
- package/test/{HTMLWriter/example3.twee → TweeParser/stylePassage.twee} +16 -13
- package/test/TweeParser/test.twee +25 -25
- package/test/TweeParser.test.js +79 -0
- package/test/TweeWriter/test1.twee +14 -9
- package/test/TweeWriter/test3.twee +7 -10
- package/test/TweeWriter/test4.twee +14 -0
- package/test/TweeWriter/test5.twee +20 -0
- package/test/TweeWriter.test.js +85 -0
- package/main.js +0 -106
- package/src/DirectoryReader.js +0 -114
- package/src/DirectoryWatcher.js +0 -87
- package/test/DirectoryReader/css/test.css +0 -3
- package/test/DirectoryReader1/js/Site.js +0 -1
- package/test/DirectoryReader2/error.js +0 -1
- package/test/DirectoryReader2/example.css +0 -3
- package/test/DirectoryReader2/index.twee +0 -6
- package/test/DirectoryReader3/twee/index.twee +0 -6
- package/test/DirectoryWatcher/example.txt +0 -0
- package/test/DirectoryWatcher/test.txt +0 -0
- package/test/DirectoryWatcher/test1.txt +0 -0
- package/test/HTMLWriter/test5.html +0 -48
- package/test/HTMLWriter/test7.html +0 -48
- package/test/HTMLWriter/test8.html +0 -48
- package/test/HTMLWriter/test9.html +0 -48
- package/test/StoryFormatParser/test.js +0 -2
- package/test/TweeParser/test.twee3 +0 -11
- package/test/TweeWriter/metatest.twee +0 -12
- package/test/TweeWriter/test2.twee +0 -15
- package/test/test.js +0 -736
package/src/TweeParser.js
CHANGED
|
@@ -1,207 +1,161 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @class TweeParser
|
|
5
|
-
* @module TweeParser
|
|
6
|
-
*/
|
|
7
|
-
class TweeParser {
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
tags
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
passages.push(new Passage(name, tags, metadata, text, pid));
|
|
163
|
-
|
|
164
|
-
// Increase pid
|
|
165
|
-
pid++;
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// All formats share StoryTitle
|
|
169
|
-
// Find it and set it
|
|
170
|
-
let pos = passages.find((el) => { return el.name === 'StoryTitle'; });
|
|
171
|
-
|
|
172
|
-
if (pos !== undefined) {
|
|
173
|
-
this.story.name = pos.text;
|
|
174
|
-
// Remove the StoryTitle passage
|
|
175
|
-
passages = passages.filter(p => p.name !== 'StoryTitle');
|
|
176
|
-
} else {
|
|
177
|
-
// There was no StoryTitle passage
|
|
178
|
-
// Set a value of "Unknown"
|
|
179
|
-
this.story.name = 'Unknown';
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Look for StoryData
|
|
183
|
-
pos = passages.find((el) => { return el.name === 'StoryData'; });
|
|
184
|
-
|
|
185
|
-
if (pos !== undefined) {
|
|
186
|
-
// Try to parse the StoryData
|
|
187
|
-
try {
|
|
188
|
-
this.story.metadata = JSON.parse(pos.text);
|
|
189
|
-
} catch (event) {
|
|
190
|
-
// Silently fail with default values
|
|
191
|
-
this._storydataError = true;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Remove the StoryData passage
|
|
195
|
-
passages = passages.filter(p => p.name !== 'StoryData');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (this._storydataError) {
|
|
199
|
-
console.warn('Error with processing StoryData JSON data. It was ignored.');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Set the passages to the internal story
|
|
203
|
-
this.story.passages = passages;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
module.exports = TweeParser;
|
|
1
|
+
import Passage from './Passage.js';
|
|
2
|
+
import Story from './Story.js';
|
|
3
|
+
/**
|
|
4
|
+
* @class TweeParser
|
|
5
|
+
* @module TweeParser
|
|
6
|
+
*/
|
|
7
|
+
export default class TweeParser {
|
|
8
|
+
/**
|
|
9
|
+
* Parse Twee
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
* @static
|
|
13
|
+
* @function parse
|
|
14
|
+
* @param {string} fileContents - File contents to parse
|
|
15
|
+
* @returns {Story} story
|
|
16
|
+
*/
|
|
17
|
+
static parse (fileContents) {
|
|
18
|
+
// Create Story.
|
|
19
|
+
const story = new Story();
|
|
20
|
+
|
|
21
|
+
// Throw error if fileContents is not a string
|
|
22
|
+
if (Object.prototype.toString.call(fileContents) !== '[object String]') {
|
|
23
|
+
throw new Error('Contents not a String');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let adjusted = '';
|
|
27
|
+
|
|
28
|
+
// Check if there are extra content in the files
|
|
29
|
+
// If so, cut it all out for the parser
|
|
30
|
+
if (fileContents[0] !== ':' && fileContents[1] !== ':') {
|
|
31
|
+
adjusted = fileContents.slice(fileContents.indexOf('::'), fileContents.length);
|
|
32
|
+
} else {
|
|
33
|
+
adjusted = fileContents;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Split the file based on the passage sigil (::) proceeded by a newline
|
|
37
|
+
const parsingPassages = adjusted.split('\n::');
|
|
38
|
+
|
|
39
|
+
// Fix the first result
|
|
40
|
+
parsingPassages[0] = parsingPassages[0].slice(2, parsingPassages[0].length);
|
|
41
|
+
|
|
42
|
+
// Set the initial pid
|
|
43
|
+
let pid = 1;
|
|
44
|
+
|
|
45
|
+
// Iterate through the passages
|
|
46
|
+
parsingPassages.forEach((passage) => {
|
|
47
|
+
// Set default values
|
|
48
|
+
let tags = '';
|
|
49
|
+
let metadata = '';
|
|
50
|
+
let text = '';
|
|
51
|
+
let name = '';
|
|
52
|
+
|
|
53
|
+
// Header is everything to the first newline
|
|
54
|
+
let header = passage.slice(0, passage.indexOf('\n'));
|
|
55
|
+
// Text is everything else
|
|
56
|
+
// (Also eat the leading newline character.)
|
|
57
|
+
// (And trim any remaining whitespace.)
|
|
58
|
+
text = passage.substring(header.length + 1, passage.length).trim();
|
|
59
|
+
|
|
60
|
+
// Test for metadata
|
|
61
|
+
const openingCurlyBracketPosition = header.lastIndexOf('{');
|
|
62
|
+
const closingCurlyBracketPosition = header.lastIndexOf('}');
|
|
63
|
+
|
|
64
|
+
if (openingCurlyBracketPosition !== -1 && closingCurlyBracketPosition !== -1) {
|
|
65
|
+
// Save the text metadata
|
|
66
|
+
metadata = header.slice(openingCurlyBracketPosition, closingCurlyBracketPosition + 1);
|
|
67
|
+
|
|
68
|
+
// Remove the metadata from the header
|
|
69
|
+
header = header.substring(0, openingCurlyBracketPosition) + header.substring(closingCurlyBracketPosition + 1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// There was passage metadata
|
|
73
|
+
if (metadata.length > 0) {
|
|
74
|
+
// Try to parse the metadata
|
|
75
|
+
try {
|
|
76
|
+
metadata = JSON.parse(metadata);
|
|
77
|
+
} catch (event) {
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// There wasn't any metadata, so set default
|
|
81
|
+
metadata = {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Test for tags
|
|
85
|
+
const openingSquareBracketPosition = header.lastIndexOf('[');
|
|
86
|
+
const closingSquareBracketPosition = header.lastIndexOf(']');
|
|
87
|
+
|
|
88
|
+
if (openingSquareBracketPosition !== -1 && closingSquareBracketPosition !== -1) {
|
|
89
|
+
tags = header.slice(openingSquareBracketPosition, closingSquareBracketPosition + 1);
|
|
90
|
+
|
|
91
|
+
// Remove the tags from the header
|
|
92
|
+
header = header.substring(0, openingSquareBracketPosition) + header.substring(closingSquareBracketPosition + 1);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Parse tags
|
|
96
|
+
if (tags.length > 0) {
|
|
97
|
+
// Eat the opening and closing square brackets
|
|
98
|
+
tags = tags.substring(1, tags.length - 1);
|
|
99
|
+
|
|
100
|
+
// Set empty default
|
|
101
|
+
let tagsArray = [];
|
|
102
|
+
|
|
103
|
+
// Test if tags is not single, empty string
|
|
104
|
+
if (!(tags === '')) {
|
|
105
|
+
tagsArray = tags.split(' ');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// There are multiple tags
|
|
109
|
+
if (tagsArray.length > 1) {
|
|
110
|
+
// Create future array
|
|
111
|
+
const futureTagArray = [];
|
|
112
|
+
|
|
113
|
+
// Move through entries
|
|
114
|
+
// Add a trimmed version into future array
|
|
115
|
+
tagsArray.forEach((tag) => { futureTagArray.push(tag.trim()); });
|
|
116
|
+
|
|
117
|
+
// Set the tags back to the future array
|
|
118
|
+
tags = futureTagArray;
|
|
119
|
+
} else if (tagsArray.length === 1) {
|
|
120
|
+
// There was only one tag
|
|
121
|
+
// Store it
|
|
122
|
+
const temp = tags;
|
|
123
|
+
|
|
124
|
+
// Switch tags over to an array
|
|
125
|
+
tags = [];
|
|
126
|
+
// Push the single entry
|
|
127
|
+
tags.push(temp);
|
|
128
|
+
} else {
|
|
129
|
+
// Make sure tags is set to empty array if no tags were found
|
|
130
|
+
tags = [];
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
// There were no tags, so set it to an empty array;
|
|
134
|
+
tags = [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Filter out any empty string tags
|
|
138
|
+
tags = tags.filter(tag => tag !== '');
|
|
139
|
+
|
|
140
|
+
// Trim any remaining whitespace
|
|
141
|
+
header = header.trim();
|
|
142
|
+
|
|
143
|
+
// Check if there is a name left
|
|
144
|
+
if (header.length > 0) {
|
|
145
|
+
name = header;
|
|
146
|
+
} else {
|
|
147
|
+
// No name left. Something went wrong. Blame user.
|
|
148
|
+
throw new Error('Malformed passage header!');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// addPassage() method does all the work
|
|
152
|
+
story.addPassage(new Passage(name, text, tags, metadata, pid));
|
|
153
|
+
|
|
154
|
+
// Increase pid
|
|
155
|
+
pid++;
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Return Story.
|
|
159
|
+
return story;
|
|
160
|
+
}
|
|
161
|
+
}
|
package/src/TweeWriter.js
CHANGED
|
@@ -1,96 +1,98 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @external Story
|
|
3
|
-
* @see Story.js
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class TweeWriter
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @external Story
|
|
3
|
+
* @see Story.js
|
|
4
|
+
* @external Passage
|
|
5
|
+
* @see Passage.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import Story from './Story.js';
|
|
10
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @class TweeWriter
|
|
14
|
+
* @module TweeWriter
|
|
15
|
+
*/
|
|
16
|
+
export default class TweeWriter {
|
|
17
|
+
/**
|
|
18
|
+
* Write to a file using a Story object
|
|
19
|
+
*
|
|
20
|
+
* @static
|
|
21
|
+
* @param {Story} story - Story format to write
|
|
22
|
+
* @param {string} file - File to write to
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
static write (story, file) {
|
|
26
|
+
if (!(story instanceof Story)) {
|
|
27
|
+
throw new Error('Not a Story object!');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Write the StoryData first.
|
|
31
|
+
let outputContents = ':: StoryData\n';
|
|
32
|
+
|
|
33
|
+
// Create default object.
|
|
34
|
+
const metadata = {};
|
|
35
|
+
|
|
36
|
+
// Is there an IFID?
|
|
37
|
+
if (story.IFID === '') {
|
|
38
|
+
// Generate a new IFID for this work.
|
|
39
|
+
// Twine 2 uses v4 (random) UUIDs, using only capital letters.
|
|
40
|
+
metadata.ifid = uuidv4().toUpperCase();
|
|
41
|
+
} else {
|
|
42
|
+
// Use existing (non-default) value.
|
|
43
|
+
metadata.ifid = story.IFID;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Is there a format?
|
|
47
|
+
if (story.format !== '') {
|
|
48
|
+
// Using existing format
|
|
49
|
+
metadata.format = story.format;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Is there a formatVersion?
|
|
53
|
+
if (story.formatVersion !== '') {
|
|
54
|
+
// Using existing format version
|
|
55
|
+
metadata['format-version'] = story.formatVersion;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Is there a zoom?
|
|
59
|
+
if (story.zoom !== 0) {
|
|
60
|
+
// Using existing zoom.
|
|
61
|
+
metadata.zoom = story.zoom;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Is there a start?
|
|
65
|
+
if (story.start !== '') {
|
|
66
|
+
// Using existing start
|
|
67
|
+
metadata.start = story.start;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get number of colors.
|
|
71
|
+
const numberOfColors = Object.keys(story.tagColors).length;
|
|
72
|
+
// Are there any colors?
|
|
73
|
+
if (numberOfColors > 0) {
|
|
74
|
+
// Add a tag-colors property
|
|
75
|
+
metadata['tag-colors'] = story.tagColors;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Write out the story metadata.
|
|
79
|
+
outputContents += `${JSON.stringify(metadata, undefined, 2)}`;
|
|
80
|
+
|
|
81
|
+
// Add two newlines.
|
|
82
|
+
outputContents += '\n\n';
|
|
83
|
+
|
|
84
|
+
// For each passage, append it to the output.
|
|
85
|
+
story.forEach((passage) => {
|
|
86
|
+
// For each passage, append it to the output.
|
|
87
|
+
outputContents += passage.toString();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// Try to write
|
|
92
|
+
fs.writeFileSync(file, outputContents);
|
|
93
|
+
} catch (event) {
|
|
94
|
+
// Throw error
|
|
95
|
+
throw new Error('Error: Cannot write Twee file!');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|