extwee 2.0.6 → 2.2.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.
Files changed (216) hide show
  1. package/.eslintrc.json +25 -25
  2. package/.github/FUNDING.yml +3 -0
  3. package/.github/dependabot.yml +11 -0
  4. package/.github/workflows/nodejs.yml +25 -24
  5. package/.travis.yml +13 -13
  6. package/CODE_OF_CONDUCT.md +82 -82
  7. package/LICENSE +21 -21
  8. package/README.md +173 -36
  9. package/SECURITY.md +12 -12
  10. package/babel.config.json +18 -22
  11. package/build/extwee +0 -0
  12. package/build/extwee.exe +0 -0
  13. package/build/extwee.web.min.js +2 -0
  14. package/build/extwee.web.min.js.LICENSE.txt +1 -0
  15. package/docs/.nojekyll +0 -0
  16. package/docs/README.md +167 -0
  17. package/docs/_sidebar.md +19 -0
  18. package/docs/examples/dynamicPassages.md +28 -0
  19. package/docs/examples/jsonToTwee.md +23 -0
  20. package/docs/examples/twsToTwee.md +25 -0
  21. package/docs/formats/json.md +17 -0
  22. package/docs/formats/twee.md +13 -0
  23. package/docs/formats/twine1HTML.md +13 -0
  24. package/docs/formats/twine2ArchiveHTML.md +13 -0
  25. package/docs/formats/twine2HTML.md +13 -0
  26. package/docs/formats/tws.md +9 -0
  27. package/docs/index.html +26 -0
  28. package/docs/install/binaries.md +9 -0
  29. package/docs/install/npm.md +20 -0
  30. package/docs/install/npx.md +9 -0
  31. package/docs/objects/passage.md +47 -0
  32. package/docs/objects/story.md +70 -0
  33. package/docs/objects/storyformat.md +27 -0
  34. package/index.html +22 -0
  35. package/index.js +29 -31
  36. package/package.json +65 -58
  37. package/src/JSON/parse.js +128 -0
  38. package/src/Passage.js +298 -202
  39. package/src/Story.js +650 -523
  40. package/src/StoryFormat/parse.js +134 -0
  41. package/src/StoryFormat.js +259 -300
  42. package/src/TWS/parse.js +86 -0
  43. package/src/Twee/parse.js +157 -0
  44. package/src/Twine1HTML/compile.js +58 -0
  45. package/src/Twine1HTML/parse.js +134 -0
  46. package/src/Twine2ArchiveHTML/compile.js +36 -0
  47. package/src/Twine2ArchiveHTML/parse.js +49 -0
  48. package/src/Twine2HTML/compile.js +35 -0
  49. package/src/Twine2HTML/parse.js +348 -0
  50. package/src/extwee.js +206 -0
  51. package/test/CLI/CLI.test.js +49 -0
  52. package/test/CLI/files/example.json +1 -0
  53. package/test/CLI/files/example6.twee +22 -0
  54. package/test/{Roundtrip → CLI/files}/harlowe.js +2 -2
  55. package/test/CLI/{input.html → files/input.html} +47 -47
  56. package/test/CLI/files/output/test.twee +0 -0
  57. package/test/CLI/{tweeExample.twee → files/tweeExample.twee} +17 -17
  58. package/test/CLI/files/twine1/LICENSE.txt +32 -0
  59. package/test/CLI/files/twine1/code.js +5 -0
  60. package/test/CLI/files/twine1/engine.js +43 -0
  61. package/test/CLI/files/twine1/header.html +325 -0
  62. package/test/CLI/files/twine1Test.html +371 -0
  63. package/test/CLI/{twineExample.html → files/twineExample.html} +16 -15
  64. package/test/JSON/JSON.Parse.test.js +316 -0
  65. package/test/Passage.test.js +175 -104
  66. package/test/Roundtrip/{Example1.html → Files/Example1.html} +63 -63
  67. package/test/Roundtrip/Files/LICENSE +19 -0
  68. package/test/Roundtrip/Files/example1.twee +10 -0
  69. package/test/Roundtrip/{example2.twee → Files/example2.twee} +27 -18
  70. package/test/Roundtrip/{example4.twee → Files/example4.twee} +27 -27
  71. package/test/{StoryFormatParser → Roundtrip/Files}/harlowe.js +2 -2
  72. package/test/Roundtrip/{round.html → Files/round.html} +6 -4
  73. package/test/Roundtrip/Roundtrip.test.js +54 -0
  74. package/test/Story.test.js +638 -423
  75. package/test/StoryFormat/StoryFormat.Parse.test.js +91 -0
  76. package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example.js +3 -3
  77. package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/example2.js +3 -3
  78. package/test/{CLI → StoryFormat/StoryFormatParser}/harlowe.js +2 -2
  79. package/test/StoryFormat.test.js +152 -152
  80. package/test/TWS/Parse.test.js +78 -0
  81. package/test/TWS/TWSParser/Example1.tws +150 -0
  82. package/test/TWS/TWSParser/Example5.tws +414 -0
  83. package/test/TWS/TWSParser/noscale.tws +0 -0
  84. package/test/TWS/TWSParser/nostory.tws +0 -0
  85. package/test/Twee/Twee.Parse.test.js +76 -0
  86. package/test/{TweeParser → Twee/TweeParser}/emptytags.twee +2 -2
  87. package/test/{TweeParser → Twee/TweeParser}/example.twee +32 -32
  88. package/test/{TweeParser → Twee/TweeParser}/missing.twee +19 -19
  89. package/test/{TweeParser → Twee/TweeParser}/multipleScriptPassages.twee +19 -19
  90. package/test/{TweeParser → Twee/TweeParser}/multipleStyleTag.twee +19 -19
  91. package/test/{TweeParser → Twee/TweeParser}/multipletags.twee +10 -10
  92. package/test/{TweeParser → Twee/TweeParser}/noTitle.twee +2 -2
  93. package/test/{TweeParser → Twee/TweeParser}/notes.twee +16 -16
  94. package/test/{TweeParser → Twee/TweeParser}/pasagemetadataerror.twee +2 -2
  95. package/test/{TweeParser → Twee/TweeParser}/scriptPassage.twee +16 -16
  96. package/test/{TweeParser → Twee/TweeParser}/singletag.twee +13 -13
  97. package/test/{TweeParser → Twee/TweeParser}/startMetadata.twee +14 -14
  98. package/test/{TweeParser → Twee/TweeParser}/storydataerror.twee +25 -25
  99. package/test/{TweeParser → Twee/TweeParser}/stylePassage.twee +16 -16
  100. package/test/{Story → Twee/TweeParser}/test.twee +25 -25
  101. package/test/Twine1HTML/Twine1HTML.Compile.test.js +180 -0
  102. package/test/Twine1HTML/Twine1HTML.Parse.test.js +183 -0
  103. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/LICENSE +674 -0
  104. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/engine.js +43 -0
  105. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/jquery.js +4 -0
  106. package/test/Twine1HTML/Twine1HTMLCompiler/Twine1/modernizr.js +4 -0
  107. package/test/Twine1HTML/Twine1HTMLCompiler/engineTest.html +1 -0
  108. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/LICENSE +32 -0
  109. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/code.js +4 -0
  110. package/test/Twine1HTML/Twine1HTMLCompiler/jonah-1.4.2/header.html +327 -0
  111. package/test/Twine1HTML/Twine1HTMLCompiler/test.html +0 -0
  112. package/test/Twine1HTML/Twine1HTMLCompiler/test1.html +6 -0
  113. package/test/Twine1HTML/Twine1HTMLCompiler/test2.html +6 -0
  114. package/test/Twine1HTML/Twine1HTMLCompiler/test3.html +43 -0
  115. package/test/Twine1HTML/Twine1HTMLCompiler/test4.html +372 -0
  116. package/test/Twine1HTML/Twine1HTMLCompiler/test5.html +372 -0
  117. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Compile.test.js +35 -0
  118. package/test/Twine2ArchiveHTML/Twine2ArchiveHTML.Parse.test.js +25 -0
  119. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLCompiler/test1.html +6 -0
  120. package/test/Twine2ArchiveHTML/Twine2ArchiveHTMLParser/test1.html +3 -0
  121. package/test/Twine2HTML/Twine2HTML.Compile.test.js +224 -0
  122. package/test/Twine2HTML/Twine2HTML.Parse.test.js +172 -0
  123. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/creator.html +4 -4
  124. package/test/{CLI → Twine2HTML/Twine2HTMLCompiler}/example6.twee +15 -15
  125. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/missingStoryTitle.twee +29 -29
  126. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test2.html +10 -8
  127. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test3.html +1 -1
  128. package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test4.html +4 -4
  129. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Example1.html +52 -52
  130. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/Tags.html +15 -15
  131. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingStartnode.html +15 -15
  132. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/lyingTagColors.html +48 -48
  133. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreator.html +11 -11
  134. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingCreatorVersion.html +11 -11
  135. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormat.html +11 -11
  136. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingFormatVersion.html +11 -11
  137. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingIFID.html +11 -11
  138. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingName.html +33 -33
  139. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPID.html +15 -15
  140. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageName.html +15 -15
  141. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPassageTags.html +15 -15
  142. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingPosition.html +15 -15
  143. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingScript.html +14 -14
  144. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingSize.html +35 -35
  145. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStartnode.html +11 -11
  146. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingStyle.html +14 -14
  147. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/missingZoom.html +11 -11
  148. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample.html +23 -23
  149. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample2.html +15 -15
  150. package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/twineExample3.html +15 -15
  151. package/web-index.js +29 -0
  152. package/webpack.config.js +12 -0
  153. package/bin/extwee.js +0 -47
  154. package/src/FileReader.js +0 -33
  155. package/src/HTMLParser.js +0 -345
  156. package/src/HTMLWriter.js +0 -231
  157. package/src/StoryFormatParser.js +0 -142
  158. package/src/TweeParser.js +0 -161
  159. package/src/TweeWriter.js +0 -121
  160. package/story-formats/chapbook-1.2.0/format.js +0 -1
  161. package/story-formats/chapbook-1.2.0/logo.svg +0 -1
  162. package/story-formats/harlowe-1.2.4/format.js +0 -1
  163. package/story-formats/harlowe-1.2.4/icon.svg +0 -78
  164. package/story-formats/harlowe-2.1.0/format.js +0 -2
  165. package/story-formats/harlowe-2.1.0/icon.svg +0 -78
  166. package/story-formats/harlowe-3.1.0/format.js +0 -3
  167. package/story-formats/harlowe-3.1.0/icon.svg +0 -78
  168. package/story-formats/paperthin-1.0.0/format.js +0 -1
  169. package/story-formats/paperthin-1.0.0/icon.svg +0 -5
  170. package/story-formats/snowman-1.4.0/format.js +0 -1
  171. package/story-formats/snowman-1.4.0/icon.svg +0 -436
  172. package/story-formats/snowman-2.0.2/format.js +0 -1
  173. package/story-formats/snowman-2.0.2/icon.svg +0 -436
  174. package/story-formats/sugarcube-1.0.35/LICENSE +0 -23
  175. package/story-formats/sugarcube-1.0.35/format.js +0 -1
  176. package/story-formats/sugarcube-1.0.35/icon.svg +0 -56
  177. package/story-formats/sugarcube-2.31.1/LICENSE +0 -22
  178. package/story-formats/sugarcube-2.31.1/format.js +0 -1
  179. package/story-formats/sugarcube-2.31.1/icon.svg +0 -56
  180. package/test/CLI/test2.html +0 -45
  181. package/test/CLI.test.js +0 -30
  182. package/test/FileReader/t1.txt +0 -1
  183. package/test/FileReader.test.js +0 -14
  184. package/test/HTMLParser.test.js +0 -177
  185. package/test/HTMLWriter/example6.twee +0 -16
  186. package/test/HTMLWriter.test.js +0 -287
  187. package/test/Roundtrip/example1.twee +0 -21
  188. package/test/Roundtrip.test.js +0 -48
  189. package/test/Story/startmeta.twee +0 -29
  190. package/test/StoryFormatParser.test.js +0 -91
  191. package/test/TweeParser/test.twee +0 -25
  192. package/test/TweeParser.test.js +0 -79
  193. package/test/TweeWriter/test1.twee +0 -18
  194. package/test/TweeWriter/test3.twee +0 -12
  195. package/test/TweeWriter/test4.twee +0 -14
  196. package/test/TweeWriter/test5.twee +0 -20
  197. package/test/TweeWriter/test6.twee +0 -15
  198. package/test/TweeWriter/test7.twee +0 -15
  199. package/test/TweeWriter.test.js +0 -107
  200. /package/test/CLI/{test.twee → files/test.twee} +0 -0
  201. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format.js +0 -0
  202. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/format_doublename.js +0 -0
  203. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingAuthor.js +0 -0
  204. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingDescription.js +0 -0
  205. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingImage.js +0 -0
  206. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingLicense.js +0 -0
  207. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingName.js +0 -0
  208. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingProofing.js +0 -0
  209. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingSource.js +0 -0
  210. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingURL.js +0 -0
  211. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/missingVersion.js +0 -0
  212. /package/test/{StoryFormatParser → StoryFormat/StoryFormatParser}/versionWrong.js +0 -0
  213. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/TestTags.html +0 -0
  214. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test11.html +0 -0
  215. /package/test/{HTMLWriter → Twine2HTML/Twine2HTMLCompiler}/test6.html +0 -0
  216. /package/test/{HTMLParser → Twine2HTML/Twine2HTMLParser}/tagColors.html +0 -0
