@turri-dev/cli 0.0.0-stage → 0.1.2-next.48dacfc
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/.pi/themes/turri-dark.json +80 -0
- package/.pi/themes/turri-light.json +82 -0
- package/LICENSE +21 -0
- package/README.md +272 -2
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/fallback.txt +252 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/bin/turri.mjs +29 -0
- package/dist/ask.js +149 -0
- package/dist/brand.js +50 -0
- package/dist/btw.js +174 -0
- package/dist/chat-models.js +37 -0
- package/dist/chat-skills.js +18 -0
- package/dist/cli.js +178 -0
- package/dist/config-command.js +197 -0
- package/dist/config-edit.js +367 -0
- package/dist/config.js +489 -0
- package/dist/conform-cli.js +29 -0
- package/dist/cultivate-cli.js +110 -0
- package/dist/drift-cli.js +25 -0
- package/dist/environment.js +70 -0
- package/dist/extension.js +2372 -0
- package/dist/genome.js +301 -0
- package/dist/graph.js +246 -0
- package/dist/identity.js +35 -0
- package/dist/init-config.js +511 -0
- package/dist/judge.js +62 -0
- package/dist/lineage-cli.js +13 -0
- package/dist/loop-cli.js +132 -0
- package/dist/loop-tools.js +225 -0
- package/dist/mature-tools.js +193 -0
- package/dist/model-check.js +117 -0
- package/dist/module-report.js +9 -0
- package/dist/pi-launch.js +31 -0
- package/dist/pi.js +813 -0
- package/dist/plain/check.js +76 -0
- package/dist/plain/concepts.js +213 -0
- package/dist/plain/errors.js +43 -0
- package/dist/plain/files.js +57 -0
- package/dist/plain/highlight.js +195 -0
- package/dist/plain/index.js +15 -0
- package/dist/plain/liquid.js +129 -0
- package/dist/plain/markdown.js +208 -0
- package/dist/plain/parser.js +752 -0
- package/dist/plain/plain.tmLanguage.json +124 -0
- package/dist/plain/spec.js +291 -0
- package/dist/plain/stdlib.js +44 -0
- package/dist/question-dialog.js +125 -0
- package/dist/questioning.js +109 -0
- package/dist/quiet.js +125 -0
- package/dist/regenerate/activity.js +187 -0
- package/dist/regenerate/agent-env.js +190 -0
- package/dist/regenerate/agent-shell.js +114 -0
- package/dist/regenerate/arbiter.js +133 -0
- package/dist/regenerate/audit.js +203 -0
- package/dist/regenerate/cases.js +83 -0
- package/dist/regenerate/change-view.js +102 -0
- package/dist/regenerate/choose-conformance.js +117 -0
- package/dist/regenerate/chunk-plan.js +625 -0
- package/dist/regenerate/codebase.js +75 -0
- package/dist/regenerate/command-check.js +312 -0
- package/dist/regenerate/compaction.js +20 -0
- package/dist/regenerate/confine.js +310 -0
- package/dist/regenerate/conform.js +133 -0
- package/dist/regenerate/conformance-agents.js +955 -0
- package/dist/regenerate/conformance.js +936 -0
- package/dist/regenerate/conversation-writes.js +100 -0
- package/dist/regenerate/credentials.js +88 -0
- package/dist/regenerate/debug-log.js +228 -0
- package/dist/regenerate/debugger.js +90 -0
- package/dist/regenerate/delivery.js +19 -0
- package/dist/regenerate/diagnose-job.js +40 -0
- package/dist/regenerate/diagnose.js +81 -0
- package/dist/regenerate/diff-view.js +240 -0
- package/dist/regenerate/drift.js +194 -0
- package/dist/regenerate/engine.js +118 -0
- package/dist/regenerate/environment-error.js +220 -0
- package/dist/regenerate/job-log.js +102 -0
- package/dist/regenerate/job-panel.js +336 -0
- package/dist/regenerate/jobs-tree.js +300 -0
- package/dist/regenerate/jobs.js +427 -0
- package/dist/regenerate/lanes.js +32 -0
- package/dist/regenerate/lineage.js +384 -0
- package/dist/regenerate/loop.js +1435 -0
- package/dist/regenerate/masking.js +95 -0
- package/dist/regenerate/notes.js +397 -0
- package/dist/regenerate/os-sandbox.js +243 -0
- package/dist/regenerate/paths.js +165 -0
- package/dist/regenerate/plain-palette.js +83 -0
- package/dist/regenerate/prompt.js +704 -0
- package/dist/regenerate/records.js +294 -0
- package/dist/regenerate/regenerate.js +598 -0
- package/dist/regenerate/regression.js +181 -0
- package/dist/regenerate/release.js +262 -0
- package/dist/regenerate/requires.js +241 -0
- package/dist/regenerate/resources.js +164 -0
- package/dist/regenerate/self-check.js +111 -0
- package/dist/regenerate/services.js +155 -0
- package/dist/regenerate/shell.js +320 -0
- package/dist/regenerate/side-panel.js +278 -0
- package/dist/regenerate/spec-map-view.js +574 -0
- package/dist/regenerate/spec-map.js +561 -0
- package/dist/regenerate/spec-steps.js +527 -0
- package/dist/regenerate/state.js +10 -0
- package/dist/regenerate/steer.js +91 -0
- package/dist/regenerate/steering.js +119 -0
- package/dist/regenerate/stop-report.js +161 -0
- package/dist/regenerate/submit-work.js +71 -0
- package/dist/regenerate/test-kinds.js +124 -0
- package/dist/regenerate/test-scripts.js +695 -0
- package/dist/regenerate/testing.js +406 -0
- package/dist/regenerate/transcript-view.js +1 -0
- package/dist/regenerate/unit-gate.js +20 -0
- package/dist/regenerate/unit-review.js +347 -0
- package/dist/regenerate/web.js +256 -0
- package/dist/regenerate/workspace.js +99 -0
- package/dist/regenerate-cli.js +68 -0
- package/dist/release-agent.js +114 -0
- package/dist/release-cli.js +131 -0
- package/dist/release-tools.js +148 -0
- package/dist/review/live.js +165 -0
- package/dist/rules.js +47 -0
- package/dist/self-invocation.js +110 -0
- package/dist/setup.js +245 -0
- package/dist/skill-progress.js +78 -0
- package/dist/skill.js +149 -0
- package/dist/skills.js +74 -0
- package/dist/spec-diff-cli.js +37 -0
- package/dist/spec-tools.js +155 -0
- package/dist/stack.js +158 -0
- package/dist/stacks.js +97 -0
- package/dist/stdlib.js +100 -0
- package/dist/terminal-images.js +46 -0
- package/dist/tools.js +242 -0
- package/dist/validate-cli.js +52 -0
- package/docs/README.md +58 -0
- package/docs/authoring-plain.md +245 -0
- package/docs/config.md +353 -0
- package/docs/deploy.md +109 -0
- package/docs/development.md +88 -0
- package/docs/headless.md +148 -0
- package/docs/images/how-turri-works.svg +50 -0
- package/docs/images/turri-banner.png +0 -0
- package/docs/installation.md +119 -0
- package/docs/parser.md +108 -0
- package/docs/regenerating.md +1116 -0
- package/docs/sandbox.md +233 -0
- package/docs/skills-and-gates.md +254 -0
- package/docs/theming.md +76 -0
- package/docs/using.md +454 -0
- package/genome/rules/bullet-continuation.md +69 -0
- package/genome/rules/concise-specs-examples.md +431 -0
- package/genome/rules/concise-specs-integration-examples.md +270 -0
- package/genome/rules/concise-specs.md +80 -0
- package/genome/rules/definitions.md +107 -0
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
- package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
- package/genome/rules/exported-concepts.md +39 -0
- package/genome/rules/func-specs.md +129 -0
- package/genome/rules/impl-reqs.md +68 -0
- package/genome/rules/import-modules.md +48 -0
- package/genome/rules/integration-embedded-testing.md +276 -0
- package/genome/rules/integration-embedded.md +171 -0
- package/genome/rules/integration-standalone.md +135 -0
- package/genome/rules/integrations.md +137 -0
- package/genome/rules/linked-resources.md +69 -0
- package/genome/rules/module-structure.md +105 -0
- package/genome/rules/project-model.md +88 -0
- package/genome/rules/required-concepts.md +44 -0
- package/genome/rules/requires-modules.md +80 -0
- package/genome/rules/stdlib.md +73 -0
- package/genome/rules/test-reqs.md +57 -0
- package/genome/skills/add-acceptance-test/SKILL.md +100 -0
- package/genome/skills/add-concept/SKILL.md +71 -0
- package/genome/skills/add-functional-spec/SKILL.md +84 -0
- package/genome/skills/add-functional-specs/SKILL.md +118 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
- package/genome/skills/add-resource/SKILL.md +105 -0
- package/genome/skills/add-template/SKILL.md +64 -0
- package/genome/skills/add-test-requirement/SKILL.md +73 -0
- package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
- package/genome/skills/analyze-func-specs/SKILL.md +123 -0
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
- package/genome/skills/break-down-func-spec/SKILL.md +205 -0
- package/genome/skills/brood/SKILL.md +229 -0
- package/genome/skills/brood/references/intent.md +35 -0
- package/genome/skills/brood/references/product.md +101 -0
- package/genome/skills/brood/references/stack.md +61 -0
- package/genome/skills/brood/references/testing.md +56 -0
- package/genome/skills/bud/SKILL.md +148 -0
- package/genome/skills/bud/references/checklist.md +44 -0
- package/genome/skills/bud/references/intent.md +30 -0
- package/genome/skills/check-plain-env/SKILL.md +285 -0
- package/genome/skills/consolidate-concepts/SKILL.md +192 -0
- package/genome/skills/create-import-module/SKILL.md +104 -0
- package/genome/skills/create-requires-module/SKILL.md +109 -0
- package/genome/skills/debug-specs/SKILL.md +188 -0
- package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
- package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
- package/genome/skills/init-plain-project/SKILL.md +117 -0
- package/genome/skills/init-plain-project/references/checklist.md +30 -0
- package/genome/skills/mature/SKILL.md +126 -0
- package/genome/skills/refactor-module/SKILL.md +196 -0
- package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
- package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
- package/genome/skills/spawn/SKILL.md +163 -0
- package/package.json +68 -4
- package/stdlib/backend/common.plain +25 -0
- package/stdlib/backend/conformance/common.plain +12 -0
- package/stdlib/backend/conformance/go-gin.plain +9 -0
- package/stdlib/backend/conformance/go-nethttp.plain +9 -0
- package/stdlib/backend/conformance/java-springboot.plain +9 -0
- package/stdlib/backend/conformance/python-django.plain +9 -0
- package/stdlib/backend/conformance/python-fastapi.plain +9 -0
- package/stdlib/backend/conformance/python-flask.plain +9 -0
- package/stdlib/backend/conformance/rust-axum.plain +9 -0
- package/stdlib/backend/conformance/typescript-express.plain +9 -0
- package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
- package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
- package/stdlib/backend/go-gin.plain +18 -0
- package/stdlib/backend/go-nethttp.plain +18 -0
- package/stdlib/backend/java-springboot.plain +19 -0
- package/stdlib/backend/python-django.plain +19 -0
- package/stdlib/backend/python-fastapi.plain +23 -0
- package/stdlib/backend/python-flask.plain +23 -0
- package/stdlib/backend/rust-axum.plain +17 -0
- package/stdlib/backend/typescript-express.plain +19 -0
- package/stdlib/backend/typescript-fastify.plain +19 -0
- package/stdlib/backend/typescript-nestjs.plain +19 -0
- package/stdlib/cli/common.plain +17 -0
- package/stdlib/cli/conformance/common.plain +11 -0
- package/stdlib/cli/conformance/go-cobra.plain +9 -0
- package/stdlib/cli/conformance/go-flag.plain +9 -0
- package/stdlib/cli/conformance/java-picocli.plain +9 -0
- package/stdlib/cli/conformance/python-argparse.plain +9 -0
- package/stdlib/cli/conformance/python-click.plain +9 -0
- package/stdlib/cli/conformance/python-typer.plain +9 -0
- package/stdlib/cli/conformance/rust-clap.plain +9 -0
- package/stdlib/cli/conformance/typescript-commander.plain +9 -0
- package/stdlib/cli/go-cobra.plain +14 -0
- package/stdlib/cli/go-flag.plain +14 -0
- package/stdlib/cli/java-picocli.plain +15 -0
- package/stdlib/cli/python-argparse.plain +18 -0
- package/stdlib/cli/python-click.plain +16 -0
- package/stdlib/cli/python-typer.plain +16 -0
- package/stdlib/cli/rust-clap.plain +14 -0
- package/stdlib/cli/typescript-commander.plain +17 -0
- package/stdlib/frontend/angular.plain +17 -0
- package/stdlib/frontend/common.plain +17 -0
- package/stdlib/frontend/conformance/angular.plain +5 -0
- package/stdlib/frontend/conformance/common.plain +11 -0
- package/stdlib/frontend/conformance/react-vite.plain +5 -0
- package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
- package/stdlib/frontend/conformance/vue-vite.plain +5 -0
- package/stdlib/frontend/react-vite.plain +16 -0
- package/stdlib/frontend/sveltekit-spa.plain +16 -0
- package/stdlib/frontend/vue-vite.plain +16 -0
- package/stdlib/lang/go.plain +13 -0
- package/stdlib/lang/java.plain +12 -0
- package/stdlib/lang/python.plain +18 -0
- package/stdlib/lang/rust.plain +12 -0
- package/stdlib/lang/typescript.plain +13 -0
- package/stdlib/library/common.plain +15 -0
- package/stdlib/library/conformance/common.plain +9 -0
- package/stdlib/library/conformance/go.plain +9 -0
- package/stdlib/library/conformance/java-gradle.plain +9 -0
- package/stdlib/library/conformance/java-maven.plain +9 -0
- package/stdlib/library/conformance/python.plain +9 -0
- package/stdlib/library/conformance/rust.plain +9 -0
- package/stdlib/library/conformance/typescript.plain +9 -0
- package/stdlib/library/go.plain +12 -0
- package/stdlib/library/java-gradle.plain +13 -0
- package/stdlib/library/java-maven.plain +13 -0
- package/stdlib/library/python.plain +13 -0
- package/stdlib/library/rust.plain +12 -0
- package/stdlib/library/typescript.plain +15 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Liquid, Tag } from "liquidjs";
|
|
2
|
+
import { InvalidLiquidVariableName } from "./errors.js";
|
|
3
|
+
/** The innermost include namespace, which is what liquid2 calls `context.scope`. */
|
|
4
|
+
const scopeStack = [];
|
|
5
|
+
function currentScope() {
|
|
6
|
+
return scopeStack[scopeStack.length - 1];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Leading indentation of the line the tag sits on, and whether that line is a
|
|
10
|
+
* comment. Ported from Plain2CodeIncludeNode.render_to_output, which walks
|
|
11
|
+
* backwards from the tag to the start of its line.
|
|
12
|
+
*/
|
|
13
|
+
function scanLine(source, start) {
|
|
14
|
+
let whitespaces = 0;
|
|
15
|
+
let i = start;
|
|
16
|
+
while (i >= 0 && source[i] !== "\n") {
|
|
17
|
+
if (source[i] === " ") {
|
|
18
|
+
whitespaces += 1;
|
|
19
|
+
}
|
|
20
|
+
else if (source[i] === ">") {
|
|
21
|
+
return { whitespaces, isComment: true };
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
whitespaces = 0;
|
|
25
|
+
}
|
|
26
|
+
i -= 1;
|
|
27
|
+
}
|
|
28
|
+
return { whitespaces, isComment: false };
|
|
29
|
+
}
|
|
30
|
+
/** `"tpl.plain", key: "value", other: "value"` */
|
|
31
|
+
function parseIncludeArgs(args) {
|
|
32
|
+
const nameMatch = /^\s*(["'])(.*?)\1/.exec(args);
|
|
33
|
+
if (!nameMatch)
|
|
34
|
+
throw new Error(`Invalid include tag arguments: ${args}`);
|
|
35
|
+
const namespace = [];
|
|
36
|
+
const rest = args.slice(nameMatch[0].length);
|
|
37
|
+
const argPattern = /([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(["'])(.*?)\2/g;
|
|
38
|
+
let match;
|
|
39
|
+
while ((match = argPattern.exec(rest)) !== null) {
|
|
40
|
+
namespace.push([match[1], match[3]]);
|
|
41
|
+
}
|
|
42
|
+
return { name: nameMatch[2], namespace };
|
|
43
|
+
}
|
|
44
|
+
export function createEnvironment(options) {
|
|
45
|
+
const engine = new Liquid({ strictVariables: options.strictVariables ?? false });
|
|
46
|
+
engine.registerTag("include", class extends Tag {
|
|
47
|
+
rawArgs;
|
|
48
|
+
begin;
|
|
49
|
+
source;
|
|
50
|
+
constructor(token, remain, liquid) {
|
|
51
|
+
super(token, remain, liquid);
|
|
52
|
+
this.rawArgs = token.args;
|
|
53
|
+
this.begin = token.begin;
|
|
54
|
+
this.source = token.input;
|
|
55
|
+
}
|
|
56
|
+
*render(_ctx, emitter) {
|
|
57
|
+
const { whitespaces, isComment } = scanLine(this.source, this.begin);
|
|
58
|
+
const { name, namespace } = parseIncludeArgs(this.rawArgs);
|
|
59
|
+
if (isComment) {
|
|
60
|
+
// liquid2 writes the node back through str(node), which normalises quoting.
|
|
61
|
+
emitter.write(`{% include '${name}' %}`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const templateSource = options.loadTemplateSource(name);
|
|
65
|
+
if (templateSource === null) {
|
|
66
|
+
throw new Error(`Template not found: ${name}\n` +
|
|
67
|
+
"The required template could not be found. Templates are searched in the following order " +
|
|
68
|
+
"(highest to lowest precedence):\n\n" +
|
|
69
|
+
" 1. The directory containing your .plain file\n" +
|
|
70
|
+
" 2. The directory specified by --template-dir (if provided)\n" +
|
|
71
|
+
" 3. The built-in 'standard_template_library' directory\n");
|
|
72
|
+
}
|
|
73
|
+
// Plain2CodeLoaderMixin.load: rstrip, then re-indent every line to match the
|
|
74
|
+
// include site. The rstrip is what stops a block-level include from ending
|
|
75
|
+
// the surrounding list.
|
|
76
|
+
const indented = templateSource.replace(/\s+$/, "").split("\n").join("\n" + " ".repeat(whitespaces));
|
|
77
|
+
const scope = Object.fromEntries(namespace);
|
|
78
|
+
scopeStack.push(scope);
|
|
79
|
+
try {
|
|
80
|
+
emitter.write(engine.parseAndRenderSync(indented, scope));
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
scopeStack.pop();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
engine.registerFilter("code_variable", (value) => {
|
|
88
|
+
const scope = currentScope();
|
|
89
|
+
if (scope === undefined || Object.keys(scope).length === 0) {
|
|
90
|
+
throw new Error("Invalid use of code_variable filter!");
|
|
91
|
+
}
|
|
92
|
+
if (options.codeVariables === undefined)
|
|
93
|
+
return value;
|
|
94
|
+
// liquid2 takes the first item of the current scope, so every code_variable in a
|
|
95
|
+
// template is attributed to the include's first argument, whatever it renders.
|
|
96
|
+
const variableName = Object.keys(scope)[0];
|
|
97
|
+
const uniqueStr = randomHex();
|
|
98
|
+
options.codeVariables[uniqueStr] = { [variableName]: String(value) };
|
|
99
|
+
return uniqueStr;
|
|
100
|
+
});
|
|
101
|
+
engine.registerFilter("prohibited_chars", (value, prohibitedChars) => {
|
|
102
|
+
const text = typeof value === "string" ? value : String(value);
|
|
103
|
+
const scope = currentScope();
|
|
104
|
+
if (scope === undefined || Object.keys(scope).length === 0) {
|
|
105
|
+
throw new Error("Invalid use of prohibited_chars filter!");
|
|
106
|
+
}
|
|
107
|
+
const variableName = Object.keys(scope)[0];
|
|
108
|
+
for (const char of prohibitedChars) {
|
|
109
|
+
if (text.includes(char)) {
|
|
110
|
+
throw new InvalidLiquidVariableName(`'${char}' is not a valid character for variable '${variableName}' (value: '${text}').`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return text;
|
|
114
|
+
});
|
|
115
|
+
return engine;
|
|
116
|
+
}
|
|
117
|
+
function randomHex() {
|
|
118
|
+
return [...crypto.getRandomValues(new Uint8Array(16))]
|
|
119
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
120
|
+
.join("");
|
|
121
|
+
}
|
|
122
|
+
/** Port of plain_file.render_plain_source. */
|
|
123
|
+
export function renderPlainSource(plainSource, loadedTemplates, codeVariables) {
|
|
124
|
+
const engine = createEnvironment({
|
|
125
|
+
loadTemplateSource: (name) => loadedTemplates[name] ?? null,
|
|
126
|
+
codeVariables,
|
|
127
|
+
});
|
|
128
|
+
return engine.parseAndRenderSync(plainSource, {});
|
|
129
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
2
|
+
/**
|
|
3
|
+
* The markdown layer.
|
|
4
|
+
*
|
|
5
|
+
* The Python parser leans on mistletoe for two things: an AST with line numbers,
|
|
6
|
+
* and a MarkdownRenderer that round-trips a token back to markdown. mdast gives
|
|
7
|
+
* us the first. The second is `renderToMarkdown` below - a serializer for the
|
|
8
|
+
* subset of markdown the plain language uses, matching mistletoe's output
|
|
9
|
+
* (source indentation preserved, links rewritten as `[resource]` spans).
|
|
10
|
+
*/
|
|
11
|
+
export const RESOURCE_MARKER = "[resource]";
|
|
12
|
+
/**
|
|
13
|
+
* Parse markdown, then pin every inline node back to its original source text.
|
|
14
|
+
*
|
|
15
|
+
* mdast decodes inline syntax: `\"` becomes `"`, and an emphasis written with `_`
|
|
16
|
+
* loses the fact that it was not `*`. mistletoe's MarkdownRenderer round-trips the
|
|
17
|
+
* source instead, so re-serialising a decoded tree drifts from the Python output.
|
|
18
|
+
* Recording the raw slice for leaf nodes and the real delimiters for emphasis and
|
|
19
|
+
* strong keeps the two renderers in agreement.
|
|
20
|
+
*/
|
|
21
|
+
export function parseMarkdown(text) {
|
|
22
|
+
const root = fromMarkdown(text);
|
|
23
|
+
pinToSource(root, text);
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
26
|
+
const RAW_LEAF_TYPES = new Set(["text", "inlineCode", "html"]);
|
|
27
|
+
function pinToSource(node, source, origin = 1) {
|
|
28
|
+
const start = node.position?.start.offset;
|
|
29
|
+
const end = node.position?.end.offset;
|
|
30
|
+
if (start !== undefined && end !== undefined) {
|
|
31
|
+
if (RAW_LEAF_TYPES.has(node.type)) {
|
|
32
|
+
const raw = source.slice(start, end);
|
|
33
|
+
// A paragraph's continuation lines carry leading whitespace that is not part of
|
|
34
|
+
// the text. mistletoe drops it when rendering; keeping it here would indent the
|
|
35
|
+
// line twice, once from the source and once from the enclosing list item.
|
|
36
|
+
node.value = node.type === "text" ? raw.replace(/\n[ \t]*/g, "\n") : raw;
|
|
37
|
+
node.raw = true;
|
|
38
|
+
}
|
|
39
|
+
else if (node.type === "emphasis" || node.type === "strong") {
|
|
40
|
+
const width = node.type === "strong" ? 2 : 1;
|
|
41
|
+
node.openDelimiter = source.slice(start, start + width);
|
|
42
|
+
node.closeDelimiter = source.slice(end - width, end);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
let childOrigin = origin;
|
|
46
|
+
if (node.type === "listItem") {
|
|
47
|
+
// mistletoe strips the enclosing item's indentation before parsing nested blocks,
|
|
48
|
+
// so `indentation` and `prepend` are measured against that stripped origin - not
|
|
49
|
+
// against the item's own column. Continuation lines are later indented by
|
|
50
|
+
// `prepend`, which is why a nested list keeps more indentation than the gap
|
|
51
|
+
// between the two columns in the source.
|
|
52
|
+
const itemColumn = column(node);
|
|
53
|
+
const first = childrenOf(node)[0];
|
|
54
|
+
const contentColumn = first ? column(first) : itemColumn + 2;
|
|
55
|
+
node.indentation = itemColumn - origin;
|
|
56
|
+
node.prepend = contentColumn - origin;
|
|
57
|
+
node.leader =
|
|
58
|
+
start !== undefined && first
|
|
59
|
+
? source.slice(start, start + (contentColumn - itemColumn)).trimEnd()
|
|
60
|
+
: "-";
|
|
61
|
+
childOrigin = contentColumn;
|
|
62
|
+
}
|
|
63
|
+
for (const child of childrenOf(node))
|
|
64
|
+
pinToSource(child, source, childOrigin);
|
|
65
|
+
}
|
|
66
|
+
export function lineNumber(node) {
|
|
67
|
+
return node.position?.start.line ?? 0;
|
|
68
|
+
}
|
|
69
|
+
function column(node) {
|
|
70
|
+
return node.position?.start.column ?? 1;
|
|
71
|
+
}
|
|
72
|
+
function isParent(node) {
|
|
73
|
+
return Array.isArray(node.children);
|
|
74
|
+
}
|
|
75
|
+
function childrenOf(node) {
|
|
76
|
+
return node.children ?? [];
|
|
77
|
+
}
|
|
78
|
+
/** Port of plain_file.remove_quotes: block quotes are comments and are dropped. */
|
|
79
|
+
export function removeQuotes(node) {
|
|
80
|
+
if (!isParent(node))
|
|
81
|
+
return;
|
|
82
|
+
const kept = [];
|
|
83
|
+
for (const child of node.children) {
|
|
84
|
+
if (child.type === "blockquote")
|
|
85
|
+
continue;
|
|
86
|
+
removeQuotes(child);
|
|
87
|
+
kept.push(child);
|
|
88
|
+
}
|
|
89
|
+
node.children = kept;
|
|
90
|
+
}
|
|
91
|
+
/** Port of plain_file.get_raw_text. */
|
|
92
|
+
export function getRawText(node) {
|
|
93
|
+
if (node.type === "text")
|
|
94
|
+
return node.value;
|
|
95
|
+
if (isParent(node)) {
|
|
96
|
+
return childrenOf(node).map(getRawText).join("");
|
|
97
|
+
}
|
|
98
|
+
const value = node.value;
|
|
99
|
+
if (value !== undefined)
|
|
100
|
+
return value;
|
|
101
|
+
throw new Error(`Unknown token type: ${node.type}`);
|
|
102
|
+
}
|
|
103
|
+
/** Port of mistletoe.utils.traverse restricted to a node type. */
|
|
104
|
+
export function traverse(node, type) {
|
|
105
|
+
const found = [];
|
|
106
|
+
const walk = (n) => {
|
|
107
|
+
if (n.type === type)
|
|
108
|
+
found.push(n);
|
|
109
|
+
if (isParent(n))
|
|
110
|
+
for (const child of n.children)
|
|
111
|
+
walk(child);
|
|
112
|
+
};
|
|
113
|
+
walk(node);
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
function renderInline(node) {
|
|
117
|
+
switch (node.type) {
|
|
118
|
+
case "text":
|
|
119
|
+
return node.value;
|
|
120
|
+
case "inlineCode":
|
|
121
|
+
// The raw slice already carries its own backticks.
|
|
122
|
+
return node.raw ? node.value : `\`${node.value}\``;
|
|
123
|
+
case "emphasis":
|
|
124
|
+
return `${node.openDelimiter ?? "*"}${renderInlineChildren(node)}${node.closeDelimiter ?? "*"}`;
|
|
125
|
+
case "strong":
|
|
126
|
+
return `${node.openDelimiter ?? "**"}${renderInlineChildren(node)}${node.closeDelimiter ?? "**"}`;
|
|
127
|
+
// PlainRenderer.render_link: a link becomes a literal `[resource]<text>` span.
|
|
128
|
+
case "link":
|
|
129
|
+
return `\`${RESOURCE_MARKER}${renderInlineChildren(node)}\``;
|
|
130
|
+
case "image":
|
|
131
|
+
return ``;
|
|
132
|
+
case "break":
|
|
133
|
+
return "\n";
|
|
134
|
+
case "html":
|
|
135
|
+
return node.value;
|
|
136
|
+
default:
|
|
137
|
+
return isParent(node) ? renderInlineChildren(node) : (node.value ?? "");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function renderInlineChildren(node) {
|
|
141
|
+
return childrenOf(node).map(renderInline).join("");
|
|
142
|
+
}
|
|
143
|
+
/** Blank lines between two sibling blocks, as they appeared in the source. */
|
|
144
|
+
function blankLinesBetween(previous, next) {
|
|
145
|
+
const previousEnd = previous.position?.end.line;
|
|
146
|
+
const nextStart = next.position?.start.line;
|
|
147
|
+
if (previousEnd === undefined || nextStart === undefined)
|
|
148
|
+
return 0;
|
|
149
|
+
return Math.max(0, nextStart - previousEnd - 1);
|
|
150
|
+
}
|
|
151
|
+
/** Port of MarkdownRenderer.prefix_lines. */
|
|
152
|
+
function prefixLines(lines, firstPrefix, followingPrefix) {
|
|
153
|
+
return lines.map((line, index) => {
|
|
154
|
+
const prefixed = (index === 0 ? firstPrefix : followingPrefix) + line;
|
|
155
|
+
return prefixed.trim() === "" ? "" : prefixed;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* 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 gaps
|
|
161
|
+
* between sibling positions.
|
|
162
|
+
*/
|
|
163
|
+
function blocksToLines(nodes) {
|
|
164
|
+
const lines = [];
|
|
165
|
+
nodes.forEach((node, index) => {
|
|
166
|
+
if (index > 0 && blankLinesBetween(nodes[index - 1], node) > 0)
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push(...renderBlockLines(node));
|
|
169
|
+
});
|
|
170
|
+
return lines;
|
|
171
|
+
}
|
|
172
|
+
/** Port of MarkdownRenderer.render_list_item. */
|
|
173
|
+
function renderListItemLines(item) {
|
|
174
|
+
const prepend = item.prepend ?? 2;
|
|
175
|
+
const indentation = item.indentation ?? 0;
|
|
176
|
+
const leader = item.leader ?? "-";
|
|
177
|
+
const lines = blocksToLines(childrenOf(item));
|
|
178
|
+
const firstPrefix = " ".repeat(Math.max(0, indentation)) +
|
|
179
|
+
leader +
|
|
180
|
+
" ".repeat(Math.max(0, prepend - leader.length - indentation));
|
|
181
|
+
return prefixLines(lines.length > 0 ? lines : [""], firstPrefix, " ".repeat(Math.max(0, prepend)));
|
|
182
|
+
}
|
|
183
|
+
function renderBlockLines(node) {
|
|
184
|
+
switch (node.type) {
|
|
185
|
+
case "paragraph":
|
|
186
|
+
return renderInlineChildren(node).split("\n");
|
|
187
|
+
case "heading":
|
|
188
|
+
return [`${"#".repeat(node.depth)} ${renderInlineChildren(node)}`];
|
|
189
|
+
case "code":
|
|
190
|
+
return [`\`\`\`${node.lang ?? ""}`, ...(node.value ?? "").split("\n"), "\`\`\`"];
|
|
191
|
+
case "thematicBreak":
|
|
192
|
+
return ["---"];
|
|
193
|
+
case "blockquote":
|
|
194
|
+
return blocksToLines(childrenOf(node)).map((line) => (line ? `> ${line}` : ">"));
|
|
195
|
+
case "list":
|
|
196
|
+
return blocksToLines(childrenOf(node));
|
|
197
|
+
case "listItem":
|
|
198
|
+
return renderListItemLines(node);
|
|
199
|
+
default:
|
|
200
|
+
return node.children !== undefined
|
|
201
|
+
? blocksToLines(childrenOf(node))
|
|
202
|
+
: (node.value ?? "").split("\n");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** Port of PlainRenderer.render. */
|
|
206
|
+
export function renderToMarkdown(node) {
|
|
207
|
+
return renderBlockLines(node).join("\n");
|
|
208
|
+
}
|