extwee 1.6.0 → 2.0.2

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.
Files changed (151) hide show
  1. package/.eslintrc.json +25 -0
  2. package/.github/workflows/nodejs.yml +24 -25
  3. package/.travis.yml +13 -13
  4. package/CODE_OF_CONDUCT.md +82 -0
  5. package/LICENSE +21 -21
  6. package/README.md +36 -205
  7. package/SECURITY.md +12 -0
  8. package/babel.config.json +22 -0
  9. package/bin/extwee.js +49 -0
  10. package/index.js +31 -26
  11. package/package.json +59 -37
  12. package/src/FileReader.js +33 -36
  13. package/src/HTMLParser.js +343 -206
  14. package/src/HTMLWriter.js +227 -177
  15. package/src/Passage.js +202 -20
  16. package/src/Story.js +461 -148
  17. package/src/StoryFormat.js +300 -41
  18. package/src/StoryFormatParser.js +142 -65
  19. package/src/TweeParser.js +161 -255
  20. package/src/TweeWriter.js +98 -111
  21. package/story-formats/chapbook-1.2.0/format.js +1 -0
  22. package/story-formats/chapbook-1.2.0/logo.svg +1 -0
  23. package/story-formats/harlowe-1.2.4/format.js +1 -0
  24. package/story-formats/harlowe-1.2.4/icon.svg +78 -0
  25. package/story-formats/harlowe-2.1.0/format.js +2 -0
  26. package/story-formats/harlowe-2.1.0/icon.svg +78 -0
  27. package/story-formats/harlowe-3.1.0/format.js +3 -0
  28. package/story-formats/harlowe-3.1.0/icon.svg +78 -0
  29. package/story-formats/paperthin-1.0.0/format.js +1 -0
  30. package/story-formats/paperthin-1.0.0/icon.svg +5 -0
  31. package/story-formats/snowman-1.4.0/format.js +1 -0
  32. package/story-formats/snowman-1.4.0/icon.svg +436 -0
  33. package/story-formats/snowman-2.0.2/format.js +1 -0
  34. package/story-formats/snowman-2.0.2/icon.svg +436 -0
  35. package/story-formats/sugarcube-1.0.35/LICENSE +23 -0
  36. package/story-formats/sugarcube-1.0.35/format.js +1 -0
  37. package/story-formats/sugarcube-1.0.35/icon.svg +56 -0
  38. package/story-formats/sugarcube-2.31.1/LICENSE +22 -0
  39. package/story-formats/sugarcube-2.31.1/format.js +1 -0
  40. package/story-formats/sugarcube-2.31.1/icon.svg +56 -0
  41. package/test/{HTMLWriter/example7.twee → CLI/example6.twee} +16 -16
  42. package/test/CLI/harlowe.js +3 -0
  43. package/test/CLI/input.html +47 -0
  44. package/test/CLI/test.twee +18 -0
  45. package/test/CLI/test2.html +47 -0
  46. package/test/CLI/tweeExample.twee +17 -0
  47. package/test/CLI/twineExample.html +15 -0
  48. package/test/CLI.test.js +30 -0
  49. package/test/FileReader.test.js +14 -0
  50. package/test/HTMLParser/Example1.html +53 -0
  51. package/test/HTMLParser/Tags.html +15 -0
  52. package/test/HTMLParser/lyingStartnode.html +15 -0
  53. package/test/HTMLParser/lyingTagColors.html +48 -0
  54. package/test/HTMLParser/missingCreator.html +11 -0
  55. package/test/HTMLParser/missingCreatorVersion.html +11 -0
  56. package/test/HTMLParser/missingFormat.html +11 -0
  57. package/test/HTMLParser/missingFormatVersion.html +11 -0
  58. package/test/HTMLParser/missingIFID.html +11 -0
  59. package/test/HTMLParser/missingName.html +33 -0
  60. package/test/HTMLParser/missingPID.html +15 -0
  61. package/test/HTMLParser/missingPassageName.html +15 -0
  62. package/test/HTMLParser/missingPassageTags.html +15 -0
  63. package/test/HTMLParser/missingPosition.html +15 -0
  64. package/test/HTMLParser/missingScript.html +14 -0
  65. package/test/HTMLParser/missingSize.html +35 -0
  66. package/test/HTMLParser/missingStartnode.html +11 -0
  67. package/test/HTMLParser/missingStyle.html +14 -0
  68. package/test/HTMLParser/missingZoom.html +11 -0
  69. package/test/HTMLParser/tagColors.html +31 -0
  70. package/test/HTMLParser/twineExample.html +15 -46
  71. package/test/HTMLParser/twineExample2.html +15 -0
  72. package/test/HTMLParser/twineExample3.html +15 -0
  73. package/test/HTMLParser.test.js +177 -0
  74. package/test/HTMLWriter/TestTags.html +42 -0
  75. package/test/HTMLWriter/{test10.html → creator.html} +8 -5
  76. package/test/HTMLWriter/example6.twee +16 -16
  77. package/test/HTMLWriter/{example.twee → missingStoryTitle.twee} +29 -29
  78. package/test/HTMLWriter/test11.html +123 -0
  79. package/test/HTMLWriter/test2.html +15 -12
  80. package/test/HTMLWriter/test3.html +7 -13
  81. package/test/HTMLWriter/test4.html +8 -5
  82. package/test/HTMLWriter/test6.html +7 -5
  83. package/test/HTMLWriter.test.js +289 -0
  84. package/test/Passage.test.js +104 -0
  85. package/test/Roundtrip/Example1.html +64 -0
  86. package/test/Roundtrip/example1.twee +21 -0
  87. package/test/Roundtrip/example2.twee +18 -0
  88. package/test/Roundtrip/harlowe.js +3 -0
  89. package/test/Roundtrip/round.html +50 -0
  90. package/test/Roundtrip.test.js +48 -0
  91. package/test/Story/startmeta.twee +29 -29
  92. package/test/Story/test.twee +25 -25
  93. package/test/Story.test.js +282 -0
  94. package/test/StoryFormat.test.js +152 -0
  95. package/test/StoryFormatParser/example.js +3 -0
  96. package/test/StoryFormatParser/{test2.js → example2.js} +3 -3
  97. package/test/StoryFormatParser/format_doublename.js +1 -0
  98. package/test/StoryFormatParser/harlowe.js +2 -2
  99. package/test/StoryFormatParser/missingAuthor.js +1 -0
  100. package/test/StoryFormatParser/missingDescription.js +1 -0
  101. package/test/StoryFormatParser/missingImage.js +1 -0
  102. package/test/StoryFormatParser/missingLicense.js +1 -0
  103. package/test/StoryFormatParser/missingName.js +1 -0
  104. package/test/StoryFormatParser/missingProofing.js +1 -0
  105. package/test/StoryFormatParser/missingSource.js +1 -0
  106. package/test/StoryFormatParser/missingURL.js +1 -0
  107. package/test/StoryFormatParser/missingVersion.js +1 -0
  108. package/test/StoryFormatParser/versionWrong.js +1 -0
  109. package/test/StoryFormatParser.test.js +91 -0
  110. package/test/TweeParser/emptytags.twee +2 -2
  111. package/test/TweeParser/example.twee +32 -29
  112. package/test/TweeParser/missing.twee +19 -0
  113. package/test/{HTMLWriter/example5.twee → TweeParser/multipleScriptPassages.twee} +19 -13
  114. package/test/{HTMLWriter/example4.twee → TweeParser/multipleStyleTag.twee} +19 -13
  115. package/test/TweeParser/multipletags.twee +10 -2
  116. package/test/TweeParser/noTitle.twee +2 -0
  117. package/test/TweeParser/notes.twee +16 -0
  118. package/test/TweeParser/pasagemetadataerror.twee +2 -2
  119. package/test/{HTMLWriter/example2.twee → TweeParser/scriptPassage.twee} +16 -13
  120. package/test/TweeParser/singletag.twee +13 -2
  121. package/test/TweeParser/startMetadata.twee +14 -0
  122. package/test/TweeParser/storydataerror.twee +25 -25
  123. package/test/{HTMLWriter/example3.twee → TweeParser/stylePassage.twee} +16 -13
  124. package/test/TweeParser/test.twee +25 -25
  125. package/test/TweeParser.test.js +79 -0
  126. package/test/TweeWriter/test1.twee +14 -9
  127. package/test/TweeWriter/test3.twee +7 -10
  128. package/test/TweeWriter/test4.twee +14 -0
  129. package/test/TweeWriter/test5.twee +20 -0
  130. package/test/TweeWriter.test.js +85 -0
  131. package/main.js +0 -106
  132. package/src/DirectoryReader.js +0 -107
  133. package/src/DirectoryWatcher.js +0 -92
  134. package/test/DirectoryReader/css/test.css +0 -3
  135. package/test/DirectoryReader1/js/Site.js +0 -1
  136. package/test/DirectoryReader2/error.js +0 -1
  137. package/test/DirectoryReader2/example.css +0 -3
  138. package/test/DirectoryReader2/index.twee +0 -6
  139. package/test/DirectoryReader3/twee/index.twee +0 -6
  140. package/test/DirectoryWatcher/example.txt +0 -0
  141. package/test/DirectoryWatcher/test.txt +0 -0
  142. package/test/DirectoryWatcher/test1.txt +0 -0
  143. package/test/HTMLWriter/test5.html +0 -48
  144. package/test/HTMLWriter/test7.html +0 -48
  145. package/test/HTMLWriter/test8.html +0 -48
  146. package/test/HTMLWriter/test9.html +0 -48
  147. package/test/StoryFormatParser/test.js +0 -2
  148. package/test/TweeParser/test.twee3 +0 -11
  149. package/test/TweeWriter/metatest.twee +0 -12
  150. package/test/TweeWriter/test2.twee +0 -15
  151. package/test/test.js +0 -722
