extwee 2.2.3 → 2.2.4

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.
@@ -0,0 +1,19 @@
1
+ import globals from "globals";
2
+ import pluginJs from "@eslint/js";
3
+ import jest from "eslint-plugin-jest";
4
+
5
+ export default [
6
+ {
7
+ languageOptions: {
8
+ globals: {
9
+ ...globals.browser,
10
+ ...globals.node,
11
+ ...globals.jest
12
+ }
13
+ },
14
+ plugins: {
15
+ jest: jest
16
+ }
17
+ },
18
+ pluginJs.configs.recommended,
19
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "extwee",
3
- "version": "2.2.3",
3
+ "version": "2.2.4",
4
4
  "description": "A story compiler tool using Twine-compatible formats",
5
5
  "author": "Dan Cox",
6
6
  "main": "index.js",
@@ -12,7 +12,7 @@
12
12
  "lint": "eslint ./src/**/*.js --fix",
13
13
  "lint:test": "eslint ./test/**/*.test.js --fix",
14
14
  "build:web": "webpack",
15
- "build:bin": "esbuild ./src/extwee.js --bundle --platform=node --target=node12 --outfile=out.js && pkg -t node18-macos-x64 out.js --output ./build/extwee && rm out.js",
15
+ "build:bin": "esbuild ./src/extwee.js --bundle --platform=node --target=node12 --outfile=out.js",
16
16
  "gen-types": "npx -p typescript tsc src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types",
17
17
  "all": "npm run lint && npm run lint:test && npm run test && npm run gen-types"
18
18
  },
@@ -24,38 +24,32 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
- "commander": "^12.0.0",
27
+ "commander": "^12.1.0",
28
28
  "graphemer": "^1.4.0",
29
29
  "html-entities": "^2.5.2",
30
30
  "node-html-parser": "^6.1.13",
31
31
  "pickleparser": "^0.2.1",
32
- "semver": "^7.6.0",
33
- "uuid": "^9.0.1"
32
+ "semver": "^7.6.2",
33
+ "uuid": "^10.0.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@babel/cli": "^7.24.1",
37
- "@babel/core": "^7.24.4",
38
- "@babel/eslint-parser": "^7.24.1",
39
- "@babel/eslint-plugin": "^7.23.5",
40
- "@babel/preset-env": "^7.24.4",
41
- "@types/uuid": "^9.0.7",
36
+ "@babel/cli": "^7.25.6",
37
+ "@babel/core": "^7.24.6",
38
+ "@babel/preset-env": "^7.24.6",
39
+ "@eslint/js": "^9.10.0",
40
+ "@types/uuid": "^10.0.0",
42
41
  "babel-loader": "^9.1.3",
43
- "clean-jsdoc-theme": "^4.2.18",
44
- "core-js": "^3.36.1",
45
- "esbuild": "^0.20.2",
46
- "eslint": "^8.57.0",
47
- "eslint-config-standard": "^17.1.0",
48
- "eslint-plugin-import": "^2.29.1",
49
- "eslint-plugin-jest": "^28.2.0",
50
- "eslint-plugin-jsdoc": "^48.2.3",
51
- "eslint-plugin-n": "^16.6.2",
52
- "eslint-plugin-node": "^11.1.0",
53
- "eslint-plugin-promise": "^6.1.1",
42
+ "clean-jsdoc-theme": "^4.3.0",
43
+ "core-js": "^3.37.1",
44
+ "esbuild": "^0.23.0",
45
+ "eslint": "^9.10.0",
46
+ "eslint-plugin-jest": "^28.8.3",
47
+ "globals": "^15.9.0",
54
48
  "jest": "^29.7.0",
55
- "pkg": "^5.8.1",
56
49
  "regenerator-runtime": "^0.14.1",
57
50
  "shelljs": "^0.8.5",
58
51
  "typescript": "^5.4.5",
52
+ "typescript-eslint": "^8.4.0",
59
53
  "webpack": "^5.91.0",
60
54
  "webpack-cli": "^5.1.4"
61
55
  },
package/src/JSON/parse.js CHANGED
@@ -61,7 +61,7 @@ function parse (jsonString) {
61
61
  try {
62
62
  result = JSON.parse(jsonString);
63
63
  } catch (error) {
64
- throw new Error('Invalid JSON!');
64
+ throw new Error(`Error: JSON could not be parsed! ${error.message}`);
65
65
  }
66
66
 
67
67
  // Name
package/src/Story.js CHANGED
@@ -3,7 +3,9 @@ import { generate as generateIFID } from './IFID/generate.js';
3
3
  import { encode } from 'html-entities';
4
4
 
5
5
  const creatorName = 'extwee';
6
- const creatorVersion = '2.2.3';
6
+
7
+ // Set the creator version.
8
+ const creatorVersion = '2.2.4';
7
9
 
8
10
  /**
9
11
  * Story class.
@@ -396,6 +398,22 @@ class Story {
396
398
  return this.#_passages.length;
397
399
  }
398
400
 
401
+ // Parse Start
402
+ if (p.name === 'Start') {
403
+ // Have we already encountered StoryData?
404
+ if (this.start == '') {
405
+ // Set internal start based on Start.
406
+ /**
407
+ * Four possible scenarios:
408
+ * 1. StoryData has already been encountered, and we will never get here.
409
+ * 2. StoryData exists and will be encountered after Start.
410
+ * 3. StoryData does not exist.
411
+ * 4. Start is the first and only passage.
412
+ */
413
+ this.start = p.name;
414
+ }
415
+ }
416
+
399
417
  // This is not StoryData or StoryTitle.
