@thi.ng/tangle 0.1.91 → 0.2.1

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**: 2024-03-01T15:22:50Z
3
+ - **Last updated**: 2024-03-07T20:40:47Z
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.
@@ -9,6 +9,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
9
9
  **Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
10
10
  and/or version bumps of transitive dependencies.
11
11
 
12
+ ## [0.2.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/tangle@0.2.0) (2024-03-07)
13
+
14
+ #### 🚀 Features
15
+
16
+ - add support for code blocks in JS/TS docstrings ([5b68f19](https://github.com/thi-ng/umbrella/commit/5b68f19))
17
+ - update CodeBlockFormat, allow regexps as prefix/suffix
18
+ - add optional body xform fn
19
+ - add ".js", ".ts" as supported file extensions
20
+ - update matching logic in extractBlocks()
21
+
22
+ #### 🩹 Bug fixes
23
+
24
+ - off-by-one error in extractBlocks() ([d7d7e03](https://github.com/thi-ng/umbrella/commit/d7d7e03))
25
+
12
26
  ### [0.1.86](https://github.com/thi-ng/umbrella/tree/@thi.ng/tangle@0.1.86) (2024-02-22)
13
27
 
14
28
  #### ♻️ Refactoring
package/README.md CHANGED
@@ -48,12 +48,18 @@ introduction. For all LP projects, a so-called "tangling" step is required to
48
48
  produce workable, standard source code files from these documents. This package
49
49
  provides just that:
50
50
 
51
- Extract, expand, transclude, combine and assemble code blocks from Markdown or
52
- [Org-mode](https://orgmode.org) files into actual/traditional source files. A
53
- single LP source file can contain code for multiple languages. Each code block
54
- can define its target file and include [noweb-style
51
+ Extract, expand, transclude, combine and assemble code blocks from arbitrary text files into actual/traditional source code files. The following input formats are supported:
52
+
53
+ - Markdown
54
+ - Markdown codeblocks embedded inside docstrings of JavaScript/TypeScript source files
55
+ - [Org-mode](https://orgmode.org)
56
+
57
+ A single input file can contain code for multiple languages. Each individual
58
+ code block can define its own target file and include [noweb-style
55
59
  references](https://orgmode.org/manual/Noweb-Reference-Syntax.html) to other
56
- code blocks, either from the same or even from other files.
60
+ code blocks, either from the same or even from other files. If multiple code
61
+ blocks in the same input file reference the same output file, they will be
62
+ concatenated.
57
63
 
58
64
  The package provides both a basic API and a CLI wrapper to perform the
59
65
  "tangling" tasks (an expression borrowed from
@@ -326,7 +332,7 @@ For Node.js REPL:
326
332
  const tangle = await import("@thi.ng/tangle");
327
333
  ```
328
334
 
329
- Package sizes (brotli'd, pre-treeshake): ESM: 1.83 KB
335
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.99 KB
330
336
 
331
337
  ## Dependencies
332
338
 
package/api.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Fn2, FnAnyT, IObjectOf, Predicate } from "@thi.ng/api";
1
+ import type { Fn, Fn2, FnAnyT, IObjectOf, Predicate } from "@thi.ng/api";
2
2
  import { type ILogger } from "@thi.ng/logger";
3
3
  export interface Block {
4
4
  id: string;
@@ -59,8 +59,24 @@ export interface TangleOpts {
59
59
  comments: boolean;
60
60
  }
61
61
  export interface CodeBlockFormat {
62
- prefix: string;
63
- suffix: string;
62
+ /**
63
+ * String or regexp matching the beginning of a code block. If given a
64
+ * regexp, it will become part of a larger one and any regexp flags given
65
+ * here will be ignored. Also the given regexp pattern will always be
66
+ * prefixed with the line-start symbol (`^`), so it must not be given
67
+ * here...
68
+ */
69
+ prefix: string | RegExp;
70
+ /**
71
+ * String or regexp matching the end of a code block. If given a
72
+ * regexp, it MUST have the multiline flag enabled!
73
+ */
74
+ suffix: string | RegExp;
75
+ /**
76
+ * Optional function to transform the codeblock's body (e.g. to remove any
77
+ * prefix characters or remove trailing whitespace).
78
+ */
79
+ xform?: Fn<string, string>;
64
80
  }
65
81
  export declare const BLOCK_FORMATS: IObjectOf<CodeBlockFormat>;
66
82
  export declare const COMMENT_FORMATS: IObjectOf<string | [string, string]>;
package/api.js CHANGED
@@ -1,4 +1,15 @@
1
1
  import { ConsoleLogger, LogLevel, ROOT } from "@thi.ng/logger";
2
+ import { split } from "@thi.ng/strings";
3
+ import { map, str, transduce } from "@thi.ng/transducers";
4
+ const JS_DOC = {
5
+ prefix: /^\s*\*\s+```/,
6
+ suffix: /^\s*\*\s+```/m,
7
+ xform: (body) => transduce(
8
+ map((x) => x.replace(/^\s*\*\s?/, "")),
9
+ str("\n"),
10
+ split(body)
11
+ )
12
+ };
2
13
  const BLOCK_FORMATS = {
3
14
  ".md": {
4
15
  prefix: "```",
@@ -7,7 +18,9 @@ const BLOCK_FORMATS = {
7
18
  ".org": {
8
19
  prefix: "#+BEGIN_SRC ",
9
20
  suffix: "#+END_SRC"
10
- }
21
+ },
22
+ ".js": JS_DOC,
23
+ ".ts": JS_DOC
11
24
  };
12
25
  const C = "//";
13
26
  const P = "#";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/tangle",
3
- "version": "0.1.91",
3
+ "version": "0.2.1",
4
4
  "description": "Literate programming code block tangling / codegen utility, inspired by org-mode & noweb",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -33,23 +33,24 @@
33
33
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
34
34
  "doc:readme": "bun ../../tools/src/module-stats.ts && bun ../../tools/src/readme.ts",
35
35
  "pub": "yarn npm publish --access public",
36
- "test": "testament test"
36
+ "test": "testament test",
37
+ "tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
37
38
  },
38
39
  "dependencies": {
39
- "@thi.ng/api": "^8.9.27",
40
- "@thi.ng/args": "^2.3.20",
41
- "@thi.ng/checks": "^3.5.1",
42
- "@thi.ng/compare": "^2.2.23",
43
- "@thi.ng/date": "^2.7.4",
44
- "@thi.ng/errors": "^2.4.19",
45
- "@thi.ng/file-io": "^1.3.5",
46
- "@thi.ng/logger": "^3.0.4",
47
- "@thi.ng/strings": "^3.7.20",
48
- "@thi.ng/transducers": "^8.9.9"
40
+ "@thi.ng/api": "^8.9.29",
41
+ "@thi.ng/args": "^2.3.22",
42
+ "@thi.ng/checks": "^3.5.2",
43
+ "@thi.ng/compare": "^2.2.25",
44
+ "@thi.ng/date": "^2.7.6",
45
+ "@thi.ng/errors": "^2.4.20",
46
+ "@thi.ng/file-io": "^1.3.7",
47
+ "@thi.ng/logger": "^3.0.5",
48
+ "@thi.ng/strings": "^3.7.22",
49
+ "@thi.ng/transducers": "^8.9.11"
49
50
  },
50
51
  "devDependencies": {
51
52
  "@microsoft/api-extractor": "^7.40.1",
52
- "@thi.ng/testament": "^0.4.21",
53
+ "@thi.ng/testament": "^0.4.22",
53
54
  "esbuild": "^0.20.0",
54
55
  "rimraf": "^5.0.5",
55
56
  "typedoc": "^0.25.7",
@@ -93,5 +94,5 @@
93
94
  "status": "alpha",
94
95
  "year": 2022
95
96
  },
96
- "gitHead": "df9e312af741d87e6b450afcfea6a6e381662b1e\n"
97
+ "gitHead": "69100942474942f7446ac645d59d91e7dfc352f9\n"
97
98
  }
package/tangle.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { isPlainObject, isString } from "@thi.ng/checks";
2
2
  import { compareByKey } from "@thi.ng/compare";
3
3
  import { FMT_ISO_SHORT } from "@thi.ng/date";
4
- import { defError } from "@thi.ng/errors/deferror";
5
- import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
4
+ import { defError, illegalArgs, illegalState } from "@thi.ng/errors";
6
5
  import { readText } from "@thi.ng/file-io";
7
6
  import { split } from "@thi.ng/strings";
8
7
  import { assocObj, map, transduce } from "@thi.ng/transducers";
@@ -19,38 +18,51 @@ const UnknownBlockError = defError(
19
18
  const extractBlocks = (src, { format, logger }) => {
20
19
  let nextID = 0;
21
20
  const blocks = {};
22
- const re = new RegExp(
23
- `^${format.prefix.replace("+", "\\+")}(\\w+)\\s+(.+)$`,
21
+ const prefix = new RegExp(
22
+ (isString(format.prefix) ? `^${format.prefix.replace("+", "\\+")}` : format.prefix.source) + `(\\w+)\\s+(.+)$`,
24
23
  "gm"
25
24
  );
26
- let match;
27
- while (match = re.exec(src)) {
28
- let { id, tangle, noweb, publish } = parseBlockHeader(match[2]);
25
+ const suffix = isString(format.suffix) ? new RegExp(`${format.suffix.replace("+", "\\+")}`) : format.suffix;
26
+ let matchPrefix;
27
+ while (matchPrefix = prefix.exec(src)) {
28
+ let { id, tangle, noweb, publish } = parseBlockHeader(matchPrefix[2]);
29
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);
30
+ const matchStart = matchPrefix.index;
31
+ const start = src.indexOf("\n", matchStart + 1) + 1;
34
32
  logger.debug(
35
- "codeblock",
33
+ "codeblock ID:",
36
34
  id,
37
- start,
38
- end,
35
+ "matchStart:",
39
36
  matchStart,
40
- src.substring(start, 8)
37
+ "start:",
38
+ start
39
+ );
40
+ const matchSuffix = suffix.exec(src.substring(start));
41
+ if (!matchSuffix)
42
+ illegalState("no codeblock end found");
43
+ const end = start + matchSuffix.index;
44
+ const matchEnd = end + matchSuffix[0].length + 1;
45
+ logger.debug(
46
+ "codeblock ID:",
47
+ id,
48
+ "end:",
49
+ end,
50
+ "matchEnd:",
51
+ matchEnd,
52
+ matchSuffix[0]
41
53
  );
42
- const body = src.substring(start, end);
54
+ const body = src.substring(start, end - 1);
43
55
  blocks[id] = {
44
56
  id,
45
- lang: match[1],
57
+ lang: matchPrefix[1],
46
58
  tangle,
47
59
  publish,
48
60
  noweb,
49
61
  start,
50
62
  end,
51
63
  matchStart,
52
- matchEnd: end + format.suffix.length + 2,
53
- body
64
+ matchEnd,
65
+ body: format.xform ? format.xform(body) : body
54
66
  };
55
67
  }
56
68
  return blocks;