package/src/TweeParser.js CHANGED
@@ -1,255 +1,161 @@
1
- const Passage = require('./Passage.js');
2
- const Story = require('./Story.js');
3
- /**
4
- * @class TweeParser
5
- * @module TweeParser
6
- */
7
- class TweeParser {
8
- /**
9
- * @method TweeParser
10
- * @constructor
11
- */
12
- constructor (content) {
13
- this.story = new Story();
14
- this._passageMetadatError = false;
15
- this._storydataError = false;
16
-
17
- this.parse(content);
18
- }
19
-
20
- /**
21
- * @method parse
22
- * @returns Array or Null on error
23
- */
24
- parse(fileContents) {
25
-
26
- let passages = [];
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
-
32
- let firstPassagePos = fileContents.indexOf('::');
33
- fileContents = fileContents.slice(firstPassagePos, fileContents.length);
34
-
35
- }
36
-
37
- // Split the file based on the passage sigil (::) preceeded by a newline
38
- let parsingPassages = fileContents.split('\n::');
39
-
40
- // Check if any passages exist
41
- if(parsingPassages == 0) {
42
-
43
- throw new Error("No passages were found!");
44
-
45
- }
46
-
47
- // Fix the first result
48
- parsingPassages[0] = parsingPassages[0].slice(2, parsingPassages[0].length);
49
-
50
- // Set the initial pid
51
- let pid = 0;
52
-
53
- // Iterate through the passages
54
- for(let passage of parsingPassages) {
55
-
56
- // Set default values
57
- let tags = "";
58
- let position = "";
59
- let metadata = "";
60
- let text = "";
61
- let name = "";
62
-
63
- // Header is everything to the first newline
64
- let header = passage.slice(0, passage.indexOf('\n'));
65
- // Text is everything else
66
- // (Also eat the leading newline character.)
67
- // (And trim any remaining whitespace.)
68
- text = passage.substring(header.length+1, passage.length).trim();
69
-
70
- // Test for metadata
71
- let openingCurlyBracketPosition = header.lastIndexOf('{');
72
- let closingCurlyBracketPosition = header.lastIndexOf('}');
73
-
74
- if(openingCurlyBracketPosition != -1 && closingCurlyBracketPosition != -1) {
75
-
76
- // Save the text metadata
77
- metadata = header.slice(openingCurlyBracketPosition, closingCurlyBracketPosition+1);
78
-
79
- // Remove the metadata from the header
80
- header = header.substring(0, openingCurlyBracketPosition) + header.substring(closingCurlyBracketPosition+1);
81
- }
82
-
83
- // There was passage metadata
84
- if(metadata.length > 0) {
85
-
86
- // Try to parse the metadata
87
- try {
88
-
89
- metadata = JSON.parse(metadata);
90
-
91
- } catch(event) {
92
-
93
- this._passageMetadatError = true;
94
-
95
- }
96
-
97
- }
98
-
99
- // Test for tags
100
- let openingSquareBracketPosition = header.lastIndexOf('[');
101
- let closingSquareBracketPosition = header.lastIndexOf(']');
102
-
103
- if(openingSquareBracketPosition != -1 && closingSquareBracketPosition != -1) {
104
-
105
- tags = header.slice(openingSquareBracketPosition, closingSquareBracketPosition+1);
106
-
107
- // Remove the tags from the header
108
- header = header.substring(0, openingSquareBracketPosition) + header.substring(closingSquareBracketPosition+1);
109
- }
110
-
111
- // Parse tags
112
- if(tags.length > 0) {
113
-
114
- // Eat the opening and closing square brackets
115
- tags = tags.substring(1, tags.length-1);
116
-
117
- // Set empty default
118
- let tagsArray = [];
119
-
120
- // Test if tags is not single, empty string
121
- if( !(tags == "") ) {
122
-
123
- tagsArray = tags.split(" ");
124
- }
125
-
126
- // There are multiple tags
127
- if(tagsArray.length > 1) {
128
-
129
- // Create future array
130
- let futureTagArray = [];
131
-
132
- // Move through entries
133
- for(let tag in tagsArray) {
134
-
135
- // Add a trimmed verion into future array
136
- futureTagArray.push(tagsArray[tag].trim());
137
-
138
- }
139
-
140
- // Set the tags back to the future array
141
- tags = futureTagArray;
142
-
143
- } else if (tagsArray.length == 1) {
144
-
145
- // There was only one tag
146
- // Store it
147
- let temp = tags;
148
-
149
- // Switch tags over to an array
150
- tags = new Array();
151
- // Push the single entry
152
- tags.push(temp);
153
-
154
- } else {
155
-
156
- // Make sure tags is set to empty array if no tags were found
157
- tags = [];
158
-
159
- }
160
-
161
- } else {
162
- // There were no tags, so set it to an empty array;
163
- tags = [];
164
- }
165
-
166
- // Filter out any empty string tags
167
- tags = tags.filter(tag => tag != "");
168
-
169
- // Trim any remaining whitespace
170
- header = header.trim();
171
-
172
- // Check if there is a name left
173
- if(header.length > 0) {
174
-
175
- name = header;
176
-
177
- } else {
178
-
179
- // No name left. Something went wrong. Blame user.
180
- throw new Error("Malformed passage header!");
181
-
182
- }
183
-
184
- if(this._passageMetadatError) {
185
-
186
- console.warn('Error parsing metadata for "' + name + '". It was ignored.');
187
-
188
- }
189
-
190
- // Add the new Passage to the internal array
191
- passages.push(new Passage(name, tags, metadata, text, pid));
192
-
193
- // Increase pid
194
- pid++;
195
-
196
- }
197
-
198
- // All formats share StoryTitle
199
- // Find it and set it
200
- let pos = passages.find((el) => {
201
- return el.name == "StoryTitle";
202
- });
203
-
204
- if(pos != undefined) {
205
-
206
- this.story.name = pos.text;
207
- // Remove the StoryTitle passage
208
- passages = passages.filter(p => p.name !== "StoryTitle");
209
-
210
- } else {
211
-
212
- // There was no StoryTitle passage
213
- // Set a value of "Unknown"
214
- this.story.name = "Unknown";
215
-
216
- }
217
-
218
- // Look for StoryData
219
- pos = passages.find((el) => {
220
- return el.name == "StoryData";
221
- });
222
-
223
- if(pos != undefined ) {
224
-
225
- // Try to parse the StoryData
226
- try {
227
-
228
- this.story.metadata = JSON.parse(pos.text);
229
-
230
- } catch(event) {
231
-
232
- // Silently fail with default values
233
- this._storydataError = true;
234
-
235
- }
236
-
237
- // Remove the StoryData passage
238
- passages = passages.filter(p => p.name !== "StoryData");
239
-
240
- }
241
-
242
- if(this._storydataError) {
243
-
244
- console.warn("Error with processing StoryData JSON data. It was ignored.");
245
-
246
- }
247
-
248
- // Set the passages to the internal story
249
- this.story.passages = passages;
250
-
251
- }
252
-
253
- }
254
-
255
- 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,111 +1,98 @@
1
- const fs = require("fs");
2
- const path = require('path');
3
- const Story = require('./Story.js');
4
-
5
- /**
6
- * @class TweeWriter
7
- * @module TweeWriter
8
- */
9
- class TweeWriter {
10
- /**
11
- * @method TweeWriter
12
- * @constructor
13
- */
14
- constructor (story, file) {
15
-
16
- if( !(story instanceof Story) ) {
17
- throw new Error("Not a Story object!");
18
- }
19
-
20
- this.writeFile(file, story);
21
- }
22
-
23
- writeFile(file, story) {
24
-
25
- // Write StoryTitle first
26
- let outputContents = ":: StoryTitle\n" + story.name + "\n\n";
27
-
28
- // Write the StoryData second
29
- outputContents += ":: StoryData\n"
30
-
31
- // Borrowed from Underscore
32
- // https://github.com/jashkenas/underscore/blob/master/underscore.js#L1319-L1323
33
- let isObject = function(obj) {
34
- var type = typeof obj;
35
- return type === 'function' || type === 'object' && !!obj;
36
- };
37
-
38
- // Test if story.metadata is an object or not
39
- if(isObject(story.metadata) ) {
40
-
41
- // Write any metadata in pretty format
42
- outputContents += " " + JSON.stringify(story.metadata, undefined, 2);
43
-
44
- } else {
45
-
46
- // If, for whatever reason, story.metadata is not an object, throw error.
47
- throw new Error("Story Metadata MUST be an object!");
48
-
49
- }
50
-
51
- // Add two newlines
52
- outputContents += "\n\n";
53
-
54
- // Are there any passages?
55
- if(story.passages.length > 0) {
56
-
57
- // Build the contents
58
- for(let passage in story.passages) {
59
-
60
- // Write the name
61
- outputContents += ":: " + story.passages[passage].name;
62
-
63
- // Test if it has any tags
64
- if(story.passages[passage].tags.length > 0) {
65
-
66
- outputContents += " [";
67
-
68
- for(let tag of story.passages[passage].tags) {
69
-
70
- outputContents += " " + tag;
71
-
72
- }
73
-
74
- outputContents += "]";
75
-
76
- }
77
-
78
- // Write out any passage metadata
79
- outputContents += JSON.stringify(story.passages[passage].metadata);
80
-
81
- // Add the text and two newlines
82
- outputContents += "\n" + story.passages[passage].text + "\n\n";
83
-
84
- }
85
-
86
- } else {
87
-
88
- // Create empty Start passage
89
- outputContents += ":: Start\n";
90
-
91
- }
92
-
93
- try {
94
-
95
- // Try to write
96
- fs.writeFileSync(file, outputContents);
97
-
98
- } catch(event) {
99
-
100
- // Throw error
101
- throw new Error("Error: Cannot write Twee file!");
102
-
103
- }
104
-
105
- // Writing was successful
106
- console.info("Created " + fs.realpathSync(file) );
107
-
108
- }
109
- }
110
-
111
- module.exports = TweeWriter;
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
+ }