@thi.ng/tangle 0.1.67 → 0.1.69
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/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/api.js +54 -48
- package/cli.js +55 -46
- package/package.json +18 -15
- package/tangle.js +195 -189
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
package/api.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { LogLevel } from "@thi.ng/logger";
|
|
2
2
|
import { ConsoleLogger } from "@thi.ng/logger/console";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
const BLOCK_FORMATS = {
|
|
4
|
+
".md": {
|
|
5
|
+
prefix: "```",
|
|
6
|
+
suffix: "```"
|
|
7
|
+
},
|
|
8
|
+
".org": {
|
|
9
|
+
prefix: "#+BEGIN_SRC ",
|
|
10
|
+
suffix: "#+END_SRC"
|
|
11
|
+
}
|
|
12
12
|
};
|
|
13
13
|
const C = "//";
|
|
14
14
|
const P = "#";
|
|
@@ -16,44 +16,50 @@ const L = "--";
|
|
|
16
16
|
const S = ";;";
|
|
17
17
|
const C89 = ["/*", "*/"];
|
|
18
18
|
const HTML = ["<!--", "-->"];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
19
|
+
const COMMENT_FORMATS = {
|
|
20
|
+
c: C89,
|
|
21
|
+
clj: S,
|
|
22
|
+
cljs: S,
|
|
23
|
+
cpp: C,
|
|
24
|
+
cs: C,
|
|
25
|
+
css: C89,
|
|
26
|
+
docker: "::",
|
|
27
|
+
elm: L,
|
|
28
|
+
erl: "%",
|
|
29
|
+
fs: "\\",
|
|
30
|
+
go: C,
|
|
31
|
+
h: C,
|
|
32
|
+
html: HTML,
|
|
33
|
+
java: C,
|
|
34
|
+
js: C,
|
|
35
|
+
jsonc: C,
|
|
36
|
+
lua: L,
|
|
37
|
+
md: HTML,
|
|
38
|
+
ml: ["(*", "*)"],
|
|
39
|
+
pde: C,
|
|
40
|
+
py: P,
|
|
41
|
+
rs: C,
|
|
42
|
+
scala: C,
|
|
43
|
+
scm: S,
|
|
44
|
+
sh: P,
|
|
45
|
+
sql: L,
|
|
46
|
+
swift: C,
|
|
47
|
+
tex: "%",
|
|
48
|
+
toml: P,
|
|
49
|
+
ts: C,
|
|
50
|
+
ttl: P,
|
|
51
|
+
wast: S,
|
|
52
|
+
xml: HTML,
|
|
53
|
+
yaml: P,
|
|
54
|
+
zig: C
|
|
55
55
|
};
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
let LOGGER = new ConsoleLogger("tangle", LogLevel.INFO);
|
|
57
|
+
const setLogger = (logger) => {
|
|
58
|
+
LOGGER = logger;
|
|
59
|
+
};
|
|
60
|
+
export {
|
|
61
|
+
BLOCK_FORMATS,
|
|
62
|
+
COMMENT_FORMATS,
|
|
63
|
+
LOGGER,
|
|
64
|
+
setLogger
|
|
59
65
|
};
|
package/cli.js
CHANGED
|
@@ -1,64 +1,73 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ParseError,
|
|
3
|
+
flag,
|
|
4
|
+
parse,
|
|
5
|
+
usage
|
|
6
|
+
} from "@thi.ng/args";
|
|
3
7
|
import { readJSON, writeText } from "@thi.ng/file-io";
|
|
4
8
|
import { ConsoleLogger } from "@thi.ng/logger";
|
|
5
9
|
import { resolve } from "path";
|
|
6
10
|
import { tangleFile } from "./tangle.js";
|
|
7
11
|
const argOpts = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
noComments: flag({ default: false, desc: "don't generate comments" }),
|
|
13
|
+
debug: flag({ alias: "d", default: false, desc: "enable debug output" }),
|
|
14
|
+
dryRun: flag({
|
|
15
|
+
default: false,
|
|
16
|
+
desc: "enable dry run (don't overwrite files)"
|
|
17
|
+
})
|
|
14
18
|
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
const INSTALL_DIR = resolve(`${process.argv[2]}/..`);
|
|
20
|
+
const PKG = readJSON(`${INSTALL_DIR}/package.json`);
|
|
21
|
+
const APP_NAME = PKG.name.split("/")[1];
|
|
22
|
+
const HEADER = `
|
|
23
|
+
\u2588 \u2588 \u2588 \u2502
|
|
24
|
+
\u2588\u2588 \u2588 \u2502
|
|
25
|
+
\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2502 ${PKG.name} ${PKG.version}
|
|
26
|
+
\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2502 Literate programming code block tangling
|
|
27
|
+
\u2588 \u2502
|
|
28
|
+
\u2588 \u2588 \u2502
|
|
25
29
|
`;
|
|
26
30
|
const usageOpts = {
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
lineWidth: process.stdout.columns,
|
|
32
|
+
prefix: `${HEADER}
|
|
29
33
|
usage: ${APP_NAME} [OPTS] SOURCE-FILES(S) ...
|
|
30
34
|
${APP_NAME} --help
|
|
31
35
|
|
|
32
36
|
`,
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
showGroupNames: true,
|
|
38
|
+
paramWidth: 20
|
|
35
39
|
};
|
|
36
40
|
const showUsage = () => {
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
process.stderr.write(usage(argOpts, usageOpts));
|
|
42
|
+
process.exit(1);
|
|
39
43
|
};
|
|
40
44
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
process.exit(1);
|
|
44
|
-
const { result: opts, rest } = result;
|
|
45
|
-
if (!rest.length)
|
|
46
|
-
showUsage();
|
|
47
|
-
let ctx = {
|
|
48
|
-
logger: new ConsoleLogger("tangle", opts.debug ? "DEBUG" : "INFO"),
|
|
49
|
-
opts: {
|
|
50
|
-
comments: opts.noComments !== true,
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
for (let file of rest) {
|
|
54
|
-
ctx = tangleFile(file, ctx);
|
|
55
|
-
}
|
|
56
|
-
for (let out in ctx.outputs) {
|
|
57
|
-
writeText(out, ctx.outputs[out], ctx.logger, opts.dryRun);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
catch (e) {
|
|
61
|
-
if (!(e instanceof ParseError))
|
|
62
|
-
process.stderr.write(e.message);
|
|
45
|
+
const result = parse(argOpts, process.argv, { start: 3, usageOpts });
|
|
46
|
+
if (!result)
|
|
63
47
|
process.exit(1);
|
|
48
|
+
const { result: opts, rest } = result;
|
|
49
|
+
if (!rest.length)
|
|
50
|
+
showUsage();
|
|
51
|
+
let ctx = {
|
|
52
|
+
logger: new ConsoleLogger("tangle", opts.debug ? "DEBUG" : "INFO"),
|
|
53
|
+
opts: {
|
|
54
|
+
comments: opts.noComments !== true
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
for (let file of rest) {
|
|
58
|
+
ctx = tangleFile(file, ctx);
|
|
59
|
+
}
|
|
60
|
+
for (let out in ctx.outputs) {
|
|
61
|
+
writeText(out, ctx.outputs[out], ctx.logger, opts.dryRun);
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
if (!(e instanceof ParseError))
|
|
65
|
+
process.stderr.write(e.message);
|
|
66
|
+
process.exit(1);
|
|
64
67
|
}
|
|
68
|
+
export {
|
|
69
|
+
APP_NAME,
|
|
70
|
+
HEADER,
|
|
71
|
+
INSTALL_DIR,
|
|
72
|
+
PKG
|
|
73
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/tangle",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.69",
|
|
4
4
|
"description": "Literate programming code block tangling / codegen utility, inspired by org-mode & noweb",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"author": "Karsten Schmidt (https://thi.ng)",
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"scripts": {
|
|
28
|
-
"build": "yarn
|
|
28
|
+
"build": "yarn build:esbuild && yarn build:decl",
|
|
29
|
+
"build:decl": "tsc --declaration --emitDeclarationOnly",
|
|
30
|
+
"build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
|
|
29
31
|
"clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
|
|
30
32
|
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
|
|
31
33
|
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
|
|
@@ -34,20 +36,21 @@
|
|
|
34
36
|
"test": "testament test"
|
|
35
37
|
},
|
|
36
38
|
"dependencies": {
|
|
37
|
-
"@thi.ng/api": "^8.9.
|
|
38
|
-
"@thi.ng/args": "^2.
|
|
39
|
-
"@thi.ng/checks": "^3.4.
|
|
40
|
-
"@thi.ng/compare": "^2.2.
|
|
41
|
-
"@thi.ng/date": "^2.5.
|
|
42
|
-
"@thi.ng/errors": "^2.4.
|
|
43
|
-
"@thi.ng/file-io": "^1.0
|
|
44
|
-
"@thi.ng/logger": "^2.0
|
|
45
|
-
"@thi.ng/strings": "^3.7.
|
|
46
|
-
"@thi.ng/transducers": "^8.8.
|
|
39
|
+
"@thi.ng/api": "^8.9.13",
|
|
40
|
+
"@thi.ng/args": "^2.3.0",
|
|
41
|
+
"@thi.ng/checks": "^3.4.13",
|
|
42
|
+
"@thi.ng/compare": "^2.2.9",
|
|
43
|
+
"@thi.ng/date": "^2.5.7",
|
|
44
|
+
"@thi.ng/errors": "^2.4.7",
|
|
45
|
+
"@thi.ng/file-io": "^1.1.0",
|
|
46
|
+
"@thi.ng/logger": "^2.1.0",
|
|
47
|
+
"@thi.ng/strings": "^3.7.4",
|
|
48
|
+
"@thi.ng/transducers": "^8.8.16"
|
|
47
49
|
},
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"@microsoft/api-extractor": "^7.38.3",
|
|
50
|
-
"@thi.ng/testament": "^0.4.
|
|
52
|
+
"@thi.ng/testament": "^0.4.6",
|
|
53
|
+
"esbuild": "^0.19.8",
|
|
51
54
|
"rimraf": "^5.0.5",
|
|
52
55
|
"tools": "^0.0.1",
|
|
53
56
|
"typedoc": "^0.25.4",
|
|
@@ -69,7 +72,7 @@
|
|
|
69
72
|
"setTimeout": false
|
|
70
73
|
},
|
|
71
74
|
"engines": {
|
|
72
|
-
"node": ">=
|
|
75
|
+
"node": ">=18"
|
|
73
76
|
},
|
|
74
77
|
"files": [
|
|
75
78
|
"./*.js",
|
|
@@ -91,5 +94,5 @@
|
|
|
91
94
|
"status": "alpha",
|
|
92
95
|
"year": 2022
|
|
93
96
|
},
|
|
94
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "25a42a81fac8603a1e440a7aa8bc343276211ff4\n"
|
|
95
98
|
}
|
package/tangle.js
CHANGED
|
@@ -7,210 +7,216 @@ import { readText } from "@thi.ng/file-io";
|
|
|
7
7
|
import { split } from "@thi.ng/strings";
|
|
8
8
|
import { assocObj, map, transduce } from "@thi.ng/transducers";
|
|
9
9
|
import { extname, isAbsolute, resolve, sep } from "path";
|
|
10
|
-
import {
|
|
11
|
-
|
|
10
|
+
import {
|
|
11
|
+
BLOCK_FORMATS,
|
|
12
|
+
COMMENT_FORMATS,
|
|
13
|
+
LOGGER
|
|
14
|
+
} from "./api.js";
|
|
15
|
+
const UnknownBlockError = defError(
|
|
16
|
+
(err) => `can't include unknown block ID: ${err[0]} (via ${err[1]})`,
|
|
17
|
+
() => ""
|
|
18
|
+
);
|
|
12
19
|
const extractBlocks = (src, { format, logger }) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
let nextID = 0;
|
|
21
|
+
const blocks = {};
|
|
22
|
+
const re = new RegExp(
|
|
23
|
+
`^${format.prefix.replace("+", "\\+")}(\\w+)\\s+(.+)$`,
|
|
24
|
+
"gm"
|
|
25
|
+
);
|
|
26
|
+
let match;
|
|
27
|
+
while (match = re.exec(src)) {
|
|
28
|
+
let { id, tangle, noweb, publish } = parseBlockHeader(match[2]);
|
|
29
|
+
!id && (id = `__block-${nextID++}`);
|
|
30
|
+
const matchStart = match.index;
|
|
31
|
+
const start = src.indexOf("\n", matchStart) + 1;
|
|
32
|
+
const end = src.indexOf(`
|
|
33
|
+
${format.suffix}`, matchStart + 1);
|
|
34
|
+
logger.debug(
|
|
35
|
+
"codeblock",
|
|
36
|
+
id,
|
|
37
|
+
start,
|
|
38
|
+
end,
|
|
39
|
+
matchStart,
|
|
40
|
+
src.substring(start, 8)
|
|
41
|
+
);
|
|
42
|
+
const body = src.substring(start, end);
|
|
43
|
+
blocks[id] = {
|
|
44
|
+
id,
|
|
45
|
+
lang: match[1],
|
|
46
|
+
tangle,
|
|
47
|
+
publish,
|
|
48
|
+
noweb,
|
|
49
|
+
start,
|
|
50
|
+
end,
|
|
51
|
+
matchStart,
|
|
52
|
+
matchEnd: end + format.suffix.length + 2,
|
|
53
|
+
body
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return blocks;
|
|
39
57
|
};
|
|
40
58
|
const resolveBlock = (block, ref, ctx) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
block.resolved = true;
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
ctx.logger.debug("resolve", block.id);
|
|
48
|
-
const re = /<<(.+)>>/g;
|
|
49
|
-
let match;
|
|
50
|
-
let body = block.body;
|
|
51
|
-
while ((match = re.exec(body))) {
|
|
52
|
-
const paramIdx = match[1].indexOf(" ");
|
|
53
|
-
let [childID, params] = paramIdx > 0
|
|
54
|
-
? [
|
|
55
|
-
match[1].substring(0, paramIdx),
|
|
56
|
-
JSON.parse(match[1].substring(paramIdx).trim()),
|
|
57
|
-
]
|
|
58
|
-
: [match[1]];
|
|
59
|
-
let childBlock;
|
|
60
|
-
if (childID.indexOf("#") > 0) {
|
|
61
|
-
const [file, blockID] = childID.split("#");
|
|
62
|
-
childBlock = loadAndResolveBlocks(ctx.fs.resolve(ctx.fs.resolve(ref.path, ".."), file), ctx).blocks[blockID];
|
|
63
|
-
childID = blockID;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
childID = childID.replace("#", "");
|
|
67
|
-
childBlock = ref.blocks[childID];
|
|
68
|
-
}
|
|
69
|
-
if (!childBlock)
|
|
70
|
-
throw new UnknownBlockError([childID, block.id]);
|
|
71
|
-
resolveBlock(childBlock, ref, ctx);
|
|
72
|
-
const newBody = isPlainObject(params)
|
|
73
|
-
? parametricBody(childBlock.body, params)
|
|
74
|
-
: childBlock.body;
|
|
75
|
-
block.body = block.body.replace(`<<${match[1]}>>`, newBody);
|
|
76
|
-
block.edited = true;
|
|
77
|
-
}
|
|
59
|
+
if (block.resolved)
|
|
60
|
+
return;
|
|
61
|
+
if (block.noweb === "no") {
|
|
78
62
|
block.resolved = true;
|
|
79
|
-
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
ctx.logger.debug("resolve", block.id);
|
|
66
|
+
const re = /<<(.+)>>/g;
|
|
67
|
+
let match;
|
|
68
|
+
let body = block.body;
|
|
69
|
+
while (match = re.exec(body)) {
|
|
70
|
+
const paramIdx = match[1].indexOf(" ");
|
|
71
|
+
let [childID, params] = paramIdx > 0 ? [
|
|
72
|
+
match[1].substring(0, paramIdx),
|
|
73
|
+
JSON.parse(match[1].substring(paramIdx).trim())
|
|
74
|
+
] : [match[1]];
|
|
75
|
+
let childBlock;
|
|
76
|
+
if (childID.indexOf("#") > 0) {
|
|
77
|
+
const [file, blockID] = childID.split("#");
|
|
78
|
+
childBlock = loadAndResolveBlocks(
|
|
79
|
+
ctx.fs.resolve(ctx.fs.resolve(ref.path, ".."), file),
|
|
80
|
+
ctx
|
|
81
|
+
).blocks[blockID];
|
|
82
|
+
childID = blockID;
|
|
83
|
+
} else {
|
|
84
|
+
childID = childID.replace("#", "");
|
|
85
|
+
childBlock = ref.blocks[childID];
|
|
86
|
+
}
|
|
87
|
+
if (!childBlock)
|
|
88
|
+
throw new UnknownBlockError([childID, block.id]);
|
|
89
|
+
resolveBlock(childBlock, ref, ctx);
|
|
90
|
+
const newBody = isPlainObject(params) ? parametricBody(childBlock.body, params) : childBlock.body;
|
|
91
|
+
block.body = block.body.replace(`<<${match[1]}>>`, newBody);
|
|
92
|
+
block.edited = true;
|
|
93
|
+
}
|
|
94
|
+
block.resolved = true;
|
|
95
|
+
block.body = block.body.replace(/\\</g, "<");
|
|
80
96
|
};
|
|
81
97
|
const resolveBlocks = (ref, ctx) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
for (let id in ref.blocks) {
|
|
99
|
+
resolveBlock(ref.blocks[id], ref, ctx);
|
|
100
|
+
}
|
|
101
|
+
return ref;
|
|
86
102
|
};
|
|
87
103
|
const parseFileMeta = (src) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
104
|
+
if (!src.startsWith("---\n"))
|
|
105
|
+
return {};
|
|
106
|
+
const res = {};
|
|
107
|
+
for (let line of split(src.substring(4))) {
|
|
108
|
+
if (line === "---")
|
|
109
|
+
break;
|
|
110
|
+
const [key, val] = line.split(/:\s+/g);
|
|
111
|
+
res[key.trim()] = val.trim();
|
|
112
|
+
}
|
|
113
|
+
return res;
|
|
98
114
|
};
|
|
99
|
-
const parseBlockHeader = (header) => transduce(
|
|
100
|
-
|
|
115
|
+
const parseBlockHeader = (header) => transduce(
|
|
116
|
+
map((x) => x.split(":")),
|
|
117
|
+
assocObj(),
|
|
118
|
+
header.split(/\s+/)
|
|
119
|
+
);
|
|
120
|
+
const parametricBody = (body, params) => body.replace(
|
|
121
|
+
/\{\{(\w+)\}\}/g,
|
|
122
|
+
(_, id) => params[id] != null ? params[id] : id
|
|
123
|
+
);
|
|
101
124
|
const commentForLang = (lang, body) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
? `${syntax} ${body}`
|
|
105
|
-
: `${syntax[0]} ${body} ${syntax[1]}`;
|
|
125
|
+
const syntax = COMMENT_FORMATS[lang];
|
|
126
|
+
return isString(syntax) ? `${syntax} ${body}` : `${syntax[0]} ${body} ${syntax[1]}`;
|
|
106
127
|
};
|
|
107
128
|
const loadAndResolveBlocks = (path, ctx) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
path = ctx.fs.resolve(path);
|
|
130
|
+
if (!ctx.files[path]) {
|
|
131
|
+
const src = ctx.fs.read(path, ctx.logger);
|
|
132
|
+
const blocks = extractBlocks(src, ctx);
|
|
133
|
+
const ref = ctx.files[path] = { path, src, blocks };
|
|
134
|
+
resolveBlocks(ref, ctx);
|
|
135
|
+
}
|
|
136
|
+
return ctx.files[path];
|
|
116
137
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
format: fmt,
|
|
135
|
-
logger: LOGGER,
|
|
136
|
-
fs: {
|
|
137
|
-
isAbsolute,
|
|
138
|
-
resolve,
|
|
139
|
-
read: readText,
|
|
140
|
-
},
|
|
141
|
-
...ctx,
|
|
142
|
-
opts: {
|
|
143
|
-
comments: true,
|
|
144
|
-
...ctx.opts,
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
const { path: $path, src, blocks } = loadAndResolveBlocks(path, $ctx);
|
|
148
|
-
const parentDir = $ctx.fs.resolve($path, "..");
|
|
149
|
-
const meta = parseFileMeta(src);
|
|
150
|
-
const sorted = Object.values(blocks).sort(compareByKey("start"));
|
|
151
|
-
let prev = 0;
|
|
152
|
-
let res = [];
|
|
153
|
-
for (let block of sorted) {
|
|
154
|
-
if (meta.publish) {
|
|
155
|
-
res.push(src.substring(prev, Math.max(prev, block.matchStart)));
|
|
156
|
-
if (block.publish !== "no") {
|
|
157
|
-
res.push(`${fmt.prefix}${block.lang}\n${block.body}\n${fmt.suffix}\n`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (block.tangle) {
|
|
161
|
-
const dest = $ctx.fs.isAbsolute(block.tangle)
|
|
162
|
-
? block.tangle
|
|
163
|
-
: $ctx.fs.resolve(parentDir, `${meta.tangle || "."}${sep}${block.tangle}`);
|
|
164
|
-
let body = block.body;
|
|
165
|
-
if (!$ctx.outputs[dest]) {
|
|
166
|
-
if ($ctx.opts.comments && COMMENT_FORMATS[block.lang]) {
|
|
167
|
-
body = [
|
|
168
|
-
commentForLang(block.lang, `Tangled @ ${FMT_ISO_SHORT()} - DO NOT EDIT!`),
|
|
169
|
-
commentForLang(block.lang, `Source: ${$path}`),
|
|
170
|
-
"",
|
|
171
|
-
body,
|
|
172
|
-
].join("\n");
|
|
173
|
-
}
|
|
174
|
-
$ctx.outputs[dest] = body;
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
$ctx.outputs[dest] += "\n\n" + body;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
prev = block.matchEnd;
|
|
138
|
+
const tangleFile = (path, ctx = {}) => {
|
|
139
|
+
const fmt = ctx.format || BLOCK_FORMATS[extname(path)];
|
|
140
|
+
!fmt && illegalArgs(`unsupported file type: ${extname(path)}`);
|
|
141
|
+
const $ctx = {
|
|
142
|
+
files: {},
|
|
143
|
+
outputs: {},
|
|
144
|
+
format: fmt,
|
|
145
|
+
logger: LOGGER,
|
|
146
|
+
fs: {
|
|
147
|
+
isAbsolute,
|
|
148
|
+
resolve,
|
|
149
|
+
read: readText
|
|
150
|
+
},
|
|
151
|
+
...ctx,
|
|
152
|
+
opts: {
|
|
153
|
+
comments: true,
|
|
154
|
+
...ctx.opts
|
|
181
155
|
}
|
|
182
|
-
|
|
156
|
+
};
|
|
157
|
+
const { path: $path, src, blocks } = loadAndResolveBlocks(path, $ctx);
|
|
158
|
+
const parentDir = $ctx.fs.resolve($path, "..");
|
|
159
|
+
const meta = parseFileMeta(src);
|
|
160
|
+
const sorted = Object.values(blocks).sort(compareByKey("start"));
|
|
161
|
+
let prev = 0;
|
|
162
|
+
let res = [];
|
|
163
|
+
for (let block of sorted) {
|
|
183
164
|
if (meta.publish) {
|
|
184
|
-
|
|
185
|
-
|
|
165
|
+
res.push(src.substring(prev, Math.max(prev, block.matchStart)));
|
|
166
|
+
if (block.publish !== "no") {
|
|
167
|
+
res.push(
|
|
168
|
+
`${fmt.prefix}${block.lang}
|
|
169
|
+
${block.body}
|
|
170
|
+
${fmt.suffix}
|
|
171
|
+
`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (block.tangle) {
|
|
176
|
+
const dest = $ctx.fs.isAbsolute(block.tangle) ? block.tangle : $ctx.fs.resolve(
|
|
177
|
+
parentDir,
|
|
178
|
+
`${meta.tangle || "."}${sep}${block.tangle}`
|
|
179
|
+
);
|
|
180
|
+
let body = block.body;
|
|
181
|
+
if (!$ctx.outputs[dest]) {
|
|
182
|
+
if ($ctx.opts.comments && COMMENT_FORMATS[block.lang]) {
|
|
183
|
+
body = [
|
|
184
|
+
commentForLang(
|
|
185
|
+
block.lang,
|
|
186
|
+
`Tangled @ ${FMT_ISO_SHORT()} - DO NOT EDIT!`
|
|
187
|
+
),
|
|
188
|
+
commentForLang(block.lang, `Source: ${$path}`),
|
|
189
|
+
"",
|
|
190
|
+
body
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
$ctx.outputs[dest] = body;
|
|
194
|
+
} else {
|
|
195
|
+
$ctx.outputs[dest] += "\n\n" + body;
|
|
196
|
+
}
|
|
186
197
|
}
|
|
187
|
-
|
|
198
|
+
prev = block.matchEnd;
|
|
199
|
+
}
|
|
200
|
+
res.push(src.substring(prev));
|
|
201
|
+
if (meta.publish) {
|
|
202
|
+
const dest = $ctx.fs.resolve(parentDir, meta.publish);
|
|
203
|
+
$ctx.outputs[dest] = res.join("").trim();
|
|
204
|
+
}
|
|
205
|
+
return $ctx;
|
|
188
206
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
* @param files
|
|
201
|
-
* @param ctx
|
|
202
|
-
*/
|
|
203
|
-
export const tangleString = (fileID, files, ctx = {}) => tangleFile(fileID, {
|
|
204
|
-
fs: {
|
|
205
|
-
isAbsolute: (path) => path[0] === "/" || path[0] === "\\",
|
|
206
|
-
resolve: (...path) => path[path.length - 1],
|
|
207
|
-
read: (path, logger) => {
|
|
208
|
-
logger.debug("reading file ref", path);
|
|
209
|
-
const body = files[path];
|
|
210
|
-
return body !== undefined
|
|
211
|
-
? body
|
|
212
|
-
: illegalArgs(`missing file for ref: ${path}`);
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
...ctx,
|
|
207
|
+
const tangleString = (fileID, files, ctx = {}) => tangleFile(fileID, {
|
|
208
|
+
fs: {
|
|
209
|
+
isAbsolute: (path) => path[0] === "/" || path[0] === "\\",
|
|
210
|
+
resolve: (...path) => path[path.length - 1],
|
|
211
|
+
read: (path, logger) => {
|
|
212
|
+
logger.debug("reading file ref", path);
|
|
213
|
+
const body = files[path];
|
|
214
|
+
return body !== void 0 ? body : illegalArgs(`missing file for ref: ${path}`);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
...ctx
|
|
216
218
|
});
|
|
219
|
+
export {
|
|
220
|
+
tangleFile,
|
|
221
|
+
tangleString
|
|
222
|
+
};
|