@dogsbay/format-hugo 0.2.0-beta.101

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Hugo shortcode scanner.
3
+ *
4
+ * Hugo content is markdown PLUS a template language, and the template has two
5
+ * delimiter forms with different evaluation semantics:
6
+ *
7
+ * {{< sc >}} output inserted RAW — not markdown-processed
8
+ * {{% sc %}} inner content IS markdown-processed
9
+ * {{< sc >}}…{{< /sc >}} paired (has a body)
10
+ * {{< sc "pos" k="v" >}} positional AND named arguments, in one call
11
+ *
12
+ * A regex sweep cannot read this. Shortcodes nest (`tabs` contains `tab`
13
+ * contains `note`), the two delimiter forms mix freely INSIDE one another
14
+ * (k8s writes `{{< tabs >}}{{% tab %}}`), and a shortcode inside a fenced code
15
+ * block is literal text that must survive byte-exact — the Hugo docs
16
+ * themselves are full of those.
17
+ *
18
+ * So this is a scanner: it walks the source once, tracks fences, and returns
19
+ * spans with their nesting resolved.
20
+ */
21
+ export type ShortcodeDelimiter = "angle" | "percent";
22
+ export interface ShortcodeArgs {
23
+ /** Positional arguments, in order: `{{< youtube dQw4w9WgXcQ >}}`. */
24
+ positional: string[];
25
+ /** Named arguments: `{{< tabs name="x" >}}`. */
26
+ named: Record<string, string>;
27
+ }
28
+ export interface ShortcodeNode {
29
+ kind: "shortcode";
30
+ name: string;
31
+ args: ShortcodeArgs;
32
+ delimiter: ShortcodeDelimiter;
33
+ /** Inner source between the open and close tags; "" when self-closing. */
34
+ inner: string;
35
+ /** Verbatim source span, open tag through close tag. */
36
+ raw: string;
37
+ /** True when the shortcode had no closing tag. */
38
+ selfClosing: boolean;
39
+ /** 1-based line of the opening tag. */
40
+ line: number;
41
+ }
42
+ export interface TextNode {
43
+ kind: "text";
44
+ text: string;
45
+ }
46
+ export type ScanNode = ShortcodeNode | TextNode;
47
+ /** Parse the argument string of a shortcode call. */
48
+ export declare function parseArgs(text: string): ShortcodeArgs;
49
+ /**
50
+ * Split source into alternating text and shortcode nodes, at the TOP level.
51
+ * A paired shortcode's body stays in `inner` for the caller to scan
52
+ * recursively — that is what makes nesting work without a second parser.
53
+ */
54
+ export declare function scanShortcodes(source: string): ScanNode[];
55
+ /**
56
+ * Hugo's content-type body markers.
57
+ *
58
+ * Kubernetes pages carry `<!-- overview -->`, `<!-- body -->`, `<!-- steps -->`
59
+ * HTML comments that the Docsy/k8s templates use to slot content into a page
60
+ * layout. They are STRUCTURAL, not decorative: left in place they render as
61
+ * stray comments, and a serializer that treats them as raw HTML forces the
62
+ * page into a JSX dialect for no reason.
63
+ */
64
+ export declare const CONTENT_TYPE_MARKERS: string[];
65
+ export declare function stripContentTypeMarkers(source: string): string;
66
+ //# sourceMappingURL=shortcodes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcodes.d.ts","sourceRoot":"","sources":["../src/shortcodes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAC;AAErD,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;IACZ,kDAAkD;IAClD,WAAW,EAAE,OAAO,CAAC;IACrB,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,QAAQ,CAAC;AAuDhD,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAwBrD;AA0CD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAyDzD;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,UAWhC,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAG9D"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Hugo shortcode scanner.
3
+ *
4
+ * Hugo content is markdown PLUS a template language, and the template has two
5
+ * delimiter forms with different evaluation semantics:
6
+ *
7
+ * {{< sc >}} output inserted RAW — not markdown-processed
8
+ * {{% sc %}} inner content IS markdown-processed
9
+ * {{< sc >}}…{{< /sc >}} paired (has a body)
10
+ * {{< sc "pos" k="v" >}} positional AND named arguments, in one call
11
+ *
12
+ * A regex sweep cannot read this. Shortcodes nest (`tabs` contains `tab`
13
+ * contains `note`), the two delimiter forms mix freely INSIDE one another
14
+ * (k8s writes `{{< tabs >}}{{% tab %}}`), and a shortcode inside a fenced code
15
+ * block is literal text that must survive byte-exact — the Hugo docs
16
+ * themselves are full of those.
17
+ *
18
+ * So this is a scanner: it walks the source once, tracks fences, and returns
19
+ * spans with their nesting resolved.
20
+ */
21
+ /**
22
+ * Byte ranges that are LITERAL: fenced code blocks and inline code spans.
23
+ * A shortcode inside them is documentation about shortcodes, not a call.
24
+ */
25
+ function literalRanges(source) {
26
+ const ranges = [];
27
+ const lines = source.split("\n");
28
+ let offset = 0;
29
+ let fence = null;
30
+ for (const line of lines) {
31
+ const lineStart = offset;
32
+ offset += line.length + 1;
33
+ const trimmed = line.trim();
34
+ if (!fence) {
35
+ const open = /^(`{3,}|~{3,})/.exec(trimmed);
36
+ if (open) {
37
+ fence = { marker: open[1], start: lineStart };
38
+ continue;
39
+ }
40
+ // Inline code spans on a non-fence line.
41
+ for (const m of line.matchAll(/`+[^`]*`+/g)) {
42
+ ranges.push([lineStart + m.index, lineStart + m.index + m[0].length]);
43
+ }
44
+ }
45
+ else {
46
+ // CommonMark: the closer is the same character, at least as long, and
47
+ // carries no info string.
48
+ const close = new RegExp(`^${fence.marker[0]}{${fence.marker.length},}[ \\t]*$`);
49
+ if (close.test(trimmed)) {
50
+ ranges.push([fence.start, offset]);
51
+ fence = null;
52
+ }
53
+ }
54
+ }
55
+ // An unterminated fence still protects everything to end of file: its
56
+ // content is code either way, and editing it is the worse failure.
57
+ if (fence)
58
+ ranges.push([fence.start, source.length]);
59
+ return ranges;
60
+ }
61
+ /** Parse the argument string of a shortcode call. */
62
+ export function parseArgs(text) {
63
+ const positional = [];
64
+ const named = {};
65
+ // Named first: `k="v"`, `k='v'`, `k=bare`. Then whatever is left is
66
+ // positional. Quoted positional args keep their spaces.
67
+ const consumed = [];
68
+ for (const m of text.matchAll(/([A-Za-z_][\w.-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))/g)) {
69
+ named[m[1]] = m[2] ?? m[3] ?? m[4] ?? "";
70
+ consumed.push([m.index, m.index + m[0].length]);
71
+ }
72
+ let rest = "";
73
+ let cursor = 0;
74
+ for (const [s, e] of consumed) {
75
+ rest += text.slice(cursor, s);
76
+ cursor = e;
77
+ }
78
+ rest += text.slice(cursor);
79
+ for (const m of rest.matchAll(/"([^"]*)"|'([^']*)'|(\S+)/g)) {
80
+ const value = m[1] ?? m[2] ?? m[3];
81
+ if (value !== undefined && value !== "")
82
+ positional.push(value);
83
+ }
84
+ return { positional, named };
85
+ }
86
+ /** Every shortcode tag in the source, skipping literal ranges. */
87
+ function findTags(source) {
88
+ const literals = literalRanges(source);
89
+ const inLiteral = (i) => literals.some(([s, e]) => i >= s && i < e);
90
+ const tags = [];
91
+ // `{{<` or `{{%`, optional `/`, name, args, matching close delimiter.
92
+ // The args run is lazy and skips quoted regions so a `>` or `%` inside a
93
+ // quoted value cannot terminate the tag early.
94
+ // Note the `\s*` on BOTH sides of the optional slash. Hugo accepts
95
+ // `{{< /note >}}` and `{{</ note >}}` alike, and the kubernetes corpus uses
96
+ // the second form for 160 of its closers — requiring the name to follow the
97
+ // slash immediately left every one of those shortcodes unpaired, which would
98
+ // have swallowed each mermaid diagram's body as loose text.
99
+ const re = /\{\{([<%])(-?)\s*(\/?)\s*([A-Za-z_][\w./-]*)((?:[^"'>%]|"[^"]*"|'[^']*')*?)(-?)\s*([>%])\}\}/g;
100
+ for (const m of source.matchAll(re)) {
101
+ const open = m[1];
102
+ const close = m[7];
103
+ // `{{< … >}}` and `{{% … %}}` — the delimiters must agree.
104
+ if ((open === "<" && close !== ">") || (open === "%" && close !== "%"))
105
+ continue;
106
+ if (inLiteral(m.index))
107
+ continue;
108
+ // `{{< tab name="x" include="y" />}}` — Hugo's explicit self-close. The
109
+ // kubeadm reference pages use it 118 times. Without recognising it, the
110
+ // tag looks like an opener with no closer, so a LATER `{{< /tab >}}` from
111
+ // a different group could be paired to it and swallow everything between.
112
+ const rawArgs = (m[5] ?? "").replace(/\s*\/\s*$/, "");
113
+ tags.push({
114
+ start: m.index,
115
+ end: m.index + m[0].length,
116
+ name: m[4],
117
+ closing: m[3] === "/",
118
+ explicitSelfClose: /\/\s*$/.test(m[5] ?? ""),
119
+ delimiter: open === "<" ? "angle" : "percent",
120
+ args: parseArgs(rawArgs),
121
+ line: source.slice(0, m.index).split("\n").length,
122
+ });
123
+ }
124
+ return tags;
125
+ }
126
+ /**
127
+ * Split source into alternating text and shortcode nodes, at the TOP level.
128
+ * A paired shortcode's body stays in `inner` for the caller to scan
129
+ * recursively — that is what makes nesting work without a second parser.
130
+ */
131
+ export function scanShortcodes(source) {
132
+ const tags = findTags(source);
133
+ const out = [];
134
+ let cursor = 0;
135
+ for (let i = 0; i < tags.length; i++) {
136
+ const tag = tags[i];
137
+ if (tag.start < cursor)
138
+ continue; // already consumed inside a pair
139
+ if (tag.closing)
140
+ continue; // orphan closer — treated as text below
141
+ // Find the matching closer, counting nested opens of the SAME name.
142
+ // An explicitly self-closed tag has no body and must not consume one.
143
+ let closer;
144
+ if (!tag.explicitSelfClose) {
145
+ let depth = 1;
146
+ for (let j = i + 1; j < tags.length; j++) {
147
+ if (tags[j].name !== tag.name)
148
+ continue;
149
+ if (tags[j].explicitSelfClose)
150
+ continue;
151
+ depth += tags[j].closing ? -1 : 1;
152
+ if (depth === 0) {
153
+ closer = tags[j];
154
+ break;
155
+ }
156
+ }
157
+ }
158
+ if (tag.start > cursor)
159
+ out.push({ kind: "text", text: source.slice(cursor, tag.start) });
160
+ if (closer) {
161
+ out.push({
162
+ kind: "shortcode",
163
+ name: tag.name,
164
+ args: tag.args,
165
+ delimiter: tag.delimiter,
166
+ inner: source.slice(tag.end, closer.start),
167
+ raw: source.slice(tag.start, closer.end),
168
+ selfClosing: false,
169
+ line: tag.line,
170
+ });
171
+ cursor = closer.end;
172
+ }
173
+ else {
174
+ out.push({
175
+ kind: "shortcode",
176
+ name: tag.name,
177
+ args: tag.args,
178
+ delimiter: tag.delimiter,
179
+ inner: "",
180
+ raw: source.slice(tag.start, tag.end),
181
+ selfClosing: true,
182
+ line: tag.line,
183
+ });
184
+ cursor = tag.end;
185
+ }
186
+ }
187
+ if (cursor < source.length)
188
+ out.push({ kind: "text", text: source.slice(cursor) });
189
+ return out;
190
+ }
191
+ /**
192
+ * Hugo's content-type body markers.
193
+ *
194
+ * Kubernetes pages carry `<!-- overview -->`, `<!-- body -->`, `<!-- steps -->`
195
+ * HTML comments that the Docsy/k8s templates use to slot content into a page
196
+ * layout. They are STRUCTURAL, not decorative: left in place they render as
197
+ * stray comments, and a serializer that treats them as raw HTML forces the
198
+ * page into a JSX dialect for no reason.
199
+ */
200
+ export const CONTENT_TYPE_MARKERS = [
201
+ "overview",
202
+ "body",
203
+ "steps",
204
+ "prerequisites",
205
+ "objectives",
206
+ "lessoncontent",
207
+ "whatsnext",
208
+ "cleanup",
209
+ "discussion",
210
+ "editorschoice",
211
+ ];
212
+ export function stripContentTypeMarkers(source) {
213
+ const names = CONTENT_TYPE_MARKERS.join("|");
214
+ return source.replace(new RegExp(`^[ \\t]*<!--\\s*(?:${names})\\s*-->[ \\t]*$\\n?`, "gim"), "");
215
+ }
216
+ //# sourceMappingURL=shortcodes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shortcodes.js","sourceRoot":"","sources":["../src/shortcodes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type HugoLedgerEntry, type ShortcodeRegistry } from "./registry.js";
2
+ import type { HugoConfig } from "./config.js";
3
+ export interface TranslateOptions {
4
+ config: HugoConfig;
5
+ /** Extra/overriding shortcode mappings, merged last. */
6
+ shortcodes?: ShortcodeRegistry;
7
+ /** Include the Kubernetes site layer. Auto-enabled when the site looks like k8s. */
8
+ kubernetes?: boolean;
9
+ resolveRef?: (target: string, from?: string) => string | undefined;
10
+ readContentFile?: (path: string, fromFile?: string) => string | undefined;
11
+ /** Source file, relative to the content root — stamped onto ledger entries. */
12
+ file?: string;
13
+ }
14
+ export interface TranslateResult {
15
+ markdown: string;
16
+ ledger: HugoLedgerEntry[];
17
+ }
18
+ export declare function translateHugo(source: string, opts: TranslateOptions): TranslateResult;
19
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"AAYA,OAAO,EAA0C,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGrH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,wDAAwD;IACxD,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,oFAAoF;IACpF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACnE,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1E,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAKD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,eAAe,CA8DrF"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Hugo source → Dogsbay-MD.
3
+ *
4
+ * Translate-then-parse, the approach format-gitbook and format-myst use: turn
5
+ * the Hugo dialect into Dogsbay-MD and hand it to the existing parser, rather
6
+ * than writing a second markdown parser that will drift from the first.
7
+ *
8
+ * The never-drop rule: a shortcode with no mapping is emitted VERBATIM and
9
+ * recorded. A migration that silently deletes what it does not understand is
10
+ * worse than one that leaves a visible marker.
11
+ */
12
+ import { scanShortcodes, stripContentTypeMarkers } from "./shortcodes.js";
13
+ import { composeRegistry } from "./registry.js";
14
+ import { BUILTIN_SHORTCODES } from "./builtins.js";
15
+ import { KUBERNETES_SHORTCODES } from "./kubernetes.js";
16
+ /** Depth cap: an include cycle must not recurse forever. */
17
+ const MAX_DEPTH = 8;
18
+ export function translateHugo(source, opts) {
19
+ const ledger = [];
20
+ const registry = composeRegistry(BUILTIN_SHORTCODES, opts.kubernetes === false ? {} : KUBERNETES_SHORTCODES, opts.shortcodes ?? {});
21
+ const report = (entry) => {
22
+ ledger.push({ ...entry, file: opts.file });
23
+ };
24
+ const translate = (text, depth) => {
25
+ if (depth > MAX_DEPTH)
26
+ return text;
27
+ const ctx = {
28
+ translate: (inner) => translate(inner, depth + 1),
29
+ config: opts.config,
30
+ report,
31
+ resolveRef: opts.resolveRef,
32
+ readContentFile: (path, from) => opts.readContentFile?.(path, from ?? opts.file),
33
+ file: opts.file,
34
+ };
35
+ let out = "";
36
+ for (const node of scanShortcodes(text)) {
37
+ if (node.kind === "text") {
38
+ out += node.text;
39
+ continue;
40
+ }
41
+ const mapping = registry[node.name];
42
+ if (!mapping) {
43
+ // NEVER drop. Preserve verbatim + record.
44
+ report({
45
+ shortcode: node.name,
46
+ action: "preserved",
47
+ note: "no mapping — kept verbatim",
48
+ line: node.line,
49
+ });
50
+ out += node.raw;
51
+ continue;
52
+ }
53
+ const result = mapping(node, ctx);
54
+ if (result === null)
55
+ continue; // deliberate drop; the mapping recorded it
56
+ if (result === undefined) {
57
+ report({
58
+ shortcode: node.name,
59
+ action: "preserved",
60
+ note: "mapping declined — kept verbatim",
61
+ line: node.line,
62
+ });
63
+ out += node.raw;
64
+ continue;
65
+ }
66
+ out += result;
67
+ }
68
+ return out;
69
+ };
70
+ // Body markers are structural template slots, not content: left in place
71
+ // they render as stray comments and push the page into a raw-HTML path.
72
+ const cleaned = stripContentTypeMarkers(source);
73
+ const markdown = tidy(translate(cleaned, 0));
74
+ return { markdown, ledger };
75
+ }
76
+ /**
77
+ * A block directive must start its own line, and Dogsbay-MD closes a `:::`
78
+ * block at a line that is only colons. A shortcode written inline
79
+ * (`text {{< note >}}…{{< /note >}}`) would otherwise emit `text :::note`,
80
+ * which is a paragraph containing colons rather than a callout.
81
+ */
82
+ function tidy(markdown) {
83
+ return (markdown
84
+ // An OPENER is a colon run followed by a directive name, so it must
85
+ // begin its own line.
86
+ // `[^\n:]` — NOT `[^\n]`. A nested group uses a longer fence
87
+ // (`::::tabs` around `:::tab`), and a class that admits `:` matched the
88
+ // run's own first colon, splitting `::::tabs` into `:` + `:::tabs` and
89
+ // leaving a stray colon in the page.
90
+ .replace(/([^\n:])\n?(:{3,}[A-Za-z])/g, "$1\n\n$2")
91
+ // A CLOSER is a colon run NOT followed by a name. Only break after it
92
+ // when text follows on the same line — the negative lookahead is what
93
+ // keeps this rule from splitting `:::note` into `:::` + `note`, which
94
+ // is exactly what a naive version of this function did.
95
+ .replace(/(^|\n)(:{3,})(?![A-Za-z{:])[ \t]*([^\s:][^\n]*)/g, "$1$2\n\n$3")
96
+ .replace(/\n{3,}/g, "\n\n")
97
+ .replace(/[ \t]+$/gm, ""));
98
+ }
99
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@dogsbay/format-hugo",
3
+ "version": "0.2.0-beta.101",
4
+ "description": "Hugo format plugin for Dogsbay — imports Hugo/Docsy sites into TreeNode[] by scanning shortcodes and translating them to Dogsbay-MD.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./cli": {
14
+ "types": "./dist/cli.d.ts",
15
+ "import": "./dist/cli.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "README.md"
21
+ ],
22
+ "dependencies": {
23
+ "smol-toml": "^1.4.2",
24
+ "yaml": "^2.9.0",
25
+ "@dogsbay/format-dogsbay-md": "0.2.0-beta.101",
26
+ "@dogsbay/types": "0.2.0-beta.101"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^25.9.5",
30
+ "typescript": "^5.9.3",
31
+ "vitest": "^4.1.10"
32
+ },
33
+ "license": "MIT",
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest"
38
+ }
39
+ }