package/src/HTMLWriter.js DELETED
@@ -1,231 +0,0 @@
1
- /**
2
- * @external Story
3
- * @see Story.js
4
- * @external StoryFormat
5
- * @see StoryFormat.js
6
- */
7
-
8
- import fs from 'fs';
9
- import Story from './Story.js';
10
- import StoryFormat from './StoryFormat.js';
11
- import { v4 as uuidv4 } from 'uuid';
12
-
13
- /**
14
- * @class HTMLWriter
15
- * @module HTMLWriter
16
- */
17
- export default class HTMLWriter {
18
- /**
19
- * Write story to file using story format and adding any CSS and JS
20
- *
21
- * @public
22
- * @static
23
- * @function writeFile
24
- * @param {string} file - File to write
25
- * @param {Story} story - Story object to write
26
- * @param {StoryFormat} storyFormat - StoryFormat to write
27
- */
28
- static write (file, story, storyFormat) {
29
- if (!(story instanceof Story)) {
30
- throw new Error('Error: story must be a Story object!');
31
- }
32
-
33
- if (!(storyFormat instanceof StoryFormat)) {
34
- throw new Error('storyFormat must be a StoryFormat object!');
35
- }
36
-
37
- let outputContents = '';
38
- let storyData = '';
39
-
40
- // Look for StoryTitle
41
- const storyTitle = story.getPassageByName('StoryTitle');
42
-
43
- // Does the passage exist?
44
- if (storyTitle != null) {
45
- // Always overwrite any existing name with StoryTitle (per spec)
46
- story.name = storyTitle.text;
47
-
48
- // Use story.name for name.
49
- storyData += `<tw-storydata name="${story.name}"`;
50
- } else {
51
- throw new Error("'name' is required attribute. (Add StoryTitle to story.)");
52
- }
53
-
54
- // Does start exist?
55
- if (story.start !== '') {
56
- // Try to get starting passage
57
- const startingPassage = story.getPassageByName(story.start);
58
- // Does it exist currently?
59
- if (startingPassage !== null) {
60
- // Add the starting passage
61
- storyData += ` startnode="${startingPassage.pid}"`;
62
- } else {
63
- // Throw error if no starting passage exists
64
- throw new Error('Starting passage not found');
65
- }
66
- } else {
67
- // Throw error if no starting passage exists
68
- throw new Error('No starting passage found!');
69
- }
70
-
71
- // Defaults to 'extwee' if missing.
72
- storyData += ` creator="${story.creator}"`;
73
-
74
- // Default to extwee version.
75
- storyData += ` creator-version="${story.creatorVersion}"`;
76
-
77
- // Check if IFID exists.
78
- if (story.IFID !== '') {
79
- // Write the existing IFID
80
- storyData += ` ifid="${story.IFID}"`;
81
- } else {
82
- // Generate a new IFID
83
- // Twine 2 uses v4 (random) UUIDs, using only capital letters
84
- storyData += ` ifid="${uuidv4().toUpperCase()}"`;
85
- }
86
-
87
- // Write existing or default value.
88
- storyData += ` zoom="${story.zoom}"`;
89
-
90
- // Write existing or default value.
91
- storyData += ` format="${storyFormat.name}"`;
92
-
93
- // Write existing or default value.
94
- storyData += ` format-version="${storyFormat.version}"`;
95
-
96
- // Add the default.
97
- storyData += ' options hidden>\n';
98
-
99
- // Start the STYLE.
100
- storyData += '\t<style role="stylesheet" id="twine-user-stylesheet" type="text/twine-css">';
101
-
102
- // Get stylesheet passages
103
- const stylesheetPassages = story.getPassagesByTag('stylesheet');
104
-
105
- // Concatenate passages
106
- stylesheetPassages.forEach((passage) => {
107
- // Add text of passages
108
- storyData += passage.text;
109
- // Remove from story
110
- story.removePassageByName(passage.name);
111
- });
112
-
113
- // Close the STYLE
114
- storyData += '</style>\n';
115
-
116
- // Start the SCRIPT
117
- storyData += '\t<script role="script" id="twine-user-script" type="text/twine-javascript">';
118
-
119
- // Get stylesheet passages
120
- const scriptPassages = story.getPassagesByTag('script');
121
-
122
- // Concatenate passages
123
- scriptPassages.forEach((passage) => {
124
- // Add text of passages
125
- storyData += passage.text;
126
- // Remove from story
127
- story.removePassageByName(passage.name);
128
- });
129
-
130
- // Close SCRIPT
131
- storyData += '</script>\n';
132
-
133
- // Build the passages
134
- story.forEach((passage) => {
135
- // Start the passage element
136
- storyData += '\t<tw-passagedata';
137
-
138
- /**
139
- * pid: (string) Required.
140
- * The Passage ID (PID).
141
- */
142
- storyData += ` pid="${passage.pid}"`;
143
-
144
- /**
145
- * name: (string) Required.
146
- * The name of the passage.
147
- */
148
- storyData += ` name="${passage.name}"`;
149
-
150
- /**
151
- * tags: (string) Optional.
152
- * Any tags for the passage separated by spaces.
153
- */
154
- if (passage.tags.length > 1) {
155
- storyData += ` tags="${passage.tags.join(' ')}" `;
156
- } else if (passage.tags.length === 1) {
157
- storyData += ` tags="${passage.tags[0]}" `;
158
- }
159
-
160
- /**
161
- * position: (string) Optional.
162
- * Comma-separated X and Y position of the upper-left of the passage
163
- * when viewed within the Twine 2 editor.
164
- */
165
- if (Object.prototype.hasOwnProperty.call(passage.metadata, 'position')) {
166
- storyData += ` position="${passage.metadata.position}" `;
167
- }
168
-
169
- /**
170
- * size: (string) Optional.
171
- * Comma-separated width and height of the passage
172
- * when viewed within the Twine 2 editor.
173
- */
174
- if (Object.prototype.hasOwnProperty.call(passage.metadata, 'size')) {
175
- storyData += `size="${passage.metadata.size}" `;
176
- }
177
-
178
- storyData += `>${HTMLWriter.escape(passage.text)}</tw-passagedata>\n`;
179
- });
180
-
181
- storyData += '</tw-storydata>';
182
-
183
- // Replace the story name in the source file
184
- storyFormat.source = storyFormat.source.replaceAll(/{{STORY_NAME}}/gm, story.name);
185
-
186
- // Replace the story data
187
- storyFormat.source = storyFormat.source.replaceAll(/{{STORY_DATA}}/gm, storyData);
188
-
189
- // Combine everything together.
190
- outputContents += storyFormat.source;
191
-
192
- try {
193
- // Try to write.
194
- fs.writeFileSync(file, outputContents);
195
- } catch (event) {
196
- // Throw error
197
- throw new Error('Error: Cannot write HTML file!');
198
- }
199
- }
200
-
201
- /**
202
- * Escape HTML characters
203
- *
204
- * @public
205
- * @static
206
- * @function escape
207
- * @param {string} text - Text to escape
208
- * @returns {string} Escaped text
209
- */
210
- static escape (text) {
211
- // Throw error if text is not a string
212
- if (Object.prototype.toString.call(text) !== '[object String]') {
213
- throw new Error('Text argument is not a String');
214
- }
215
-
216
- const rules = [
217
- ['&', '&amp;'],
218
- ['<', '&lt;'],
219
- ['>', '&gt;'],
220
- ['"', '&quot;'],
221
- ["'", '&#x27;'],
222
- ['`', '&#x60;']
223
- ];
224
-
225
- rules.forEach(([rule, template]) => {
226
- text = text.replaceAll(rule, template);
227
- });
228
-
229
- return text;
230
- }
231
- }
@@ -1,142 +0,0 @@
1
- import StoryFormat from './StoryFormat.js';
2
- import semver from 'semver';
3
- /**
4
- * @class StoryFormatParser
5
- * @module StoryFormatParser
6
- */
7
- export default class StoryFormatParser {
8
- /**
9
- * Parse a Story Format file
10
- *
11
- * @public
12
- * @static
13
- * @memberof StoryFormatParser
14
- * @function parse
15
- * @param {string} contents - Content
16
- * @returns {StoryFormat} StoryFormat object
17
- */
18
- static parse (contents) {
19
- // Harlowe has malformed JSON, so we have to test for it
20
- const harlowePosition = contents.indexOf('harlowe');
21
-
22
- if (harlowePosition !== -1) {
23
- // The 'setup' property is malformed
24
- const setupPosition = contents.lastIndexOf(',"setup": function');
25
- contents = contents.slice(0, setupPosition) + '}';
26
- }
27
-
28
- // Find the start of story format or -1, if not found
29
- const openingCurlyBracketPosition = contents.indexOf('{');
30
- // Find the end of story format or -1, if not found
31
- const closingCurlyBracketPosition = contents.lastIndexOf('}');
32
-
33
- // Look for JSON among the story format
34
- // If either is -1, this is not valid JSON
35
- if (openingCurlyBracketPosition === -1 || closingCurlyBracketPosition === -1) {
36
- // Either start or end curly brackets were now found!
37
- throw new Error('Unable to find Twine2 JSON chunk!');
38
- } else {
39
- // Slice out the JSON based on curly brackets
40
- contents = contents.slice(openingCurlyBracketPosition, closingCurlyBracketPosition + 1);
41
- }
42
-
43
- // Create an object literal
44
- let jsonContent = {};
45
-
46
- try {
47
- jsonContent = JSON.parse(contents);
48
- } catch (event) {
49
- throw new Error('Unable to parse Twine2 JSON chunk!');
50
- }
51
-
52
- /**
53
- * The following keys are found in most or all story formats:
54
- * - name: (string) Optional. Name of the story format.
55
- * (Omitting the name will lead to an Untitled Story Format.)
56
- * - version: (string) Required, and semantic version-style formatting
57
- * (x.y.z, e.g., 1.2.1) of the version is also required.
58
- * - author: (string) Optional.
59
- * - description: (string) Optional.
60
- * - image: (string) Optional. The filename of an image (ideally SVG)
61
- * served from the same directory as the format.js file.
62
- * - url: (string) Optional. The URL of the directory containing the format.js file.
63
- * - license: (string) Optional.
64
- * - proofing: (boolean) Optional (defaults to false). True if the story format
65
- * is a "proofing" format. The distinction is relevant only in the Twine 2 UI.
66
- * - source: (string) Required. Full HTML output of the story format.
67
- * (See: https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md)
68
- */
69
-
70
- // Name is optional, so we have to test for it
71
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'name')) {
72
- // Use the default name
73
- jsonContent.name = 'Untitled Story Format';
74
- }
75
-
76
- // Author is optional, so we have to test for it
77
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'author')) {
78
- // Use the default author
79
- jsonContent.author = '';
80
- }
81
-
82
- // Description is optional, so we have to test for it
83
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'description')) {
84
- // Use the default description
85
- jsonContent.description = '';
86
- }
87
-
88
- // Image is optional, so we have to test for it
89
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'image')) {
90
- // Use the default image
91
- jsonContent.image = '';
92
- }
93
-
94
- // URL is optional, so we have to test for it
95
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'url')) {
96
- // Use the default url
97
- jsonContent.url = '';
98
- }
99
-
100
- // License is optional, so we have to test for it
101
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'license')) {
102
- // Use the default license
103
- jsonContent.license = '';
104
- }
105
-
106
- // Proofing is optional, so we have to test for it
107
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'proofing')) {
108
- // Use the default proofing
109
- jsonContent.proofing = false;
110
- }
111
-
112
- // Version is required, so we have to test for it
113
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'version')) {
114
- // Throw error
115
- throw new Error('Processed story format does not have required version property!');
116
- }
117
-
118
- // Test if version is semantic-style, which is required
119
- if (semver.valid(jsonContent.version) === null) {
120
- throw new Error('Processed story format\'s version is not a valid semantic value!');
121
- }
122
-
123
- // Source is required, so we have to test for it
124
- if (!Object.prototype.hasOwnProperty.call(jsonContent, 'source')) {
125
- // Throw error
126
- throw new Error('Processed story format does not have required source property!');
127
- }
128
-
129
- // Pass all values to the constructor and return the result
130
- return new StoryFormat(
131
- jsonContent.name,
132
- jsonContent.version,
133
- jsonContent.description,
134
- jsonContent.author,
135
- jsonContent.image,
136
- jsonContent.url,
137
- jsonContent.license,
138
- jsonContent.proofing,
139
- jsonContent.source
140
- );
141
- }
142
- }
package/src/TweeParser.js DELETED
@@ -1,161 +0,0 @@
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 DELETED
@@ -1,121 +0,0 @@
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
- // Look for StoryTitle
85
- const storyTitlePassage = story.getPassageByName('StoryTitle');
86
-
87
- // Does it exist?
88
- if (storyTitlePassage !== null) {
89
- // Append StoryTitle content
90
- outputContents += storyTitlePassage.toString();
91
- }
92
-
93
- // For each passage, append it to the output.
94
- story.forEach((passage) => {
95
- // For each passage, append it to the output.
96
- outputContents += passage.toString();
97
- });
98
-
99
- // Look for 'script' passages
100
- const scriptPassages = story.getPassagesByTag('script');
101
- // For each 'script', add it back
102
- scriptPassages.forEach(passage => {
103
- outputContents += passage.toString();
104
- });
105
-
106
- // Look for 'stylesheet' passages
107
- const stylesheetPassages = story.getPassagesByTag('stylesheet');
108
- // For each 'stylesheet', add it back
109
- stylesheetPassages.forEach(passage => {
110
- outputContents += passage.toString();
111
- });
112
-
113
- try {
114
- // Try to write
115
- fs.writeFileSync(file, outputContents);
116
- } catch (event) {
117
- // Throw error
118
- throw new Error('Error: Cannot write Twee file!');
119
- }
120
- }
121
- }