extwee 2.2.4 → 2.2.6
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 +26 -21
- package/eslint.config.js +7 -1
- package/index.js +2 -0
- package/package.json +22 -20
- package/src/Config/parser.js +36 -0
- package/src/Config/reader.js +34 -0
- package/src/Story.js +10 -1
- package/src/StoryFormat/compile.js +19 -0
- package/src/StoryFormat.js +51 -0
- package/src/TWS/parse.js +1 -2
- package/src/extwee.js +6 -6
- package/test/CLI/CLI.test.js +2 -2
- package/test/Config/Config.test.js +46 -0
- package/test/Config/files/empty.json +3 -0
- package/test/Config/files/invalid.json +1 -0
- package/test/Config/files/valid.json +5 -0
- package/test/Objects/Story.test.js +43 -0
- package/test/Objects/StoryFormat.test.js +60 -0
- package/types/Config/parser.d.ts +6 -0
- package/types/Config/reader.d.ts +11 -0
- package/types/Passage.d.ts +2 -2
- package/types/Story.d.ts +5 -5
- package/types/StoryFormat/compile.d.ts +8 -0
- package/types/StoryFormat.d.ts +7 -0
- package/build/extwee +0 -0
- package/build/extwee.exe +0 -0
- package/build/extwee.web.min.js +0 -2
- package/build/extwee.web.min.js.LICENSE.txt +0 -1
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
## Story Compilation
|
|
29
29
|
|
|
30
|
-
The process of *story compilation* converts human-readable content, what is generally called a *story*, into a playable format such as HyperText Markup Language (HTML). The result is then presented as links or other visual, interactive elements for another party
|
|
30
|
+
The process of *story compilation* converts human-readable content, what is generally called a *story*, into a playable format such as HyperText Markup Language (HTML). The result is then presented as links or other visual, interactive elements for another party.
|
|
31
31
|
|
|
32
32
|
The term *compilation* is used because different parts of code are put together in a specific arrangement to enable later play. As part of Twine-compatible HTML, this means combining JavaScript code (generally a "story format") with story HTML data.
|
|
33
33
|
|
|
@@ -59,12 +59,13 @@ Extwee supports multiple historical and current Twine-compatible formats.
|
|
|
59
59
|
|
|
60
60
|
| Format | Input | Output |
|
|
61
61
|
|----------------------------------------------------------------------------------------------------------------------------------|-------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
62
|
-
| [
|
|
63
|
-
| [
|
|
64
|
-
| [
|
|
65
|
-
| [
|
|
66
|
-
| [
|
|
67
|
-
| [
|
|
62
|
+
| [Twine 1 HTML (2006 - 2015)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-htmloutput-doc.md ) | Yes | Partial support. Twine 1 HTML can be produced, but the `StorySettings` optional passage introduced in Twine 1.4.0 requires external libraries like jQuery not included with Extwee. |
|
|
63
|
+
| [Twine 1 TWS (2009 - 2015)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-1-twsoutput.md ) | Yes | Extwee does not support TWS (Python pickle) output because no current version of Twine or other story compilation tool produces this historical format. |
|
|
64
|
+
| [Twine 2 HTML (2015 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-htmloutput-spec.md ) | Yes | Yes |
|
|
65
|
+
| [Twine 2 Archive HTML (2015 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-archive-spec.md ) | Yes | Yes |
|
|
66
|
+
| [Twee 3 (2021 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twee-3-specification.md ) | Yes | Yes |
|
|
67
|
+
| [Twine 2 JSON (2023 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-jsonoutput-doc.md ) | Yes | Yes |
|
|
68
|
+
| [Twine 2 Story Format (2015 - Present)]( https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md ) | Yes | Yes |
|
|
68
69
|
|
|
69
70
|
**Note:** Round-trip translations can present problems because of required fields and properties per format. Some metadata may be added or removed based on the specification being followed.
|
|
70
71
|
|
|
@@ -86,6 +87,8 @@ An object must be created using either the `new` keyword in JavaScript or as the
|
|
|
86
87
|
|
|
87
88
|
Story and Passage objects can generate multiple output formats: `toTwee()`, `toTwine1HTML()`, `toTwine2HTML()`, and `toJSON()`. Stories cannot be played in a browser without the corresponding compiler combining it with story format data.
|
|
88
89
|
|
|
90
|
+
The StoryFormat object supports `toString()` method of producing a tab-separated JSON output and `toJSON()` method of generating JSON output matching the Twine 2 Story Format Specification.
|
|
91
|
+
|
|
89
92
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
90
93
|
|
|
91
94
|
### Parsers
|
|
@@ -110,8 +113,11 @@ Compiles story, story formats, or other data into an archive or playable format.
|
|
|
110
113
|
- `compileTwine2HTML()`
|
|
111
114
|
- `compileTwine1HTML()`
|
|
112
115
|
- `compileTwine2ArchiveHTML()`
|
|
116
|
+
- `compileStoryFormat()`
|
|
117
|
+
|
|
118
|
+
To create playable Twine 1 HTML, an `engine.js` file must be supplied. (See [Story Format Archive](https://github.com/videlais/story-formats-archive/tree/main/official/twine1/engine/1.4.2) for available Twine 1 `engine.js` file.)
|
|
113
119
|
|
|
114
|
-
|
|
120
|
+
Compilation of a story format adds the necessary function wrapper to convert the JSON output, via `toJSON()`, to JSONP.
|
|
115
121
|
|
|
116
122
|
### Support Functionality
|
|
117
123
|
|
|
@@ -129,7 +135,9 @@ Extwee has [documentation hosted on GitHub Pages](https://videlais.github.io/ext
|
|
|
129
135
|
|
|
130
136
|
## Command-Line Usage
|
|
131
137
|
|
|
132
|
-
Extwee supports a command-line interface for four general scenarios
|
|
138
|
+
Extwee supports a command-line interface for four general scenarios.
|
|
139
|
+
|
|
140
|
+
**Notes:** As of Extwee 2.2.5, short and long command-line option flags are separated. Short options use one hyphen followed by one character and all long options begin with two hyphens and a name as word.
|
|
133
141
|
|
|
134
142
|
### Compiling Twee 3 + Twine 2 Story Format into Twine 2 HTML
|
|
135
143
|
|
|
@@ -145,30 +153,27 @@ De-compile Twine 2 HTML into Twee 3:
|
|
|
145
153
|
|
|
146
154
|
### Compiling Twee 3 into Twine 1 HTML
|
|
147
155
|
|
|
148
|
-
Enabling Twine 1 mode requires using the
|
|
156
|
+
Enabling Twine 1 mode requires using the `--twine1` long flag.
|
|
149
157
|
|
|
150
158
|
Because Twine 1 story formats can be split across files, compilation requires the "engine" from Twine 1 named `engine.js`, the name of the story format, and then its `header.html` template code and the optional but often included `code.js` file.
|
|
151
159
|
|
|
152
|
-
|
|
160
|
+
(Refer to the [Story Formats Archive](https://github.com/videlais/story-formats-archive) for access to historic `engine.js` and other files.)
|
|
161
|
+
|
|
162
|
+
`extwee --twine1 -c -i <tweeFile> -o <Twine1HTML> --engine <engineJS> --name <storyFormatName> --codejs <CodeJS> --header <header>`
|
|
153
163
|
|
|
154
164
|
### De-compiling Twine 1 HTML into Twee 3
|
|
155
165
|
|
|
156
|
-
Enabling Twine 1 mode requires using the
|
|
166
|
+
Enabling Twine 1 mode requires using the `--twine1` flag.
|
|
157
167
|
|
|
158
|
-
`extwee
|
|
168
|
+
`extwee --twine1 -d -i <twine1HTML> -o <outputTwee>`
|
|
159
169
|
|
|
160
170
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
161
171
|
|
|
162
|
-
## Roadmap
|
|
163
|
-
|
|
164
|
-
Each major version has its own GitHub project:
|
|
165
|
-
|
|
166
|
-
- [Road to Extwee 2.2.0](https://github.com/users/videlais/projects/2)
|
|
167
|
-
- [Road to Extwee 2.4.0](https://github.com/users/videlais/projects/4)
|
|
168
|
-
|
|
169
172
|
## Tree Shaking Support
|
|
170
173
|
|
|
171
|
-
For [advanced tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) patterns, most formats are broke into `compile.js` and `parse.js` files exporting associated `compile()` and `parse()` functions.
|
|
174
|
+
For [advanced tree shaking](https://developer.mozilla.org/en-US/docs/Glossary/Tree_shaking) patterns, most formats are broke into `compile.js` and `parse.js` files exporting associated `compile()` and `parse()` functions.
|
|
175
|
+
|
|
176
|
+
When using the API, it is possible to only import a single function or object to produce smaller and potentially faster code in projects dependent on Extwee functionality.
|
|
172
177
|
|
|
173
178
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
|
174
179
|
|
package/eslint.config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import globals from "globals";
|
|
2
2
|
import pluginJs from "@eslint/js";
|
|
3
3
|
import jest from "eslint-plugin-jest";
|
|
4
|
+
import jsdoc from 'eslint-plugin-jsdoc';
|
|
4
5
|
|
|
5
6
|
export default [
|
|
7
|
+
jsdoc.configs['flat/recommended'],
|
|
6
8
|
{
|
|
7
9
|
languageOptions: {
|
|
8
10
|
globals: {
|
|
@@ -12,7 +14,11 @@ export default [
|
|
|
12
14
|
}
|
|
13
15
|
},
|
|
14
16
|
plugins: {
|
|
15
|
-
jest: jest
|
|
17
|
+
jest: jest,
|
|
18
|
+
jsdoc: jsdoc
|
|
19
|
+
},
|
|
20
|
+
rules: {
|
|
21
|
+
'jsdoc/require-description': 'warn'
|
|
16
22
|
}
|
|
17
23
|
},
|
|
18
24
|
pluginJs.configs.recommended,
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { parse as parseTWS } from './src/TWS/parse.js';
|
|
|
8
8
|
import { compile as compileTwine1HTML } from './src/Twine1HTML/compile.js';
|
|
9
9
|
import { compile as compileTwine2HTML } from './src/Twine2HTML/compile.js';
|
|
10
10
|
import { compile as compileTwine2ArchiveHTML } from './src/Twine2ArchiveHTML/compile.js';
|
|
11
|
+
import { compile as compileStoryFormat } from './src/StoryFormat/compile.js';
|
|
11
12
|
import { generate as generateIFID } from './src/IFID/generate.js';
|
|
12
13
|
import { Story } from './src/Story.js';
|
|
13
14
|
import Passage from './src/Passage.js';
|
|
@@ -24,6 +25,7 @@ export {
|
|
|
24
25
|
compileTwine1HTML,
|
|
25
26
|
compileTwine2HTML,
|
|
26
27
|
compileTwine2ArchiveHTML,
|
|
28
|
+
compileStoryFormat,
|
|
27
29
|
generateIFID,
|
|
28
30
|
Story,
|
|
29
31
|
Passage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "extwee",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
4
4
|
"description": "A story compiler tool using Twine-compatible formats",
|
|
5
5
|
"author": "Dan Cox",
|
|
6
6
|
"main": "index.js",
|
|
@@ -24,34 +24,36 @@
|
|
|
24
24
|
],
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"commander": "^
|
|
27
|
+
"commander": "^13.1.0",
|
|
28
28
|
"graphemer": "^1.4.0",
|
|
29
29
|
"html-entities": "^2.5.2",
|
|
30
|
-
"node-html-parser": "^
|
|
30
|
+
"node-html-parser": "^7.0.1",
|
|
31
31
|
"pickleparser": "^0.2.1",
|
|
32
|
-
"semver": "^7.
|
|
33
|
-
"uuid": "^
|
|
32
|
+
"semver": "^7.7.1",
|
|
33
|
+
"uuid": "^11.1.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@babel/cli": "^7.
|
|
37
|
-
"@babel/core": "^7.
|
|
38
|
-
"@babel/preset-env": "^7.
|
|
39
|
-
"@eslint/js": "^9.
|
|
36
|
+
"@babel/cli": "^7.26.4",
|
|
37
|
+
"@babel/core": "^7.26.10",
|
|
38
|
+
"@babel/preset-env": "^7.26.9",
|
|
39
|
+
"@eslint/js": "^9.22.0",
|
|
40
|
+
"@types/semver": "^7.5.8",
|
|
40
41
|
"@types/uuid": "^10.0.0",
|
|
41
|
-
"babel-loader": "^
|
|
42
|
+
"babel-loader": "^10.0.0",
|
|
42
43
|
"clean-jsdoc-theme": "^4.3.0",
|
|
43
|
-
"core-js": "^3.
|
|
44
|
-
"esbuild": "^0.
|
|
45
|
-
"eslint": "^9.
|
|
46
|
-
"eslint-plugin-jest": "^28.
|
|
47
|
-
"
|
|
44
|
+
"core-js": "^3.41.0",
|
|
45
|
+
"esbuild": "^0.25.1",
|
|
46
|
+
"eslint": "^9.22.0",
|
|
47
|
+
"eslint-plugin-jest": "^28.11.0",
|
|
48
|
+
"eslint-plugin-jsdoc": "^50.6.6",
|
|
49
|
+
"globals": "^16.0.0",
|
|
48
50
|
"jest": "^29.7.0",
|
|
49
51
|
"regenerator-runtime": "^0.14.1",
|
|
50
|
-
"shelljs": "^0.
|
|
51
|
-
"typescript": "^5.
|
|
52
|
-
"typescript-eslint": "^8.
|
|
53
|
-
"webpack": "^5.
|
|
54
|
-
"webpack-cli": "^
|
|
52
|
+
"shelljs": "^0.9.1",
|
|
53
|
+
"typescript": "^5.8.2",
|
|
54
|
+
"typescript-eslint": "^8.26.1",
|
|
55
|
+
"webpack": "^5.98.0",
|
|
56
|
+
"webpack-cli": "^6.0.1"
|
|
55
57
|
},
|
|
56
58
|
"repository": {
|
|
57
59
|
"type": "git",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a JSON object and extracts the StoryFormat, StoryTitle and StoryVersion.
|
|
3
|
+
* @param {object} obj Incoming JSON object.
|
|
4
|
+
* @returns {object} An object containing the extracted results.
|
|
5
|
+
*/
|
|
6
|
+
export function parser(obj) {
|
|
7
|
+
// Check if the object is a valid JSON object.
|
|
8
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
9
|
+
throw new Error('Error: Invalid JSON object');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Extracted results.
|
|
13
|
+
let results = {
|
|
14
|
+
StoryFormat: null,
|
|
15
|
+
StoryTitle: null,
|
|
16
|
+
StoryVersion: null
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Does the object contain 'StoryFormat'?
|
|
20
|
+
if (Object.hasOwnProperty.call(obj, 'story-format')) {
|
|
21
|
+
results.StoryFormat = obj['story-format'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Does the object contain 'StoryTitle'?
|
|
25
|
+
if (Object.hasOwnProperty.call(obj, 'story-title')) {
|
|
26
|
+
results.StoryTitle = obj['story-title'];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Does the object contain 'StoryVersion'?
|
|
30
|
+
if (Object.hasOwnProperty.call(obj, 'story-version')) {
|
|
31
|
+
results.StoryVersion = obj['story-version'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return the extracted results.
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {readFileSync, existsSync} from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Read a JSON file and return its contents.
|
|
5
|
+
* @param {string} path Path to the JSON file.
|
|
6
|
+
* @returns {object} Parsed JSON object.
|
|
7
|
+
* @throws {Error} If the file does not exist.
|
|
8
|
+
* @throws {Error} If the file is not a valid JSON file.
|
|
9
|
+
* @example
|
|
10
|
+
* const contents = reader('test/Config/files/valid.json');
|
|
11
|
+
* console.log(contents); // {"story-format": 'Harlowe', "story-title": "My Story"}
|
|
12
|
+
*/
|
|
13
|
+
export function reader(path) {
|
|
14
|
+
// Does the file exist?
|
|
15
|
+
if (!existsSync(path)) {
|
|
16
|
+
throw new Error(`Error: File ${path} not found`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Read the file.
|
|
20
|
+
const contents = readFileSync(path, 'utf8');
|
|
21
|
+
|
|
22
|
+
// Parsed contents.
|
|
23
|
+
let parsedContents = null;
|
|
24
|
+
|
|
25
|
+
// Try to parse the contents into JSON object.
|
|
26
|
+
try {
|
|
27
|
+
parsedContents = JSON.parse(contents);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Error: File ${path} is not a valid JSON file. ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Return the parsed contents.
|
|
33
|
+
return parsedContents;
|
|
34
|
+
}
|
package/src/Story.js
CHANGED
|
@@ -5,7 +5,7 @@ import { encode } from 'html-entities';
|
|
|
5
5
|
const creatorName = 'extwee';
|
|
6
6
|
|
|
7
7
|
// Set the creator version.
|
|
8
|
-
const creatorVersion = '2.2.
|
|
8
|
+
const creatorVersion = '2.2.6';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Story class.
|
|
@@ -781,6 +781,15 @@ class Story {
|
|
|
781
781
|
PIDcounter++;
|
|
782
782
|
});
|
|
783
783
|
|
|
784
|
+
// Generate <tw-tag> elements for each tag, if any.
|
|
785
|
+
const tagList = Object.keys(this.tagColors);
|
|
786
|
+
|
|
787
|
+
// For each tag, generate a <tw-tag> element.
|
|
788
|
+
tagList.forEach((tag) => {
|
|
789
|
+
// Add the <tw-tag> element.
|
|
790
|
+
storyData += `\t<tw-tag name="${tag}" color="${this.tagColors[tag]}"></tw-tag>\n`;
|
|
791
|
+
});
|
|
792
|
+
|
|
784
793
|
// Close the HTML element.
|
|
785
794
|
storyData += '</tw-storydata>';
|
|
786
795
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import StoryFormat from '../StoryFormat.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compiles a {@link StoryFormat} object into a JSONP string for writing to a `format.js` file.
|
|
5
|
+
* @see {@link https://github.com/iftechfoundation/twine-specs/blob/master/twine-2-storyformats-spec.md Twine 2 Story Formats Specification}
|
|
6
|
+
* @param {StoryFormat} storyFormat Story format object to compile.
|
|
7
|
+
* @returns {string} JSONP string.
|
|
8
|
+
*/
|
|
9
|
+
function compile (storyFormat) {
|
|
10
|
+
// Test if storyFormat is a StoryFormat object.
|
|
11
|
+
if (!(storyFormat instanceof StoryFormat)) {
|
|
12
|
+
throw new TypeError('Error: Incoming object is not a storyFormat object');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Create a JSONP string wrapped with the function window.StoryFormat.
|
|
16
|
+
return `window.storyFormat(${JSON.stringify(storyFormat)})`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { compile };
|
package/src/StoryFormat.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { valid } from 'semver';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* StoryFormat representing a Twine 2 story format.
|
|
3
5
|
*
|
|
@@ -85,6 +87,19 @@ export default class StoryFormat {
|
|
|
85
87
|
*/
|
|
86
88
|
#_source = '';
|
|
87
89
|
|
|
90
|
+
// Constructor with all parameters.
|
|
91
|
+
constructor(name = 'Untitled Story Format', version = '', description = '', author = '', image = '', url = '', license = '', proofing = false, source = '') {
|
|
92
|
+
this.name = name;
|
|
93
|
+
this.version = version;
|
|
94
|
+
this.description = description;
|
|
95
|
+
this.author = author;
|
|
96
|
+
this.image = image;
|
|
97
|
+
this.url = url;
|
|
98
|
+
this.license = license;
|
|
99
|
+
this.proofing = proofing;
|
|
100
|
+
this.source = source;
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
/**
|
|
89
104
|
* Name
|
|
90
105
|
* @returns {string} Name.
|
|
@@ -246,4 +261,40 @@ export default class StoryFormat {
|
|
|
246
261
|
toString() {
|
|
247
262
|
return JSON.stringify(this, null, "\t");
|
|
248
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Produces a JSON representation of the story format object.
|
|
267
|
+
* @method toJSON
|
|
268
|
+
* @returns {object} - A JSON representation of the story format.
|
|
269
|
+
*/
|
|
270
|
+
toJSON() {
|
|
271
|
+
// name: (string) Optional. The name of the story format. (Omitting the name will lead to an Untitled Story Format.)
|
|
272
|
+
// Set a default name.
|
|
273
|
+
let name = "Untitled Story Format";
|
|
274
|
+
|
|
275
|
+
// Check if the story format is not an empty string.
|
|
276
|
+
if (this.name.length > 0) {
|
|
277
|
+
// Update the name.
|
|
278
|
+
name = this.name;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// version: (string) Required, and semantic version-style formatting (x.y.z, e.g., 1.2.1) of the version is also required.
|
|
282
|
+
|
|
283
|
+
// Check if the version is valid. If not, throw an error.
|
|
284
|
+
if (!valid(this.version)) {
|
|
285
|
+
throw new TypeError('ERROR: Version must be a valid semantic version!');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return JSON.stringify({
|
|
289
|
+
name,
|
|
290
|
+
version: this.version,
|
|
291
|
+
description: this.description,
|
|
292
|
+
author: this.author,
|
|
293
|
+
image: this.image,
|
|
294
|
+
url: this.url,
|
|
295
|
+
license: this.license,
|
|
296
|
+
proofing: this.proofing,
|
|
297
|
+
source: this.source
|
|
298
|
+
});
|
|
299
|
+
}
|
|
249
300
|
}
|
package/src/TWS/parse.js
CHANGED
|
@@ -28,9 +28,8 @@ function parse (binaryFileContents) {
|
|
|
28
28
|
// Try to parse the pickle data, assuming it is pickle data.
|
|
29
29
|
pythonObject = parser.parse(binaryFileContents);
|
|
30
30
|
} catch (error) {
|
|
31
|
-
console.log(error);
|
|
32
31
|
// This is a Buffer, but not pickle data.
|
|
33
|
-
throw new TypeError(
|
|
32
|
+
throw new TypeError(`Error: Buffer does not contain Python pickle data! ${error}`);
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
// Create Story object.
|
package/src/extwee.js
CHANGED
|
@@ -53,12 +53,12 @@ const isFile = (path) => {
|
|
|
53
53
|
program
|
|
54
54
|
.name('extwee')
|
|
55
55
|
.description('CLI for Extwee')
|
|
56
|
-
.
|
|
56
|
+
.option('-v, --version', '2.2.4')
|
|
57
57
|
.option('-c, --compile', 'Compile input into output')
|
|
58
58
|
.option('-d, --decompile', 'De-compile input into output')
|
|
59
|
-
.option('
|
|
60
|
-
.option('
|
|
61
|
-
.option('
|
|
59
|
+
.option('--twine1', 'Enable Twine 1 processing')
|
|
60
|
+
.option('--name <storyFormatName>', 'Name of the Twine 1 story format (needed for `code.js` inclusion)')
|
|
61
|
+
.option('--codejs <codeJSFile>', 'Twine 1 code.js file for use with Twine 1 HTML', (value) => {
|
|
62
62
|
// Does the input file exist?
|
|
63
63
|
if (isFile(value) === false) {
|
|
64
64
|
// We cannot do anything without valid input.
|
|
@@ -67,7 +67,7 @@ program
|
|
|
67
67
|
|
|
68
68
|
return value;
|
|
69
69
|
})
|
|
70
|
-
.option('
|
|
70
|
+
.option('--engine <engineFile>', 'Twine 1 engine.js file for use with Twine 1 HTML', (value) => {
|
|
71
71
|
// Does the input file exist?
|
|
72
72
|
if (isFile(value) === false) {
|
|
73
73
|
// We cannot do anything without valid input.
|
|
@@ -76,7 +76,7 @@ program
|
|
|
76
76
|
|
|
77
77
|
return value;
|
|
78
78
|
})
|
|
79
|
-
.option('
|
|
79
|
+
.option('--header <headerFile>', 'Twine 1 header.html file for use with Twine 1 HTML', (value) => {
|
|
80
80
|
// Does the input file exist?
|
|
81
81
|
if (isFile(value) === false) {
|
|
82
82
|
// We cannot do anything without valid input.
|
package/test/CLI/CLI.test.js
CHANGED
|
@@ -27,12 +27,12 @@ describe('CLI', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
it('Twine 1 - compile: Twee 3 + Twine 1 engine.js + Twine 1 code.js + Twine 1 header.html', () => {
|
|
30
|
-
shell.exec(`node ${currentPath}/src/extwee.js
|
|
30
|
+
shell.exec(`node ${currentPath}/src/extwee.js --twine1 -c -i ${testFilePath}/example6.twee -o ${testFilePath}/output/test3.html --codejs ${testFilePath}/twine1/code.js --engine ${testFilePath}/twine1/engine.js --header ${testFilePath}/twine1/header.html --name Test`);
|
|
31
31
|
expect(shell.test('-e', `${testFilePath}/output/test3.html`)).toBe(true);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('Twine 1 - de-compile: Twine 1 HTML into Twee 3', () => {
|
|
35
|
-
shell.exec(`node ${currentPath}/src/extwee.js
|
|
35
|
+
shell.exec(`node ${currentPath}/src/extwee.js --twine1 -d -i ${testFilePath}/twine1Test.html -o ${testFilePath}/output/test.twee`);
|
|
36
36
|
expect(shell.test('-e', `${testFilePath}/output/test.twee`)).toBe(true);
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {reader as ConfigReader} from '../../src/Config/reader.js';
|
|
2
|
+
import {parser as ConfigParser} from '../../src/Config/parser.js';
|
|
3
|
+
|
|
4
|
+
describe('src/Config/reader.js', () => {
|
|
5
|
+
describe('reader()', () => {
|
|
6
|
+
it('should throw an error if the file does not exist', () => {
|
|
7
|
+
expect(() => ConfigReader('non-existent-file.json')).toThrow('Error: File non-existent-file.json not found');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should throw an error if the file is not a valid JSON file', () => {
|
|
11
|
+
expect(() => ConfigReader('test/Config/files/invalid.json')).toThrow();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should return the parsed JSON contents of the file', () => {
|
|
15
|
+
const contents = ConfigReader('test/Config/files/valid.json');
|
|
16
|
+
expect(contents).toEqual({
|
|
17
|
+
"story-format": 'Harlowe',
|
|
18
|
+
"story-title": "My Story",
|
|
19
|
+
"story-version": "2.0.1"
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe('parser()', () => {
|
|
25
|
+
|
|
26
|
+
it('should throw an error if the object is not a valid JSON object', () => {
|
|
27
|
+
expect(() => ConfigParser('{')).toThrow();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should extract the StoryFormat, StoryTitle, and StoryVersion from the JSON object', () => {
|
|
31
|
+
const jsonObject = ConfigReader('test/Config/files/valid.json');
|
|
32
|
+
const contents = ConfigParser(jsonObject);
|
|
33
|
+
expect(contents.StoryFormat).toEqual('Harlowe');
|
|
34
|
+
expect(contents.StoryTitle).toEqual('My Story');
|
|
35
|
+
expect(contents.StoryVersion).toEqual('2.0.1');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should not extract the StoryFormat, StoryTitle, and StoryVersion if they do not exist in the JSON object', () => {
|
|
39
|
+
const jsonObject = ConfigReader('test/Config/files/empty.json');
|
|
40
|
+
const contents = ConfigParser(jsonObject);
|
|
41
|
+
expect(contents.StoryFormat).toBeNull();
|
|
42
|
+
expect(contents.StoryTitle).toBeNull();
|
|
43
|
+
expect(contents.StoryVersion).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
invalid
|
|
@@ -819,6 +819,49 @@ describe('Story', () => {
|
|
|
819
819
|
// Expect the creator to be encoded.
|
|
820
820
|
expect(result.includes(`creator="${creatorName}"`)).not.toBe(true);
|
|
821
821
|
});
|
|
822
|
+
|
|
823
|
+
it('Should not encode story tag colors if none are present', () => {
|
|
824
|
+
// Add passage.
|
|
825
|
+
s.addPassage(new Passage('Start', 'Word'));
|
|
826
|
+
// Set start.
|
|
827
|
+
s.start = 'Start';
|
|
828
|
+
// Create HTML.
|
|
829
|
+
const result = s.toTwine2HTML();
|
|
830
|
+
// Expect the tag colors to not be encoded if none are present.
|
|
831
|
+
expect(result.includes('<tw-tag')).toBe(false);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it('Should encode single story tag color', () => {
|
|
835
|
+
// Add passage.
|
|
836
|
+
s.addPassage(new Passage('Start', 'Word'));
|
|
837
|
+
// Set start.
|
|
838
|
+
s.start = 'Start';
|
|
839
|
+
// Add tag colors.
|
|
840
|
+
s.tagColors = {
|
|
841
|
+
bar: 'green'
|
|
842
|
+
};
|
|
843
|
+
// Create HTML.
|
|
844
|
+
const result = s.toTwine2HTML();
|
|
845
|
+
// Expect the tag colors to be encoded.
|
|
846
|
+
expect(result.includes('<tw-tag name="bar" color="green"></tw-tag>')).toBe(true);
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
it('Should encode multiple story tag colors', () => {
|
|
850
|
+
// Add passage.
|
|
851
|
+
s.addPassage(new Passage('Start', 'Word'));
|
|
852
|
+
// Set start.
|
|
853
|
+
s.start = 'Start';
|
|
854
|
+
// Add tag colors.
|
|
855
|
+
s.tagColors = {
|
|
856
|
+
bar: 'green',
|
|
857
|
+
foo: 'red'
|
|
858
|
+
};
|
|
859
|
+
// Create HTML.
|
|
860
|
+
const result = s.toTwine2HTML();
|
|
861
|
+
// Expect the tag colors to be encoded.
|
|
862
|
+
expect(result.includes('<tw-tag name="bar" color="green"></tw-tag>')).toBe(true);
|
|
863
|
+
expect(result.includes('<tw-tag name="foo" color="red"></tw-tag>')).toBe(true);
|
|
864
|
+
});
|
|
822
865
|
});
|
|
823
866
|
|
|
824
867
|
describe('toTwine1HTML()', function () {
|
|
@@ -15,6 +15,29 @@ describe('StoryFormat', () => {
|
|
|
15
15
|
});
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
describe('Constructor with default values', () => {
|
|
19
|
+
it('Should create an instance of StoryFormat', () => {
|
|
20
|
+
const sf = new StoryFormat();
|
|
21
|
+
expect(sf).toBeInstanceOf(StoryFormat);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('Constructor with parameters', () => {
|
|
26
|
+
it('Should create an instance of StoryFormat with parameters', () => {
|
|
27
|
+
const sf = new StoryFormat('name', 'version', 'description', 'author', 'image', 'url', 'license', true, 'source');
|
|
28
|
+
expect(sf).toBeInstanceOf(StoryFormat);
|
|
29
|
+
expect(sf.name).toBe('name');
|
|
30
|
+
expect(sf.version).toBe('version');
|
|
31
|
+
expect(sf.description).toBe('description');
|
|
32
|
+
expect(sf.author).toBe('author');
|
|
33
|
+
expect(sf.image).toBe('image');
|
|
34
|
+
expect(sf.url).toBe('url');
|
|
35
|
+
expect(sf.license).toBe('license');
|
|
36
|
+
expect(sf.proofing).toBe(true);
|
|
37
|
+
expect(sf.source).toBe('source');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
18
41
|
describe('name', () => {
|
|
19
42
|
it('Set new String', () => {
|
|
20
43
|
const sf = new StoryFormat();
|
|
@@ -153,7 +176,44 @@ describe('StoryFormat', () => {
|
|
|
153
176
|
describe('toString', () => {
|
|
154
177
|
it('Should return string representation', () => {
|
|
155
178
|
const sf = new StoryFormat();
|
|
179
|
+
sf.version = '1.0.0';
|
|
156
180
|
expect(sf.toString()).toBe(JSON.stringify(sf, null, '\t'));
|
|
157
181
|
});
|
|
182
|
+
|
|
183
|
+
it('Should throw error if version is not valid', () => {
|
|
184
|
+
const sf = new StoryFormat();
|
|
185
|
+
expect(() => {
|
|
186
|
+
sf.toString();
|
|
187
|
+
}).toThrow();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('toJSON', () => {
|
|
192
|
+
it('Should return JSON representation with default name', () => {
|
|
193
|
+
const sf = new StoryFormat();
|
|
194
|
+
sf.version = '1.0.0';
|
|
195
|
+
expect(sf.toJSON().includes("Untitled Story Format")).toEqual(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('Should return JSON representation with custom name', () => {
|
|
199
|
+
const sf = new StoryFormat();
|
|
200
|
+
sf.name = 'Custom Name';
|
|
201
|
+
sf.version = '1.0.0';
|
|
202
|
+
expect(sf.toJSON().includes("Custom Name")).toEqual(true);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('Should overwrite with default name if name is empty string', () => {
|
|
206
|
+
const sf = new StoryFormat();
|
|
207
|
+
sf.name = '';
|
|
208
|
+
sf.version = '1.0.0';
|
|
209
|
+
expect(sf.toJSON().includes("Untitled Story Format")).toEqual(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('Should throw error if version is not valid', () => {
|
|
213
|
+
const sf = new StoryFormat();
|
|
214
|
+
expect(() => {
|
|
215
|
+
sf.toJSON();
|
|
216
|
+
}).toThrow();
|
|
217
|
+
});
|
|
158
218
|
});
|
|
159
219
|
});
|