@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-03T12:13:31Z
3
+ - **Last updated**: 2023-12-18T13:41:20Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -322,7 +322,7 @@ For Node.js REPL:
322
322
  const tangle = await import("@thi.ng/tangle");
323
323
  ```
324
324
 
325
- Package sizes (brotli'd, pre-treeshake): ESM: 1.84 KB
325
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.83 KB
326
326
 
327
327
  ## Dependencies
328
328
 
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
- export const BLOCK_FORMATS = {
4
- ".md": {
5
- prefix: "```",
6
- suffix: "```",
7
- },
8
- ".org": {
9
- prefix: "#+BEGIN_SRC ",
10
- suffix: "#+END_SRC",
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
- export 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,
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
- export let LOGGER = new ConsoleLogger("tangle", LogLevel.INFO);
57
- export const setLogger = (logger) => {
58
- LOGGER = logger;
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
- // thing:no-export
2
- import { ParseError, flag, parse, usage, } from "@thi.ng/args";
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
- noComments: flag({ default: false, desc: "don't generate comments" }),
9
- debug: flag({ alias: "d", default: false, desc: "enable debug output" }),
10
- dryRun: flag({
11
- default: false,
12
- desc: "enable dry run (don't overwrite files)",
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
- export const INSTALL_DIR = resolve(`${process.argv[2]}/..`);
16
- export const PKG = readJSON(`${INSTALL_DIR}/package.json`);
17
- export const APP_NAME = PKG.name.split("/")[1];
18
- export const HEADER = `
19
-
20
- ██
21
- ${PKG.name} ${PKG.version}
22
- Literate programming code block tangling
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
- lineWidth: process.stdout.columns,
28
- prefix: `${HEADER}
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
- showGroupNames: true,
34
- paramWidth: 20,
37
+ showGroupNames: true,
38
+ paramWidth: 20
35
39
  };
36
40
  const showUsage = () => {
37
- process.stderr.write(usage(argOpts, usageOpts));
38
- process.exit(1);
41
+ process.stderr.write(usage(argOpts, usageOpts));
42
+ process.exit(1);
39
43
  };
40
44
  try {
41
- const result = parse(argOpts, process.argv, { start: 3, usageOpts });
42
- if (!result)
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.67",
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 clean && tsc --declaration",
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.11",
38
- "@thi.ng/args": "^2.2.45",
39
- "@thi.ng/checks": "^3.4.11",
40
- "@thi.ng/compare": "^2.2.7",
41
- "@thi.ng/date": "^2.5.5",
42
- "@thi.ng/errors": "^2.4.5",
43
- "@thi.ng/file-io": "^1.0.4",
44
- "@thi.ng/logger": "^2.0.1",
45
- "@thi.ng/strings": "^3.7.2",
46
- "@thi.ng/transducers": "^8.8.14"
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.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": ">=14"
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": "25f2ac8ff795a432a930119661b364d4d93b59a0\n"
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 { BLOCK_FORMATS, COMMENT_FORMATS, LOGGER, } from "./api.js";
11
- const UnknownBlockError = defError((err) => `can't include unknown block ID: ${err[0]} (via ${err[1]})`, () => "");
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
- let nextID = 0;
14
- const blocks = {};
15
- const re = new RegExp(`^${format.prefix.replace("+", "\\+")}(\\w+)\\s+(.+)$`, "gm");
16
- let match;
17
- while ((match = re.exec(src))) {
18
- let { id, tangle, noweb, publish } = parseBlockHeader(match[2]);
19
- !id && (id = `__block-${nextID++}`);
20
- const matchStart = match.index;
21
- const start = src.indexOf("\n", matchStart) + 1;
22
- const end = src.indexOf(`\n${format.suffix}`, matchStart + 1);
23
- logger.debug("codeblock", id, start, end, matchStart, src.substring(start, 8));
24
- const body = src.substring(start, end);
25
- blocks[id] = {
26
- id,
27
- lang: match[1],
28
- tangle,
29
- publish,
30
- noweb,
31
- start,
32
- end,
33
- matchStart,
34
- matchEnd: end + format.suffix.length + 2,
35
- body,
36
- };
37
- }
38
- return blocks;
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
- if (block.resolved)
42
- return;
43
- if (block.noweb === "no") {
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
- block.body = block.body.replace(/\\</g, "<");
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
- for (let id in ref.blocks) {
83
- resolveBlock(ref.blocks[id], ref, ctx);
84
- }
85
- return ref;
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
- if (!src.startsWith("---\n"))
89
- return {};
90
- const res = {};
91
- for (let line of split(src.substring(4))) {
92
- if (line === "---")
93
- break;
94
- const [key, val] = line.split(/:\s+/g);
95
- res[key.trim()] = val.trim();
96
- }
97
- return res;
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(map((x) => x.split(":")), assocObj(), header.split(/\s+/));
100
- const parametricBody = (body, params) => body.replace(/\{\{(\w+)\}\}/g, (_, id) => params[id] != null ? params[id] : id);
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
- const syntax = COMMENT_FORMATS[lang];
103
- return isString(syntax)
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
- path = ctx.fs.resolve(path);
109
- if (!ctx.files[path]) {
110
- const src = ctx.fs.read(path, ctx.logger);
111
- const blocks = extractBlocks(src, ctx);
112
- const ref = (ctx.files[path] = { path, src, blocks });
113
- resolveBlocks(ref, ctx);
114
- }
115
- return ctx.files[path];
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
- * Takes a file `path` and partial {@link TangleCtx}. Reads "file"
119
- * (implementation specific, could be from memory or other source) and then
120
- * performs all tangling steps on the document body, i.e. expanding &
121
- * transcluding code blocks, generating outputs for various target files etc.
122
- * Returns updated TangleCtx with all generated outputs stored under the
123
- * {@link TangleCtx.outputs} key.
124
- *
125
- * @param path
126
- * @param ctx
127
- */
128
- export const tangleFile = (path, ctx = {}) => {
129
- const fmt = ctx.format || BLOCK_FORMATS[extname(path)];
130
- !fmt && illegalArgs(`unsupported file type: ${extname(path)}`);
131
- const $ctx = {
132
- files: {},
133
- outputs: {},
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
- res.push(src.substring(prev));
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
- const dest = $ctx.fs.resolve(parentDir, meta.publish);
185
- $ctx.outputs[dest] = res.join("").trim();
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
- return $ctx;
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
- * In-memory version of {@link tangleFile}. Take a file name and an object of
191
- * file names and their respective contents, then calls {@link tangleFile} with
192
- * a customized {@link TangleCtx} which resolves code block refs using given
193
- * virtual "file system" (of sorts).
194
- *
195
- * @remarks
196
- * Relative file reference paths are only supported if they refer to children or
197
- * siblings, i.e. `foo/bar.md` is okay, but `../foo/bar.md` is NOT supported.
198
- *
199
- * @param fileID
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
+ };