400
418
  // Push the passage to the array.
401
419
  return this.#_passages.push(p);
@@ -636,9 +654,6 @@ class Story {
636
654
  storyData += ` ifid="${ generateIFID() }"`;
637
655
  }
638
656
 
639
- // 'Start' passage (if there is not a 'start' value set).
640
- let startPassagePID = null;
641
-
642
657
  // Passage Identification (PID) counter.
643
658
  // (Twine 2 starts with 1, so we mirror that.)
644
659
  let PIDcounter = 1;
@@ -655,35 +670,25 @@ class Story {
655
670
  startPID = PIDcounter;
656
671
  }
657
672
 
658
- // Have we found the 'Start' passage?
659
- if (p.name === 'Start') {
660
- // If so, set the PID based on index.
661
- startPassagePID = PIDcounter;
662
- }
663
-
664
673
  // Increase and keep looking.
665
674
  PIDcounter++;
666
675
  });
667
676
 
668
- // startnode: (integer) Optional.
669
- //
670
- // Maps to <tw-storydata startnode>.
671
- //
672
- // Check if startnode exists.
673
- if(this.start !== '') {
674
- // Set starting passage PID.
675
- storyData += ` startnode="${startPID}"`;
677
+ // Are there any passages?
678
+ if (passages.length === 0) {
679
+ // No passages, so we can't set a startnode.
680
+ startPID = 0;
676
681
  }
677
682
 
678
683
  /**
679
- * If we came from Twee or another source, we might not have a start value.
680
- *
681
- * We might, however, have a passage with the name "Start".
684
+ * Multiple possible scenarios:
685
+ * 1. No passages. (StartPID is 0.)
686
+ * 2. Start is the first or only passage. (StartPID is 1.)
687
+ * 3. Starting passage is not the first passage. (StartPID is > 1.)
682
688
  */
683
- if(this.start === '' && startPassagePID !== null) {
684
- // Set starting passage PID.
685
- storyData += ` startnode="${startPassagePID}"`;
686
- }
689
+
690
+ // startnode: (integer) Optional. The PID of the starting passage.
691
+ storyData += ` startnode="${startPID}"`;
687
692
 
688
693
  // creator: (string) Optional. The name of the program that created the story.
689
694
  // Maps to <tw-storydata creator>.
@@ -90,8 +90,8 @@ function parse (contents) {
90
90
  // Attempt to parse the JSON.
91
91
  try {
92
92
  jsonContent = JSON.parse(contents);
93
- } catch (event) {
94
- throw new Error('Error: Unable to parse Twine 2 JSON chunk!');
93
+ } catch (error) {
94
+ throw new Error(`Error: Unable to parse Twine 2 JSON chunk! ${error.message}`);
95
95
  }
96
96
 
97
97
  /**
package/src/TWS/parse.js CHANGED
@@ -28,8 +28,9 @@ 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);
31
32
  // This is a Buffer, but not pickle data.
32
- throw new Error('Buffer does not contain Python pickle data!');
33
+ throw new TypeError('Error: Buffer does not contain Python pickle data!');
33
34
  }
34
35
 
35
36
  // Create Story object.
package/src/Twee/parse.js CHANGED
@@ -68,7 +68,9 @@ function parse (fileContents) {
68
68
  // Try to parse the metadata
69
69
  try {
70
70
  metadata = JSON.parse(metadata);
71
- } catch (event) {
71
+ } catch (error) {
72
+ console.info(`Info: Metadata could not be parsed. Setting to empty object. Reported error: ${error.message}`);
73
+ metadata = {};
72
74
  }
73
75
  } else {
74
76
  // There wasn't any metadata, so set default
@@ -311,6 +311,27 @@ describe('Story', () => {
311
311
  // Test for format.
312
312
  expect(s.formatVersion).toBe('2.28.2');
313
313
  });
314
+
315
+ it('addPassage() - should override StoryData: zoom', function () {
316
+ // Generate object.
317
+ const o = {
318
+ zoom: 0.5
319
+ };
320
+
321
+ // Add the passage.
322
+ s.addPassage(new Passage('StoryData', JSON.stringify(o)));
323
+
324
+ // Test for zoom.
325
+ expect(s.zoom).toBe(0.5);
326
+ });
327
+
328
+ it('addPassage() - should set start if Start passage and StoryData is not present', function () {
329
+ // Add the passage.
330
+ s.addPassage(new Passage('Start'));
331
+
332
+ // Test for start.
333
+ expect(s.start).toBe('Start');
334
+ });
314
335
  });
315
336
 
316
337
  describe('removePassageByName()', () => {
@@ -726,17 +747,22 @@ describe('Story', () => {
726
747
  expect(result.includes('zoom="2"')).toBe(true);
727
748
  });
728
749
 
729
- it('Should encode start', () => {
750
+ it('Should encode startnode as Start as single and only passage', () => {
730
751
  // Add passage.
731
752
  s.addPassage(new Passage('Start', 'Word'));
732
- // Set start.
733
- s.start = 'Start';
734
753
  // Create HTML.
735
754
  const result = s.toTwine2HTML();
736
755
  // Expect the start to be encoded.
737
756
  expect(result.includes('startnode="1"')).toBe(true);
738
757
  });
739
758
 
759
+ it('Should encode startnode as 0 if no passages', () => {
760
+ // Create HTML.
761
+ const result = s.toTwine2HTML();
762
+ // Expect the start to be encoded.
763
+ expect(result.includes('startnode="0"')).toBe(true);
764
+ });
765
+
740
766
  it('Should encode start if property is not set but Start passage is', () => {
741
767
  // Add passage.
742
768
  s.addPassage(new Passage('Start', 'Word'));
@@ -11,9 +11,9 @@ describe('TWSParser', () => {
11
11
  let r = null;
12
12
 
13
13
  beforeAll(() => {
14
- const contents = fs.readFileSync('test/TWS/TWSParser/Example1.tws', 'binary');
15
- const b = Buffer.from(contents, 'binary');
16
- r = parseTWS(b);
14
+ const contents = fs.readFileSync('test/TWS/TWSParser/Example1.tws');
15
+ //const b = Buffer.from(contents, 'binary');
16
+ r = parseTWS(contents);
17
17
  });
18
18
 
19
19
  it('Should parse StoryTitle', function () {
@@ -15,6 +15,14 @@ describe('Twee', () => {
15
15
  expect(() => { parseTwee('()()'); }).toThrow();
16
16
  });
17
17
 
18
+ it('Should ignore malformed passage metadata and create empty object', () => {
19
+ const fr = readFileSync('test/Twee/TweeParser/malformed.twee', 'utf-8');
20
+ const story = parseTwee(fr);
21
+ const metadata = story.getPassageByName('Start').metadata;
22
+ const numberOfMetadataProperties = Object.keys(metadata).length;
23
+ expect(numberOfMetadataProperties).toBe(0);
24
+ });
25
+
18
26
  it('Should throw error if it detects malformed passage headers', () => {
19
27
  expect(() => { parseTwee('::{}[]\nNo name'); }).toThrow();
20
28
  });
@@ -72,5 +80,14 @@ describe('Twee', () => {
72
80
  const p = story.getPassageByName('StoryAuthor');
73
81
  expect(p).not.toBe(null);
74
82
  });
83
+
84
+ it('Should parse single and only passage Start', () => {
85
+ const fr = readFileSync('test/Twee/TweeParser/start.twee', 'utf-8');
86
+ const story = parseTwee(fr);
87
+ const p = story.getPassageByName('Start');
88
+ const startingPassage = story.start;
89
+ expect(p).not.toBe(null);
90
+ expect(startingPassage).toBe('Start');
91
+ });
75
92
  });
76
93
  });
@@ -0,0 +1,16 @@
1
+ :: StoryTitle
2
+ Cursed
3
+
4
+
5
+ :: StoryData
6
+ {
7
+ "ifid": "22F25A58-7062-4927-95B6-F424DDB2EC65",
8
+ "format": "Harlowe",
9
+ "format-version": "3.3.8",
10
+ "start": "[Hello] {world} \\\\",
11
+ "zoom": 1
12
+ }
13
+
14
+
15
+ :: \[Hello\] \{world\} \\\\ {"position":"400,200","size":"100,100"}
16
+ \:: Extra header
@@ -0,0 +1,2 @@
1
+ :: Start {"position":"353,60""size":"100,100"}
2
+ Malformed metadata.
@@ -0,0 +1,2 @@
1
+ :: Start
2
+ Technically, a valid story.
package/types/Story.d.ts CHANGED
@@ -226,5 +226,5 @@ export class Story {
226
226
  #private;
227
227
  }
228
228
  export const creatorName: "extwee";
229
- export const creatorVersion: "2.2.3";
229
+ export const creatorVersion: "2.2.4";
230
230
  import Passage from './Passage.js';
package/.eslintrc.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "env": {
3
- "browser": true,
4
- "es2021": true,
5
- "node": true,
6
- "jest/globals": true
7
- },
8
- "extends": "standard",
9
- "parserOptions": {
10
- "ecmaVersion": "latest",
11
- "sourceType": "module"
12
- },
13
- "rules": {
14
- "semi": [2, "always"]
15
- },
16
- "plugins": [
17
- "jest"
18
- ]
19
- }