@turri-dev/cli 0.1.27-next.763d091 → 0.1.28-next.d5f2680
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 +6 -6
- package/dist/advisor-chat.d.ts +63 -0
- package/dist/advisor-chat.js +290 -0
- package/dist/background-skills.d.ts +53 -0
- package/dist/background-skills.js +121 -0
- package/dist/bedrock-region.d.ts +66 -0
- package/dist/bedrock-region.js +144 -0
- package/dist/btw.d.ts +1 -1
- package/dist/btw.js +1 -1
- package/dist/change-plain-cli.js +20 -13
- package/dist/chat-idle.d.ts +7 -0
- package/dist/chat-idle.js +20 -0
- package/dist/chats.d.ts +10 -0
- package/dist/chats.js +23 -0
- package/dist/cli-options.d.ts +1 -2
- package/dist/cli-options.js +2 -7
- package/dist/cli.js +11 -3
- package/dist/config-command.d.ts +14 -0
- package/dist/config-command.js +35 -15
- package/dist/config-edit.d.ts +3 -3
- package/dist/config-edit.js +16 -12
- package/dist/config.d.ts +54 -34
- package/dist/config.js +40 -44
- package/dist/conform-cli.js +12 -10
- package/dist/conformance-switch.d.ts +34 -0
- package/dist/conformance-switch.js +108 -0
- package/dist/cultivate-cli.js +15 -5
- package/dist/drift-cli.js +2 -0
- package/dist/extension.d.ts +15 -0
- package/dist/extension.js +260 -83
- package/dist/host-session.d.ts +26 -0
- package/dist/host-session.js +30 -2
- package/dist/host.d.ts +5 -2
- package/dist/host.js +7 -1
- package/dist/init-config.d.ts +1 -1
- package/dist/init-config.js +20 -11
- package/dist/loop-cli.d.ts +3 -3
- package/dist/loop-cli.js +13 -16
- package/dist/loop-tools.js +18 -10
- package/dist/model-check.d.ts +8 -3
- package/dist/model-check.js +13 -8
- package/dist/new-project.js +12 -8
- package/dist/{validate-cli.js → parse-cli.js} +11 -5
- package/dist/parse-tool.d.ts +9 -0
- package/dist/parse-tool.js +33 -0
- package/dist/pi-launch.js +1 -1
- package/dist/plain/check.d.ts +53 -6
- package/dist/plain/check.js +217 -24
- package/dist/plain/concepts.d.ts +8 -0
- package/dist/plain/concepts.js +66 -23
- package/dist/plain/cycles.d.ts +21 -0
- package/dist/plain/cycles.js +190 -0
- package/dist/plain/errors.d.ts +4 -0
- package/dist/plain/errors.js +7 -0
- package/dist/plain/files.d.ts +18 -1
- package/dist/plain/files.js +39 -2
- package/dist/plain/graph.d.ts +33 -4
- package/dist/plain/graph.js +60 -12
- package/dist/plain/highlight.d.ts +3 -2
- package/dist/plain/highlight.js +4 -3
- package/dist/plain/index.d.ts +8 -5
- package/dist/plain/index.js +8 -5
- package/dist/plain/liquid.js +1 -6
- package/dist/plain/markdown.d.ts +32 -3
- package/dist/plain/markdown.js +146 -24
- package/dist/plain/module-concepts.js +2 -2
- package/dist/plain/parser.d.ts +83 -28
- package/dist/plain/parser.js +381 -260
- package/dist/plain/plain.tmLanguage.json +29 -3
- package/dist/plain/project.d.ts +48 -0
- package/dist/plain/project.js +155 -0
- package/dist/plain/spec.d.ts +90 -28
- package/dist/plain/spec.js +107 -133
- package/dist/plain/test-plain-file.d.ts +59 -0
- package/dist/plain/test-plain-file.js +232 -0
- package/dist/regenerate/activity.d.ts +20 -9
- package/dist/regenerate/activity.js +11 -4
- package/dist/regenerate/advisor-records.d.ts +93 -0
- package/dist/regenerate/advisor-records.js +110 -0
- package/dist/regenerate/advisor-runs.d.ts +87 -0
- package/dist/regenerate/advisor-runs.js +327 -0
- package/dist/regenerate/base-folder.d.ts +68 -0
- package/dist/regenerate/base-folder.js +168 -0
- package/dist/regenerate/cases.d.ts +9 -6
- package/dist/regenerate/cases.js +71 -15
- package/dist/regenerate/change-view.d.ts +8 -3
- package/dist/regenerate/change-view.js +71 -22
- package/dist/regenerate/chunk-plan.d.ts +36 -15
- package/dist/regenerate/chunk-plan.js +165 -59
- package/dist/regenerate/conform.d.ts +3 -0
- package/dist/regenerate/conform.js +6 -5
- package/dist/regenerate/conformance-agents.d.ts +18 -39
- package/dist/regenerate/conformance-agents.js +60 -88
- package/dist/regenerate/conformance.d.ts +23 -20
- package/dist/regenerate/conformance.js +118 -121
- package/dist/regenerate/diff-view.d.ts +1 -1
- package/dist/regenerate/diff-view.js +20 -4
- package/dist/regenerate/drift.d.ts +47 -15
- package/dist/regenerate/drift.js +101 -30
- package/dist/regenerate/jobs.d.ts +2 -2
- package/dist/regenerate/jobs.js +1 -1
- package/dist/regenerate/ledger.d.ts +19 -0
- package/dist/regenerate/loop.d.ts +61 -49
- package/dist/regenerate/loop.js +473 -340
- package/dist/regenerate/paths.d.ts +4 -0
- package/dist/regenerate/paths.js +7 -1
- package/dist/regenerate/plain-palette.js +2 -0
- package/dist/regenerate/prompt.d.ts +57 -19
- package/dist/regenerate/prompt.js +157 -122
- package/dist/regenerate/records.d.ts +43 -12
- package/dist/regenerate/records.js +102 -88
- package/dist/regenerate/regenerate.js +29 -11
- package/dist/regenerate/regression.d.ts +4 -4
- package/dist/regenerate/regression.js +14 -10
- package/dist/regenerate/renames.d.ts +42 -0
- package/dist/regenerate/renames.js +185 -0
- package/dist/regenerate/requires.d.ts +1 -1
- package/dist/regenerate/requires.js +10 -3
- package/dist/regenerate/resources.d.ts +5 -3
- package/dist/regenerate/resources.js +4 -4
- package/dist/regenerate/scripts-being-written.d.ts +19 -0
- package/dist/regenerate/scripts-being-written.js +30 -0
- package/dist/regenerate/spec-map-view.d.ts +2 -2
- package/dist/regenerate/spec-map-view.js +46 -15
- package/dist/regenerate/spec-map.d.ts +27 -5
- package/dist/regenerate/spec-map.js +92 -60
- package/dist/regenerate/spec-steps.d.ts +48 -5
- package/dist/regenerate/spec-steps.js +213 -28
- package/dist/regenerate/state.d.ts +4 -3
- package/dist/regenerate/stop-report.d.ts +1 -1
- package/dist/regenerate/stop-report.js +10 -10
- package/dist/regenerate/test-advisor.d.ts +101 -0
- package/dist/regenerate/test-advisor.js +379 -0
- package/dist/regenerate/test-kinds.d.ts +69 -31
- package/dist/regenerate/test-kinds.js +109 -80
- package/dist/regenerate/test-scripts.d.ts +2 -0
- package/dist/regenerate/test-scripts.js +4 -0
- package/dist/regenerate/testing-steps.d.ts +41 -0
- package/dist/regenerate/testing-steps.js +110 -0
- package/dist/regenerate/testing.d.ts +1 -1
- package/dist/regenerate/testing.js +5 -4
- package/dist/regenerate/unit-review.d.ts +1 -1
- package/dist/regenerate/unit-review.js +3 -3
- package/dist/regenerate-cli.js +11 -12
- package/dist/release-agent.js +4 -0
- package/dist/release-cli.js +2 -0
- package/dist/release-tools.js +2 -0
- package/dist/run-report.d.ts +2 -2
- package/dist/run-report.js +4 -3
- package/dist/self-invocation.d.ts +1 -1
- package/dist/self-invocation.js +1 -1
- package/dist/sentinel.js +1 -1
- package/dist/setup.d.ts +10 -0
- package/dist/setup.js +52 -4
- package/dist/skill.d.ts +10 -0
- package/dist/skill.js +6 -0
- package/dist/skills.js +3 -1
- package/dist/spec-tools.js +21 -31
- package/dist/test-plain-files.d.ts +41 -0
- package/dist/test-plain-files.js +177 -0
- package/docs/README.md +3 -1
- package/docs/authoring-plain.md +46 -13
- package/docs/chats.md +3 -1
- package/docs/config.md +20 -15
- package/docs/deploy.md +1 -1
- package/docs/development.md +2 -1
- package/docs/headless.md +15 -18
- package/docs/hosting.md +28 -12
- package/docs/installation.md +1 -0
- package/docs/parser.md +88 -27
- package/docs/regenerating.md +89 -54
- package/docs/sentinel.md +1 -1
- package/docs/skills-and-gates.md +3 -1
- package/docs/test-advisor.md +110 -0
- package/docs/theming.md +1 -1
- package/docs/using.md +17 -15
- package/genome/rules/bullet-continuation.md +8 -8
- package/genome/rules/concise-specs-examples.md +111 -80
- package/genome/rules/concise-specs-integration-examples.md +56 -41
- package/genome/rules/concise-specs.md +17 -16
- package/genome/rules/definitions.md +5 -5
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +1 -1
- package/genome/rules/func-specs.md +69 -74
- package/genome/rules/impl-reqs.md +4 -4
- package/genome/rules/integration-embedded-testing.md +20 -20
- package/genome/rules/integration-embedded.md +31 -31
- package/genome/rules/integration-standalone.md +9 -9
- package/genome/rules/integrations.md +18 -18
- package/genome/rules/linked-resources.md +19 -16
- package/genome/rules/module-structure.md +21 -14
- package/genome/rules/project-model.md +18 -17
- package/genome/rules/required-concepts.md +1 -1
- package/genome/rules/requires-modules.md +6 -5
- package/genome/rules/semantic-ids.md +49 -0
- package/genome/rules/stdlib.md +4 -5
- package/genome/rules/test-plain-files.md +130 -0
- package/genome/rules/test-reqs.md +8 -8
- package/genome/rules/turri-regeneration.md +24 -8
- package/genome/skills/analyze-2-func-specs/SKILL.md +36 -21
- package/genome/skills/analyze-func-specs/SKILL.md +46 -32
- package/genome/skills/break-down-func-spec/SKILL.md +91 -67
- package/genome/skills/consolidate-concepts/SKILL.md +10 -10
- package/genome/skills/convert-old-syntax/SKILL.md +68 -0
- package/genome/skills/convert-old-syntax/references/rubric.md +11 -0
- package/genome/skills/debug-specs/SKILL.md +62 -56
- package/genome/skills/implement-conformance-testing-script/SKILL.md +19 -15
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +2 -2
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +2 -2
- package/genome/skills/implement-unit-testing-script/SKILL.md +14 -10
- package/genome/skills/refactor-module/SKILL.md +35 -35
- package/genome/skills/resolve-section-ownership/SKILL.md +9 -9
- package/genome/skills/resolve-spec-conflict/SKILL.md +35 -29
- package/package.json +7 -6
- package/dist/regenerate/choose-conformance.d.ts +0 -32
- package/dist/regenerate/choose-conformance.js +0 -117
- /package/dist/{validate-cli.d.ts → parse-cli.d.ts} +0 -0
package/dist/plain/markdown.d.ts
CHANGED
|
@@ -6,11 +6,14 @@
|
|
|
6
6
|
* us the first. The second is `renderToMarkdown` below - a serializer for the
|
|
7
7
|
* subset of markdown the plain language uses, matching mistletoe's output
|
|
8
8
|
* (source indentation preserved, links rewritten as `[resource]` spans).
|
|
9
|
+
*
|
|
10
|
+
* The reference parses inside the renderer's context, so blank lines are tokens of
|
|
11
|
+
* their own (BlankLine), one per blank line, and survive in what is rendered; they
|
|
12
|
+
* are recovered here from the source.
|
|
9
13
|
*/
|
|
10
14
|
export declare const RESOURCE_MARKER = "[resource]";
|
|
11
15
|
/** Extra fields the parser hangs off AST nodes, as the Python code hangs them off tokens. */
|
|
12
16
|
export interface PlainAnnotations {
|
|
13
|
-
acceptance_tests?: AnyNode[];
|
|
14
17
|
linked_resources?: LinkedResource[];
|
|
15
18
|
/** Set when `value` holds the original source slice rather than mdast's decoded text. */
|
|
16
19
|
raw?: boolean;
|
|
@@ -20,6 +23,19 @@ export interface PlainAnnotations {
|
|
|
20
23
|
leader?: string;
|
|
21
24
|
indentation?: number;
|
|
22
25
|
prepend?: number;
|
|
26
|
+
/**
|
|
27
|
+
* The blank lines in the source before a block, after the block before it: one run, and one more for each comment
|
|
28
|
+
* dropped in between - mistletoe's BlankLine tokens, one per blank line.
|
|
29
|
+
*/
|
|
30
|
+
blankRuns?: number[];
|
|
31
|
+
/** A thematic break's line, or a setext heading's underline, as its container holds it. */
|
|
32
|
+
sourceLine?: string;
|
|
33
|
+
/** A fenced code block's opening: its indentation, its delimiter and its info string, as written. */
|
|
34
|
+
fence?: {
|
|
35
|
+
indentation: number;
|
|
36
|
+
delimiter: string;
|
|
37
|
+
infoString: string;
|
|
38
|
+
};
|
|
23
39
|
}
|
|
24
40
|
export interface LinkedResource {
|
|
25
41
|
text: string;
|
|
@@ -60,15 +76,28 @@ export interface Section {
|
|
|
60
76
|
* loses the fact that it was not `*`. mistletoe's MarkdownRenderer round-trips the
|
|
61
77
|
* source instead, so re-serialising a decoded tree drifts from the Python output.
|
|
62
78
|
* Recording the raw slice for leaf nodes and the real delimiters for emphasis and
|
|
63
|
-
* strong keeps the two renderers in agreement.
|
|
79
|
+
* strong keeps the two renderers in agreement. Tables are GFM's, as mistletoe parses them.
|
|
64
80
|
*/
|
|
65
81
|
export declare function parseMarkdown(text: string): Root;
|
|
66
82
|
export declare function lineNumber(node: AnyNode): number;
|
|
67
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Port of plain_file.remove_quotes: block quotes are comments and are dropped. The blank
|
|
85
|
+
* lines around a dropped one stay, as mistletoe's BlankLine tokens do.
|
|
86
|
+
*/
|
|
68
87
|
export declare function removeQuotes(node: AnyNode): void;
|
|
69
88
|
/** Port of plain_file.get_raw_text. */
|
|
70
89
|
export declare function getRawText(node: AnyNode): string;
|
|
71
90
|
/** Port of mistletoe.utils.traverse restricted to a node type. */
|
|
72
91
|
export declare function traverse(node: AnyNode, type: string): AnyNode[];
|
|
92
|
+
/**
|
|
93
|
+
* Port of MarkdownRenderer.blocks_to_lines. Blocks are concatenated with no separator;
|
|
94
|
+
* blank lines are their own tokens in mistletoe, and are recovered here from the source.
|
|
95
|
+
*/
|
|
96
|
+
export declare function blocksToLines(nodes: AnyNode[]): string[];
|
|
73
97
|
/** Port of PlainRenderer.render. */
|
|
74
98
|
export declare function renderToMarkdown(node: AnyNode): string;
|
|
99
|
+
/**
|
|
100
|
+
* Port of plain_file.render_tokens: blocks rendered as one markdown text, without leading
|
|
101
|
+
* and trailing blank lines.
|
|
102
|
+
*/
|
|
103
|
+
export declare function renderTokens(nodes: AnyNode[]): string;
|
package/dist/plain/markdown.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
import { gfmTableFromMarkdown } from "mdast-util-gfm-table";
|
|
3
|
+
import { gfmTable } from "micromark-extension-gfm-table";
|
|
2
4
|
/**
|
|
3
5
|
* The markdown layer.
|
|
4
6
|
*
|
|
@@ -7,6 +9,10 @@ import { fromMarkdown } from "mdast-util-from-markdown";
|
|
|
7
9
|
* us the first. The second is `renderToMarkdown` below - a serializer for the
|
|
8
10
|
* subset of markdown the plain language uses, matching mistletoe's output
|
|
9
11
|
* (source indentation preserved, links rewritten as `[resource]` spans).
|
|
12
|
+
*
|
|
13
|
+
* The reference parses inside the renderer's context, so blank lines are tokens of
|
|
14
|
+
* their own (BlankLine), one per blank line, and survive in what is rendered; they
|
|
15
|
+
* are recovered here from the source.
|
|
10
16
|
*/
|
|
11
17
|
export const RESOURCE_MARKER = "[resource]";
|
|
12
18
|
/**
|
|
@@ -16,17 +22,35 @@ export const RESOURCE_MARKER = "[resource]";
|
|
|
16
22
|
* loses the fact that it was not `*`. mistletoe's MarkdownRenderer round-trips the
|
|
17
23
|
* source instead, so re-serialising a decoded tree drifts from the Python output.
|
|
18
24
|
* Recording the raw slice for leaf nodes and the real delimiters for emphasis and
|
|
19
|
-
* strong keeps the two renderers in agreement.
|
|
25
|
+
* strong keeps the two renderers in agreement. Tables are GFM's, as mistletoe parses them.
|
|
20
26
|
*/
|
|
21
27
|
export function parseMarkdown(text) {
|
|
22
|
-
const root = fromMarkdown(text);
|
|
23
|
-
pinToSource(root, text);
|
|
28
|
+
const root = fromMarkdown(text, { extensions: [gfmTable()], mdastExtensions: [gfmTableFromMarkdown()] });
|
|
29
|
+
pinToSource(root, text, 1, text.split("\n"));
|
|
24
30
|
return root;
|
|
25
31
|
}
|
|
26
32
|
const RAW_LEAF_TYPES = new Set(["text", "inlineCode", "html"]);
|
|
27
|
-
|
|
33
|
+
/** The containers whose children are blocks, with blank lines between them as mistletoe keeps them. */
|
|
34
|
+
const BLOCK_CONTAINERS = new Set(["root", "list", "listItem", "blockquote"]);
|
|
35
|
+
/** The whitespace-only lines strictly between two source lines. */
|
|
36
|
+
function blankLinesWithin(lines, afterLine, beforeLine) {
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (let line = afterLine + 1; line < beforeLine; line += 1)
|
|
39
|
+
if ((lines[line - 1] ?? "").trim() === "")
|
|
40
|
+
count += 1;
|
|
41
|
+
return count;
|
|
42
|
+
}
|
|
43
|
+
function pinToSource(node, source, origin, lines) {
|
|
28
44
|
const start = node.position?.start.offset;
|
|
29
45
|
const end = node.position?.end.offset;
|
|
46
|
+
if (BLOCK_CONTAINERS.has(node.type)) {
|
|
47
|
+
const children = childrenOf(node);
|
|
48
|
+
children.forEach((child, index) => {
|
|
49
|
+
const previousEnd = children[index - 1]?.position?.end.line;
|
|
50
|
+
const childStart = child.position?.start.line;
|
|
51
|
+
child.blankRuns = [previousEnd === undefined || childStart === undefined ? 0 : blankLinesWithin(lines, previousEnd, childStart)];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
30
54
|
if (start !== undefined && end !== undefined) {
|
|
31
55
|
if (RAW_LEAF_TYPES.has(node.type)) {
|
|
32
56
|
const raw = source.slice(start, end);
|
|
@@ -41,6 +65,23 @@ function pinToSource(node, source, origin = 1) {
|
|
|
41
65
|
node.openDelimiter = source.slice(start, start + width);
|
|
42
66
|
node.closeDelimiter = source.slice(end - width, end);
|
|
43
67
|
}
|
|
68
|
+
else if (node.type === "thematicBreak") {
|
|
69
|
+
// mistletoe keeps the line as written, with its indentation in the container.
|
|
70
|
+
node.sourceLine = (lines[node.position.start.line - 1] ?? "").slice(origin - 1);
|
|
71
|
+
}
|
|
72
|
+
else if (node.type === "heading" && source[start] !== "#") {
|
|
73
|
+
// A setext heading: mistletoe keeps its underline as written.
|
|
74
|
+
node.sourceLine = (lines[node.position.end.line - 1] ?? "").slice(origin - 1).trimEnd();
|
|
75
|
+
}
|
|
76
|
+
else if (node.type === "code") {
|
|
77
|
+
const opening = /^(`{3,}|~{3,})([^\n]*)/.exec(source.slice(start, end));
|
|
78
|
+
if (opening !== null)
|
|
79
|
+
node.fence = { indentation: column(node) - origin, delimiter: opening[1], infoString: opening[2] };
|
|
80
|
+
}
|
|
81
|
+
else if (node.type === "definition") {
|
|
82
|
+
node.value = source.slice(start, end);
|
|
83
|
+
node.raw = true;
|
|
84
|
+
}
|
|
44
85
|
}
|
|
45
86
|
let childOrigin = origin;
|
|
46
87
|
if (node.type === "listItem") {
|
|
@@ -61,7 +102,7 @@ function pinToSource(node, source, origin = 1) {
|
|
|
61
102
|
childOrigin = contentColumn;
|
|
62
103
|
}
|
|
63
104
|
for (const child of childrenOf(node))
|
|
64
|
-
pinToSource(child, source, childOrigin);
|
|
105
|
+
pinToSource(child, source, childOrigin, lines);
|
|
65
106
|
}
|
|
66
107
|
export function lineNumber(node) {
|
|
67
108
|
return node.position?.start.line ?? 0;
|
|
@@ -75,14 +116,24 @@ function isParent(node) {
|
|
|
75
116
|
function childrenOf(node) {
|
|
76
117
|
return node.children ?? [];
|
|
77
118
|
}
|
|
78
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* Port of plain_file.remove_quotes: block quotes are comments and are dropped. The blank
|
|
121
|
+
* lines around a dropped one stay, as mistletoe's BlankLine tokens do.
|
|
122
|
+
*/
|
|
79
123
|
export function removeQuotes(node) {
|
|
80
124
|
if (!isParent(node))
|
|
81
125
|
return;
|
|
82
126
|
const kept = [];
|
|
127
|
+
let dropped = [];
|
|
83
128
|
for (const child of node.children) {
|
|
84
|
-
if (child.type === "blockquote")
|
|
129
|
+
if (child.type === "blockquote") {
|
|
130
|
+
dropped = [...dropped, ...(child.blankRuns ?? [0])];
|
|
85
131
|
continue;
|
|
132
|
+
}
|
|
133
|
+
if (dropped.length > 0) {
|
|
134
|
+
child.blankRuns = [...dropped, ...(child.blankRuns ?? [0])];
|
|
135
|
+
dropped = [];
|
|
136
|
+
}
|
|
86
137
|
removeQuotes(child);
|
|
87
138
|
kept.push(child);
|
|
88
139
|
}
|
|
@@ -98,6 +149,8 @@ export function getRawText(node) {
|
|
|
98
149
|
const value = node.value;
|
|
99
150
|
if (value !== undefined)
|
|
100
151
|
return value;
|
|
152
|
+
if (node.type === "thematicBreak")
|
|
153
|
+
return "";
|
|
101
154
|
throw new Error(`Unknown token type: ${node.type}`);
|
|
102
155
|
}
|
|
103
156
|
/** Port of mistletoe.utils.traverse restricted to a node type. */
|
|
@@ -140,13 +193,24 @@ function renderInline(node) {
|
|
|
140
193
|
function renderInlineChildren(node) {
|
|
141
194
|
return childrenOf(node).map(renderInline).join("");
|
|
142
195
|
}
|
|
143
|
-
/**
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
196
|
+
/** Whether a block ends with a list: a list, or a list item whose last block does. */
|
|
197
|
+
function endsWithList(node) {
|
|
198
|
+
if (node.type === "list")
|
|
199
|
+
return true;
|
|
200
|
+
const last = childrenOf(node).at(-1);
|
|
201
|
+
return node.type === "listItem" && last !== undefined && endsWithList(last);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The blank lines mistletoe keeps between two sibling blocks: one per blank line in the
|
|
205
|
+
* source, comment lines dropped in between counted out. A list item that ends without a
|
|
206
|
+
* next item gives back only the last of the blank lines after it (`ListItem.read` steps
|
|
207
|
+
* back one line), so the run right after a list counts once.
|
|
208
|
+
*/
|
|
209
|
+
function blankLinesBefore(previous, node) {
|
|
210
|
+
const runs = [...(node.blankRuns ?? [0])];
|
|
211
|
+
if (endsWithList(previous))
|
|
212
|
+
runs[0] = Math.min(runs[0], 1);
|
|
213
|
+
return runs.reduce((sum, run) => sum + run, 0);
|
|
150
214
|
}
|
|
151
215
|
/** Port of MarkdownRenderer.prefix_lines. */
|
|
152
216
|
function prefixLines(lines, firstPrefix, followingPrefix) {
|
|
@@ -157,14 +221,14 @@ function prefixLines(lines, firstPrefix, followingPrefix) {
|
|
|
157
221
|
}
|
|
158
222
|
/**
|
|
159
223
|
* Port of MarkdownRenderer.blocks_to_lines. Blocks are concatenated with no separator;
|
|
160
|
-
* blank lines are their own tokens in mistletoe, and are recovered here from the
|
|
161
|
-
* between sibling positions.
|
|
224
|
+
* blank lines are their own tokens in mistletoe, and are recovered here from the source.
|
|
162
225
|
*/
|
|
163
|
-
function blocksToLines(nodes) {
|
|
226
|
+
export function blocksToLines(nodes) {
|
|
164
227
|
const lines = [];
|
|
165
228
|
nodes.forEach((node, index) => {
|
|
166
|
-
if (index > 0
|
|
167
|
-
|
|
229
|
+
if (index > 0)
|
|
230
|
+
for (let blank = blankLinesBefore(nodes[index - 1], node); blank > 0; blank -= 1)
|
|
231
|
+
lines.push("");
|
|
168
232
|
lines.push(...renderBlockLines(node));
|
|
169
233
|
});
|
|
170
234
|
return lines;
|
|
@@ -180,16 +244,67 @@ function renderListItemLines(item) {
|
|
|
180
244
|
" ".repeat(Math.max(0, prepend - leader.length - indentation));
|
|
181
245
|
return prefixLines(lines.length > 0 ? lines : [""], firstPrefix, " ".repeat(Math.max(0, prepend)));
|
|
182
246
|
}
|
|
247
|
+
/** mistletoe's alignment of a table column: null for left or none, 0 for centre, 1 for right. */
|
|
248
|
+
function alignOf(align) {
|
|
249
|
+
return align === "center" ? 0 : align === "right" ? 1 : null;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Port of MarkdownRenderer.render_table: each cell re-rendered with its pipes escaped,
|
|
253
|
+
* every column as wide as its widest cell and at least three, padded by its alignment,
|
|
254
|
+
* and the separator row rebuilt from the alignments.
|
|
255
|
+
*/
|
|
256
|
+
function renderTableLines(node) {
|
|
257
|
+
const aligns = (node.align ?? []).map(alignOf);
|
|
258
|
+
const rows = childrenOf(node).map((row) => childrenOf(row).map((cell) => (renderInlineChildren(cell).split("\n")[0] ?? "").replace(/\\\|/g, "|").replace(/\|/g, "\\|")));
|
|
259
|
+
const [header = [], ...body] = rows;
|
|
260
|
+
const columns = Math.max(aligns.length, ...rows.map((row) => row.length));
|
|
261
|
+
const widths = Array.from({ length: columns }, (_, index) => Math.max(3, ...rows.map((row) => (row[index] ?? "").length)));
|
|
262
|
+
const lineOf = (cells) => "| " +
|
|
263
|
+
widths
|
|
264
|
+
.map((width, index) => {
|
|
265
|
+
const text = cells[index] ?? "";
|
|
266
|
+
const align = aligns[index] ?? null;
|
|
267
|
+
if (align === null)
|
|
268
|
+
return text.padEnd(width);
|
|
269
|
+
if (align === 1)
|
|
270
|
+
return text.padStart(width);
|
|
271
|
+
const left = Math.floor((width - text.length) / 2);
|
|
272
|
+
return " ".repeat(Math.max(0, left)) + text + " ".repeat(Math.max(0, width - text.length - left));
|
|
273
|
+
})
|
|
274
|
+
.join(" | ") +
|
|
275
|
+
" |";
|
|
276
|
+
const separator = widths.map((width, index) => {
|
|
277
|
+
const align = aligns[index] ?? null;
|
|
278
|
+
return (align === 0 ? ":" : "-") + "-".repeat(width - 2) + (align === 0 || align === 1 ? ":" : "-");
|
|
279
|
+
});
|
|
280
|
+
return [lineOf(header), `| ${separator.join(" | ")} |`, ...body.map(lineOf)];
|
|
281
|
+
}
|
|
183
282
|
function renderBlockLines(node) {
|
|
184
283
|
switch (node.type) {
|
|
185
284
|
case "paragraph":
|
|
186
285
|
return renderInlineChildren(node).split("\n");
|
|
187
|
-
case "heading":
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
286
|
+
case "heading": {
|
|
287
|
+
// A setext heading keeps its underline; an ATX one is its hashes and its text.
|
|
288
|
+
if (node.sourceLine !== undefined)
|
|
289
|
+
return [...renderInlineChildren(node).split("\n"), node.sourceLine];
|
|
290
|
+
const text = renderInlineChildren(node);
|
|
291
|
+
return [`${"#".repeat(node.depth)}${text ? ` ${text}` : ""}`];
|
|
292
|
+
}
|
|
293
|
+
case "code": {
|
|
294
|
+
const content = (node.value ?? "").split("\n");
|
|
295
|
+
if (node.fence === undefined)
|
|
296
|
+
return prefixLines(content, " ", " ");
|
|
297
|
+
const indentation = " ".repeat(Math.max(0, node.fence.indentation));
|
|
298
|
+
return [
|
|
299
|
+
indentation + node.fence.delimiter + node.fence.infoString,
|
|
300
|
+
...prefixLines(content, indentation, indentation),
|
|
301
|
+
indentation + node.fence.delimiter,
|
|
302
|
+
];
|
|
303
|
+
}
|
|
191
304
|
case "thematicBreak":
|
|
192
|
-
return ["---"];
|
|
305
|
+
return [node.sourceLine ?? "---"];
|
|
306
|
+
case "table":
|
|
307
|
+
return renderTableLines(node);
|
|
193
308
|
case "blockquote":
|
|
194
309
|
return blocksToLines(childrenOf(node)).map((line) => (line ? `> ${line}` : ">"));
|
|
195
310
|
case "list":
|
|
@@ -206,3 +321,10 @@ function renderBlockLines(node) {
|
|
|
206
321
|
export function renderToMarkdown(node) {
|
|
207
322
|
return renderBlockLines(node).join("\n");
|
|
208
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Port of plain_file.render_tokens: blocks rendered as one markdown text, without leading
|
|
326
|
+
* and trailing blank lines.
|
|
327
|
+
*/
|
|
328
|
+
export function renderTokens(nodes) {
|
|
329
|
+
return blocksToLines(nodes).join("\n").replace(/^\n+|\n+$/g, "");
|
|
330
|
+
}
|
|
@@ -3,7 +3,7 @@ import { basename } from "node:path";
|
|
|
3
3
|
import { resolveTemplateDirs } from "../regenerate/paths.js";
|
|
4
4
|
import { DEFAULT_CONCEPT_DEFINITIONS, definitionText, extractConceptsFromDefinition } from "./concepts.js";
|
|
5
5
|
import { renderToMarkdown } from "./markdown.js";
|
|
6
|
-
import { exportedDefinitionsOf, IMPORT_DIRECTIVE, parsePlainFile, parsePlainSource, PLAIN_SOURCE_FILE_EXTENSION, readPlainSourceMetadata, REQUIRES_DIRECTIVE, restoreStrippedLines, } from "./parser.js";
|
|
6
|
+
import { exportedDefinitionsOf, IMPORT_DIRECTIVE, parsePlainFile, parsePlainSource, PLAIN_SOURCE_FILE_EXTENSION, readPlainSourceMetadata, REQUIRES_DIRECTIVE, restoreStrippedLines, sectionOf, } from "./parser.js";
|
|
7
7
|
import { DEFINITIONS } from "./spec.js";
|
|
8
8
|
/**
|
|
9
9
|
* A module read for what it can name, as the parser builds its definitions: its own, its imports' (`import`), and its
|
|
@@ -17,7 +17,7 @@ export function readModule(moduleName, plainFile, templateDirs) {
|
|
|
17
17
|
for (const node of nodes)
|
|
18
18
|
definitions.push({ node, module: result.importedFrom.get(node) ?? module });
|
|
19
19
|
};
|
|
20
|
-
const definitionsOf = (result) => result.plainSource
|
|
20
|
+
const definitionsOf = (result) => sectionOf(result.plainSource, DEFINITIONS)?.children ?? [];
|
|
21
21
|
let requires;
|
|
22
22
|
let parsed = parse(moduleName);
|
|
23
23
|
if (parsed !== undefined) {
|
package/dist/plain/parser.d.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
|
-
import { type AnyNode, type Section } from "./markdown.ts";
|
|
2
|
-
import { type
|
|
1
|
+
import { type AnyNode, type LinkedResource, type Section } from "./markdown.ts";
|
|
2
|
+
import { type CodeVariable, type FunctionalSpecItem, type MarshalledPlainSource } from "./spec.ts";
|
|
3
3
|
export declare const IMPORT_DIRECTIVE = "import";
|
|
4
4
|
export declare const REQUIRES_DIRECTIVE = "requires";
|
|
5
|
+
export declare const PREVIOUS_SYNTAX_HINT = "A functionality is a `# <func-id>` heading followed by its body; the previous syntax is not supported.";
|
|
5
6
|
export declare const REQUIRED_CONCEPTS_DIRECTIVE = "required_concepts";
|
|
6
7
|
export declare const EXPORTED_CONCEPTS_DIRECTIVE = "exported_concepts";
|
|
7
8
|
export declare const PLAIN_SOURCE_FILE_EXTENSION = ".plain";
|
|
8
|
-
/**
|
|
9
|
-
export
|
|
9
|
+
/** A functional spec as it is read: its func-id and the blocks of its body. */
|
|
10
|
+
export interface Functionality {
|
|
11
|
+
id: string;
|
|
12
|
+
lineNumber: number;
|
|
13
|
+
/** Every block after its heading, up to the next heading or section marker, in source order. */
|
|
14
|
+
body: AnyNode[];
|
|
15
|
+
linkedResources: LinkedResource[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* definitions / implementation reqs / test reqs always exist as keys, each a section of list items; functional specs
|
|
19
|
+
* does not, and is a list of functional specs when it does.
|
|
20
|
+
*/
|
|
21
|
+
export type ParsedPlainSource = Record<string, Section | Functionality[] | null | undefined>;
|
|
22
|
+
/** A list section of a parsed module. */
|
|
23
|
+
export declare function sectionOf(plainSource: ParsedPlainSource, heading: string): Section | null | undefined;
|
|
24
|
+
/** The functional specs of a parsed module, none when it has no section for them. */
|
|
25
|
+
export declare function functionalitiesOf(plainSource: ParsedPlainSource): Functionality[];
|
|
10
26
|
export interface FrontmatterPost {
|
|
11
27
|
metadata: Record<string, unknown>;
|
|
12
28
|
content: string;
|
|
@@ -22,41 +38,37 @@ export interface PlainFileParseResult {
|
|
|
22
38
|
export declare function getFilenameFromModuleName(moduleName: string): string;
|
|
23
39
|
export declare function getModuleNameFromFilename(filename: string): string;
|
|
24
40
|
export declare function hasScheme(target: string): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Port of loaders.resolve_linked_resource: a link's file, in the first of the folders resources are loaded from - the
|
|
43
|
+
* spec's own folder first, then the other template folders - where it exists, or null. Existence, not being a file,
|
|
44
|
+
* decides the match, as in `open_from`'s search. Links are relative to the spec, so a spec in a subfolder is checked
|
|
45
|
+
* where it is, wherever turri was started.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveLinkedResource(dirs: string[], target: string): string | null;
|
|
25
48
|
/** The links written in an item - what each says and where it points - as the parser reads them, checked or not. */
|
|
26
49
|
export declare function linksIn(section: AnyNode): Array<{
|
|
27
50
|
text: string;
|
|
28
51
|
target: string;
|
|
29
52
|
node: AnyNode;
|
|
30
53
|
}>;
|
|
54
|
+
/**
|
|
55
|
+
* The links in the blocks, each checked to be a relative path to an existing file that is not a .plain file.
|
|
56
|
+
* `where` names the place of the links in error messages, e.g. " in test plain file 'tests/m/f.plain', scenario `s`".
|
|
57
|
+
*/
|
|
58
|
+
export declare function findLinkedResources(nodes: AnyNode[], dirs?: string[], where?: string): LinkedResource[];
|
|
31
59
|
export declare function checkSectionForLinkedResources(section: AnyNode, dirs?: string[]): void;
|
|
32
60
|
export declare function checkForLinkedResources(plainSource: ParsedPlainSource, dirs?: string[]): void;
|
|
33
|
-
|
|
61
|
+
/** Put `{{ name }}` placeholders back into the section text and record the code variables it uses. */
|
|
62
|
+
export declare function processSectionCodeVariables(section: {
|
|
63
|
+
code_variables?: CodeVariable[];
|
|
64
|
+
} & Record<string, unknown>, codeVariables: Record<string, Record<string, string>>, textKey?: string): void;
|
|
34
65
|
export declare function processCodeVariables(marshalled: MarshalledPlainSource, codeVariables: Record<string, Record<string, string>>): void;
|
|
35
66
|
/** Whether the module declares a ***functional specs*** section (even if it is empty). */
|
|
36
67
|
export declare function hasFunctionalSpecsSection(plainSource: ParsedPlainSource): boolean;
|
|
37
68
|
/** Number of functionalities under ***functional specs*** (0 if absent or empty). */
|
|
38
69
|
export declare function countFunctionalities(plainSource: ParsedPlainSource): number;
|
|
39
70
|
export declare function validateFunctionalitiesHaveImplementationReqs(plainSource: ParsedPlainSource, moduleName: string, hasRequires?: boolean): void;
|
|
40
|
-
|
|
41
|
-
* Is this token an `acceptance tests` heading?
|
|
42
|
-
*
|
|
43
|
-
* True only for a paragraph holding exactly one emphasis, holding exactly one strong,
|
|
44
|
-
* holding exactly one text node whose content is the heading. Anything that looks like
|
|
45
|
-
* a heading but reads differently comes back as a problem string rather than false, so
|
|
46
|
-
* the caller can reject a near miss instead of silently keeping it.
|
|
47
|
-
*/
|
|
48
|
-
export declare function isAcceptanceTestHeading(token: AnyNode): [boolean, string | null];
|
|
49
|
-
/** Find an acceptance tests heading anywhere in a subtree. */
|
|
50
|
-
export declare function findAcceptanceTestHeading(token: AnyNode): AnyNode | null;
|
|
51
|
-
/**
|
|
52
|
-
* Pull the acceptance tests out of one functionality.
|
|
53
|
-
*
|
|
54
|
-
* A functionality with acceptance tests has three children: its own text, the
|
|
55
|
-
* `***acceptance tests***` heading, and the list of tests. The heading and list are
|
|
56
|
-
* lifted onto `acceptance_tests` and removed from the children.
|
|
57
|
-
*/
|
|
58
|
-
export declare function processSingleAcceptanceTestRequirement(functionalRequirement: AnyNode): void;
|
|
59
|
-
export declare function processAcceptanceTests(plainSource: ParsedPlainSource): void;
|
|
71
|
+
export declare function marshallFunctionality(functionality: Functionality): FunctionalSpecItem;
|
|
60
72
|
export declare function marshallPlainSource(input: ParsedPlainSource): MarshalledPlainSource;
|
|
61
73
|
export declare function normalizeLineEndings(plainSourceText: string): string;
|
|
62
74
|
/**
|
|
@@ -65,7 +77,22 @@ export declare function normalizeLineEndings(plainSourceText: string): string;
|
|
|
65
77
|
*/
|
|
66
78
|
export declare function restoreStrippedLines(plainSourceText: string, content: string): string;
|
|
67
79
|
export declare function readPlainSourceMetadata(plainSourceText: string): FrontmatterPost;
|
|
68
|
-
|
|
80
|
+
/** `Syntax error at line N in module 'm'`, without the parts that are not known. */
|
|
81
|
+
export declare function syntaxErrorAt(line: number | null | undefined, moduleName: string | null | undefined): string;
|
|
82
|
+
export declare const TEST_PLAIN_FILE_SECTION_HEADINGS: string[];
|
|
83
|
+
/** Throw when a `***conformance tests***` or `***acceptance tests***` marker is anywhere in a spec plain file. */
|
|
84
|
+
export declare function checkNoTestSections(plainSourceFullText: string, moduleName?: string | null): void;
|
|
85
|
+
/** The name in a `***name***` paragraph, or null when the paragraph is not a section marker. */
|
|
86
|
+
export declare function sectionMarker(token: AnyNode): string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Add the sections of the rendered spec plain file to `plainSource`.
|
|
89
|
+
*
|
|
90
|
+
* A list section holds a section whose children are its list items; `functional specs` holds a list of
|
|
91
|
+
* functionalities, one per `# <func-id>` heading, each with every block up to the next heading or section marker.
|
|
92
|
+
* Throw on the first structural error.
|
|
93
|
+
*/
|
|
94
|
+
export declare function readSections(plainSource: ParsedPlainSource, document: AnyNode, moduleName?: string | null): void;
|
|
95
|
+
export declare function parsePlainSource(plainSourceText: string, codeVariables: Record<string, Record<string, string>>, templateDirs: string[], importedModules: string[], modulesTrace: string[], moduleName?: string): PlainFileParseResult;
|
|
69
96
|
/**
|
|
70
97
|
* The file a module name is read from, as `readModulePlainSource` reads it - a standard library module's in turri's
|
|
71
98
|
* `stdlib/`, any other's in the first template folder that has it - or null when there is none.
|
|
@@ -73,8 +100,17 @@ export declare function parsePlainSource(plainSourceText: string, codeVariables:
|
|
|
73
100
|
export declare function modulePlainFile(moduleName: string, templateDirs: string[]): string | null;
|
|
74
101
|
export declare function readModulePlainSource(moduleName: string, templateDirs: string[]): string;
|
|
75
102
|
export declare function parsePlainFile(moduleName: string, codeVariables: Record<string, Record<string, string>>, templateDirs: string[], importedModules: string[], modulesTrace: string[]): PlainFileParseResult;
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Parse every required module, deepest first, without validating it - each with code variables of its own - and
|
|
105
|
+
* return the definitions the directly required modules export. A module listed again right after it was walked is
|
|
106
|
+
* not parsed again, and what it exports is still visible to this module. `exports` records what each module walked
|
|
107
|
+
* exports, so a repeat can find it.
|
|
108
|
+
*/
|
|
109
|
+
export declare function processRequiredModules(requiredModules: string[], templateDirs: string[], allRequiredModules: string[], modulesTrace: string[], exports?: Map<string, AnyNode[]>): AnyNode[];
|
|
110
|
+
/**
|
|
111
|
+
* The definitions a required module exports: those of its `exported_concepts`, with the definitions they use. What
|
|
112
|
+
* it may export is checked as it is parsed (`parsePlainSource`).
|
|
113
|
+
*/
|
|
78
114
|
export declare function exportedDefinitionsOf(result: PlainFileParseResult): AnyNode[];
|
|
79
115
|
export declare function processExportedDefinitions(plainSource: ParsedPlainSource, exportedDefinitions: AnyNode[]): void;
|
|
80
116
|
/**
|
|
@@ -89,8 +125,27 @@ export interface ParseOptions {
|
|
|
89
125
|
* product interview has so far given it definitions and reqs.
|
|
90
126
|
*/
|
|
91
127
|
allowNoFunctionalities?: boolean;
|
|
128
|
+
/**
|
|
129
|
+
* The project's test folder: the module's chain is parsed with its test plain files, `<testDir>/<module>` for each
|
|
130
|
+
* module of the chain - turri hands the reference one test folder per module - and their scenarios are bound to
|
|
131
|
+
* the functional specs they name. turri's: the reference takes the test folders themselves.
|
|
132
|
+
*/
|
|
133
|
+
testDir?: string;
|
|
134
|
+
/**
|
|
135
|
+
* With `testDir`: every functional spec of the chain must have a test plain file, which may hold no scenario
|
|
136
|
+
* (`MissingTestPlainFileError` for the first one that has none) - as the reference asks when the project names a
|
|
137
|
+
* conformance testing script. turri never asks: a functional spec with no test plain file is not tested, as one
|
|
138
|
+
* whose file is empty, and no file is the way to say so.
|
|
139
|
+
*/
|
|
140
|
+
requireTestPlainFiles?: boolean;
|
|
92
141
|
}
|
|
93
142
|
export declare function plainFileParser(plainSourceFileName: string, templateDirs: string[], options?: ParseOptions): [string, MarshalledPlainSource, string[]];
|
|
143
|
+
/**
|
|
144
|
+
* Port of plain_file.parse_module_chain: a module and every module in its `requires` chain, each as its name and its
|
|
145
|
+
* tree - every required module first, deepest ancestors first, then the module itself. A module reached through more
|
|
146
|
+
* than one `requires` path appears once, at its first (deepest) position.
|
|
147
|
+
*/
|
|
148
|
+
export declare function parseModuleChain(plainSourceFileName: string, templateDirs: string[], options?: ParseOptions): Array<[string, MarshalledPlainSource]>;
|
|
94
149
|
/**
|
|
95
150
|
* One parse per file for the length of `run`, however many times it is asked for.
|
|
96
151
|
*
|