@rohal12/spindle 0.8.0 → 0.10.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.
- package/dist/pkg/format.js +1 -1
- package/package.json +3 -1
- package/src/components/Passage.tsx +3 -2
- package/src/components/macros/Button.tsx +5 -1
- package/src/components/macros/Computed.tsx +6 -2
- package/src/components/macros/Do.tsx +2 -1
- package/src/components/macros/For.tsx +4 -3
- package/src/components/macros/If.tsx +2 -1
- package/src/components/macros/Include.tsx +2 -1
- package/src/components/macros/MacroLink.tsx +9 -2
- package/src/components/macros/Print.tsx +2 -1
- package/src/components/macros/Set.tsx +5 -1
- package/src/components/macros/Switch.tsx +6 -3
- package/src/utils/source-location.ts +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rohal12/spindle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A Preact-based story format for Twine 2.",
|
|
6
6
|
"license": "Unlicense",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"compile": "bun run scripts/compile-story.ts",
|
|
39
39
|
"preview": "bun run build && bun run compile",
|
|
40
40
|
"test": "vitest run",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
41
42
|
"test:watch": "vitest",
|
|
42
43
|
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
43
44
|
"typecheck": "tsc --noEmit",
|
|
@@ -64,6 +65,7 @@
|
|
|
64
65
|
"@preact/preset-vite": "^2.10.3",
|
|
65
66
|
"@rohal12/twee-ts": "^1.1.2",
|
|
66
67
|
"@types/js-yaml": "^4.0.9",
|
|
68
|
+
"@types/react": "^18.3.28",
|
|
67
69
|
"@vitest/coverage-v8": "^4.0.18",
|
|
68
70
|
"happy-dom": "^20.8.3",
|
|
69
71
|
"husky": "^9.1.7",
|
|
@@ -3,6 +3,7 @@ import { tokenize } from '../markup/tokenizer';
|
|
|
3
3
|
import { buildAST } from '../markup/ast';
|
|
4
4
|
import { renderNodes } from '../markup/render';
|
|
5
5
|
import type { Passage as PassageData } from '../parser';
|
|
6
|
+
import { sourceLocationOf } from '../utils/source-location';
|
|
6
7
|
|
|
7
8
|
interface PassageProps {
|
|
8
9
|
passage: PassageData;
|
|
@@ -17,8 +18,8 @@ export function Passage({ passage }: PassageProps) {
|
|
|
17
18
|
} catch (err) {
|
|
18
19
|
return (
|
|
19
20
|
<div class="error">
|
|
20
|
-
Error parsing passage “{passage.name}&rdquo
|
|
21
|
-
{(err as Error).message}
|
|
21
|
+
Error parsing passage “{passage.name}”
|
|
22
|
+
{sourceLocationOf(passage)}: {(err as Error).message}
|
|
22
23
|
</div>
|
|
23
24
|
);
|
|
24
25
|
}
|
|
@@ -6,6 +6,7 @@ import { deepClone } from '../../class-registry';
|
|
|
6
6
|
import { collectText } from '../../utils/extract-text';
|
|
7
7
|
import { useAction } from '../../hooks/use-action';
|
|
8
8
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
9
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
9
10
|
import type { ASTNode } from '../../markup/ast';
|
|
10
11
|
|
|
11
12
|
interface ButtonProps {
|
|
@@ -28,7 +29,10 @@ export function Button({ rawArgs, children, className, id }: ButtonProps) {
|
|
|
28
29
|
try {
|
|
29
30
|
execute(rawArgs, vars, temps, localsClone);
|
|
30
31
|
} catch (err) {
|
|
31
|
-
console.error(
|
|
32
|
+
console.error(
|
|
33
|
+
`spindle: Error in {button ${rawArgs}}${currentSourceLocation()}:`,
|
|
34
|
+
err,
|
|
35
|
+
);
|
|
32
36
|
return;
|
|
33
37
|
}
|
|
34
38
|
|
|
@@ -2,6 +2,7 @@ import { useLayoutEffect } from 'preact/hooks';
|
|
|
2
2
|
import { useStoryStore } from '../../store';
|
|
3
3
|
import { evaluate } from '../../expression';
|
|
4
4
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
5
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
5
6
|
|
|
6
7
|
interface ComputedProps {
|
|
7
8
|
rawArgs: string;
|
|
@@ -71,7 +72,7 @@ export function Computed({ rawArgs }: ComputedProps) {
|
|
|
71
72
|
class="error"
|
|
72
73
|
title={String(err)}
|
|
73
74
|
>
|
|
74
|
-
{`{computed error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
75
|
+
{`{computed error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
75
76
|
</span>
|
|
76
77
|
);
|
|
77
78
|
}
|
|
@@ -86,7 +87,10 @@ export function Computed({ rawArgs }: ComputedProps) {
|
|
|
86
87
|
try {
|
|
87
88
|
newValue = evaluate(expr, mergedVars, mergedTemps, mergedLocals);
|
|
88
89
|
} catch (err) {
|
|
89
|
-
console.error(
|
|
90
|
+
console.error(
|
|
91
|
+
`spindle: Error in {computed ${rawArgs}}${currentSourceLocation()}:`,
|
|
92
|
+
err,
|
|
93
|
+
);
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
96
|
|
|
@@ -5,6 +5,7 @@ import type { ASTNode } from '../../markup/ast';
|
|
|
5
5
|
import { deepClone } from '../../class-registry';
|
|
6
6
|
import { LocalsContext } from '../../markup/render';
|
|
7
7
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
8
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
8
9
|
|
|
9
10
|
interface DoProps {
|
|
10
11
|
children: ASTNode[];
|
|
@@ -31,7 +32,7 @@ export function Do({ children }: DoProps) {
|
|
|
31
32
|
try {
|
|
32
33
|
execute(code, vars, temps, localsClone);
|
|
33
34
|
} catch (err) {
|
|
34
|
-
console.error(`spindle: Error in {do}:`, err);
|
|
35
|
+
console.error(`spindle: Error in {do}${currentSourceLocation()}:`, err);
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -3,6 +3,7 @@ import { evaluate } from '../../expression';
|
|
|
3
3
|
import { LocalsContext, renderNodes } from '../../markup/render';
|
|
4
4
|
import type { LocalsScope } from '../../markup/render';
|
|
5
5
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
6
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
6
7
|
import type { ASTNode } from '../../markup/ast';
|
|
7
8
|
|
|
8
9
|
interface ForProps {
|
|
@@ -87,7 +88,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
87
88
|
class="error"
|
|
88
89
|
title={String(err)}
|
|
89
90
|
>
|
|
90
|
-
{`{for error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
91
|
+
{`{for error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
91
92
|
</span>
|
|
92
93
|
);
|
|
93
94
|
}
|
|
@@ -100,7 +101,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
100
101
|
if (!Array.isArray(result)) {
|
|
101
102
|
return (
|
|
102
103
|
<span class="error">
|
|
103
|
-
{`{for error: expression did not evaluate to an array}`}
|
|
104
|
+
{`{for error${currentSourceLocation()}: expression did not evaluate to an array}`}
|
|
104
105
|
</span>
|
|
105
106
|
);
|
|
106
107
|
}
|
|
@@ -111,7 +112,7 @@ export function For({ rawArgs, children, className, id }: ForProps) {
|
|
|
111
112
|
class="error"
|
|
112
113
|
title={String(err)}
|
|
113
114
|
>
|
|
114
|
-
{`{for error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
115
|
+
{`{for error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
115
116
|
</span>
|
|
116
117
|
);
|
|
117
118
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { evaluate } from '../../expression';
|
|
2
2
|
import { renderNodes } from '../../markup/render';
|
|
3
3
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
4
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
4
5
|
import type { Branch } from '../../markup/ast';
|
|
5
6
|
|
|
6
7
|
interface IfProps {
|
|
@@ -46,7 +47,7 @@ export function If({ branches }: IfProps) {
|
|
|
46
47
|
class="error"
|
|
47
48
|
title={String(err)}
|
|
48
49
|
>
|
|
49
|
-
{`{if error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
50
|
+
{`{if error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
50
51
|
</span>
|
|
51
52
|
);
|
|
52
53
|
}
|
|
@@ -4,6 +4,7 @@ import { tokenize } from '../../markup/tokenizer';
|
|
|
4
4
|
import { buildAST } from '../../markup/ast';
|
|
5
5
|
import { renderNodes } from '../../markup/render';
|
|
6
6
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
7
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
7
8
|
|
|
8
9
|
interface IncludeProps {
|
|
9
10
|
rawArgs: string;
|
|
@@ -31,7 +32,7 @@ export function Include({ rawArgs, className, id }: IncludeProps) {
|
|
|
31
32
|
}
|
|
32
33
|
if (!passage) {
|
|
33
34
|
return (
|
|
34
|
-
<span class="error">{`{include: passage "${passageName}" not found}`}</span>
|
|
35
|
+
<span class="error">{`{include${currentSourceLocation()}: passage "${passageName}" not found}`}</span>
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -5,6 +5,7 @@ import type { ASTNode } from '../../markup/ast';
|
|
|
5
5
|
import { deepClone } from '../../class-registry';
|
|
6
6
|
import { LocalsContext } from '../../markup/render';
|
|
7
7
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
8
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
8
9
|
|
|
9
10
|
interface MacroLinkProps {
|
|
10
11
|
rawArgs: string;
|
|
@@ -56,14 +57,20 @@ function executeChildren(
|
|
|
56
57
|
try {
|
|
57
58
|
execute(node.rawArgs, vars, temps, localsClone);
|
|
58
59
|
} catch (err) {
|
|
59
|
-
console.error(
|
|
60
|
+
console.error(
|
|
61
|
+
`spindle: Error in {link} child {set}${currentSourceLocation()}:`,
|
|
62
|
+
err,
|
|
63
|
+
);
|
|
60
64
|
}
|
|
61
65
|
} else if (node.name === 'do') {
|
|
62
66
|
const code = collectText(node.children);
|
|
63
67
|
try {
|
|
64
68
|
execute(code, vars, temps, localsClone);
|
|
65
69
|
} catch (err) {
|
|
66
|
-
console.error(
|
|
70
|
+
console.error(
|
|
71
|
+
`spindle: Error in {link} child {do}${currentSourceLocation()}:`,
|
|
72
|
+
err,
|
|
73
|
+
);
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { evaluate } from '../../expression';
|
|
2
2
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
3
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
3
4
|
|
|
4
5
|
interface PrintProps {
|
|
5
6
|
rawArgs: string;
|
|
@@ -29,7 +30,7 @@ export function Print({ rawArgs, className, id }: PrintProps) {
|
|
|
29
30
|
class="error"
|
|
30
31
|
title={String(err)}
|
|
31
32
|
>
|
|
32
|
-
{`{print error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
33
|
+
{`{print error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
33
34
|
</span>
|
|
34
35
|
);
|
|
35
36
|
}
|
|
@@ -4,6 +4,7 @@ import { execute } from '../../expression';
|
|
|
4
4
|
import { deepClone } from '../../class-registry';
|
|
5
5
|
import { LocalsContext } from '../../markup/render';
|
|
6
6
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
7
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
7
8
|
|
|
8
9
|
interface SetProps {
|
|
9
10
|
rawArgs: string;
|
|
@@ -22,7 +23,10 @@ export function Set({ rawArgs }: SetProps) {
|
|
|
22
23
|
try {
|
|
23
24
|
execute(rawArgs, vars, temps, localsClone);
|
|
24
25
|
} catch (err) {
|
|
25
|
-
console.error(
|
|
26
|
+
console.error(
|
|
27
|
+
`spindle: Error in {set ${rawArgs}}${currentSourceLocation()}:`,
|
|
28
|
+
err,
|
|
29
|
+
);
|
|
26
30
|
return;
|
|
27
31
|
}
|
|
28
32
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { evaluate } from '../../expression';
|
|
2
2
|
import { renderNodes } from '../../markup/render';
|
|
3
3
|
import { useMergedLocals } from '../../hooks/use-merged-locals';
|
|
4
|
+
import { currentSourceLocation } from '../../utils/source-location';
|
|
4
5
|
import type { Branch } from '../../markup/ast';
|
|
5
6
|
|
|
6
7
|
interface SwitchProps {
|
|
@@ -20,14 +21,16 @@ export function Switch({ rawArgs, branches }: SwitchProps) {
|
|
|
20
21
|
class="error"
|
|
21
22
|
title={String(err)}
|
|
22
23
|
>
|
|
23
|
-
{`{switch error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
24
|
+
{`{switch error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
24
25
|
</span>
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
// Find matching {case} branch or {default}
|
|
30
|
+
// Skip first branch (index 0) — it holds the switch expression, not a case
|
|
29
31
|
let defaultBranch: Branch | null = null;
|
|
30
|
-
for (
|
|
32
|
+
for (let i = 1; i < branches.length; i++) {
|
|
33
|
+
const branch = branches[i]!;
|
|
31
34
|
// {default} has empty rawArgs
|
|
32
35
|
if (branch.rawArgs === '') {
|
|
33
36
|
defaultBranch = branch;
|
|
@@ -50,7 +53,7 @@ export function Switch({ rawArgs, branches }: SwitchProps) {
|
|
|
50
53
|
class="error"
|
|
51
54
|
title={String(err)}
|
|
52
55
|
>
|
|
53
|
-
{`{case error: ${err instanceof Error ? err.message : String(err)}}`}
|
|
56
|
+
{`{case error${currentSourceLocation()}: ${err instanceof Error ? err.message : String(err)}}`}
|
|
54
57
|
</span>
|
|
55
58
|
);
|
|
56
59
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Passage } from '../parser';
|
|
2
|
+
import { useStoryStore } from '../store';
|
|
3
|
+
|
|
4
|
+
/** Returns e.g. " (story.twee:5)" or "" if not available. */
|
|
5
|
+
export function sourceLocationOf(passage: Passage): string {
|
|
6
|
+
const file = passage.metadata['data-source-file'];
|
|
7
|
+
const line = passage.metadata['data-source-line'];
|
|
8
|
+
if (!file) return '';
|
|
9
|
+
return line ? ` (${file}:${line})` : ` (${file})`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Look up current passage from store and return its source location. */
|
|
13
|
+
export function currentSourceLocation(): string {
|
|
14
|
+
const state = useStoryStore.getState();
|
|
15
|
+
if (!state.storyData) return '';
|
|
16
|
+
const passage = state.storyData.passages.get(state.currentPassage);
|
|
17
|
+
if (!passage) return '';
|
|
18
|
+
return sourceLocationOf(passage);
|
|
19
|
+
}
|