@dogsbay/format-asciidoc 0.2.0-beta.48
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/README.md +40 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +239 -0
- package/dist/cli.js.map +1 -0
- package/dist/detect.d.ts +42 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +56 -0
- package/dist/detect.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/inline-walker.d.ts +17 -0
- package/dist/inline-walker.d.ts.map +1 -0
- package/dist/inline-walker.js +167 -0
- package/dist/inline-walker.js.map +1 -0
- package/dist/loaders/antora.d.ts +3 -0
- package/dist/loaders/antora.d.ts.map +1 -0
- package/dist/loaders/antora.js +254 -0
- package/dist/loaders/antora.js.map +1 -0
- package/dist/loaders/asciibinder.d.ts +3 -0
- package/dist/loaders/asciibinder.d.ts.map +1 -0
- package/dist/loaders/asciibinder.js +206 -0
- package/dist/loaders/asciibinder.js.map +1 -0
- package/dist/loaders/master-adoc.d.ts +3 -0
- package/dist/loaders/master-adoc.d.ts.map +1 -0
- package/dist/loaders/master-adoc.js +168 -0
- package/dist/loaders/master-adoc.js.map +1 -0
- package/dist/loaders/plain.d.ts +3 -0
- package/dist/loaders/plain.d.ts.map +1 -0
- package/dist/loaders/plain.js +61 -0
- package/dist/loaders/plain.js.map +1 -0
- package/dist/loaders/types.d.ts +88 -0
- package/dist/loaders/types.d.ts.map +1 -0
- package/dist/loaders/types.js +2 -0
- package/dist/loaders/types.js.map +1 -0
- package/dist/loaders/util.d.ts +15 -0
- package/dist/loaders/util.d.ts.map +1 -0
- package/dist/loaders/util.js +49 -0
- package/dist/loaders/util.js.map +1 -0
- package/dist/parse.d.ts +110 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +116 -0
- package/dist/parse.js.map +1 -0
- package/dist/tree.d.ts +2 -0
- package/dist/tree.d.ts.map +1 -0
- package/dist/tree.js +2 -0
- package/dist/tree.js.map +1 -0
- package/dist/walker.d.ts +8 -0
- package/dist/walker.d.ts.map +1 -0
- package/dist/walker.js +193 -0
- package/dist/walker.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antora corpus loader (component mode).
|
|
3
|
+
*
|
|
4
|
+
* Detects: presence of `antora.yml` at the source root. Reads the
|
|
5
|
+
* component manifest (name + version + nav file list + asciidoc
|
|
6
|
+
* attribute defaults), walks `modules/<name>/pages/**\/*.adoc`, and
|
|
7
|
+
* builds nav from the referenced `nav.adoc` files.
|
|
8
|
+
*
|
|
9
|
+
* **Scope of this MVP — what's covered:**
|
|
10
|
+
*
|
|
11
|
+
* - Single-component mode (one antora.yml).
|
|
12
|
+
* - All modules under `modules/<name>/pages/` — ROOT pages get no
|
|
13
|
+
* slug prefix; other modules' pages get a `<module-name>/` prefix.
|
|
14
|
+
* - nav.adoc parsing: depth-based list items (`*`, `**`, `***`), three
|
|
15
|
+
* shapes — `* xref:path[label]`, `** xref:path[]` (empty brackets →
|
|
16
|
+
* label from filename), `* Text` (no xref → group header).
|
|
17
|
+
* - Per-component attribute defaults from `antora.yml`'s
|
|
18
|
+
* `asciidoc.attributes:` block are surfaced on every
|
|
19
|
+
* `PageSource.attributes`.
|
|
20
|
+
* - Component name + version surfaced on `LoaderResult.component`.
|
|
21
|
+
*
|
|
22
|
+
* **Deliberately out of scope (deferred):**
|
|
23
|
+
*
|
|
24
|
+
* - Playbook mode (`antora-playbook.yml`) — pulls in git-resolved
|
|
25
|
+
* sources + multi-component aggregation. Component mode covers the
|
|
26
|
+
* common single-repo case; playbook mode is a follow-up.
|
|
27
|
+
* - Cross-module xref resolution (`xref:module:path[label]`). The
|
|
28
|
+
* engine emits the xref as text or `{% include %}` per its rules;
|
|
29
|
+
* format-astro's downstream link audit will surface the unresolved
|
|
30
|
+
* refs.
|
|
31
|
+
* - Version axis emission. The component's version is surfaced as
|
|
32
|
+
* data but not wired into format-astro's version-axis machinery
|
|
33
|
+
* (per plans/asciidoc-corpus-loaders.md open question — same
|
|
34
|
+
* deferral as asciibinder distros).
|
|
35
|
+
*
|
|
36
|
+
* Sample antora.yml shape:
|
|
37
|
+
*
|
|
38
|
+
* name: product
|
|
39
|
+
* title: My Product
|
|
40
|
+
* version: '2.0' # null / ~ also valid (unversioned)
|
|
41
|
+
* nav:
|
|
42
|
+
* - modules/ROOT/nav.adoc
|
|
43
|
+
* - modules/admin/nav.adoc
|
|
44
|
+
* asciidoc:
|
|
45
|
+
* attributes:
|
|
46
|
+
* page-component-name: product
|
|
47
|
+
*
|
|
48
|
+
* Sample nav.adoc:
|
|
49
|
+
*
|
|
50
|
+
* * xref:index.adoc[Introduction]
|
|
51
|
+
* * Getting Started
|
|
52
|
+
* ** xref:install.adoc[]
|
|
53
|
+
* ** xref:configure.adoc[Configuration]
|
|
54
|
+
*
|
|
55
|
+
* See plans/asciidoc-corpus-loaders.md "Antora" section.
|
|
56
|
+
*/
|
|
57
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
58
|
+
import { basename, join, relative } from "node:path";
|
|
59
|
+
import YAML from "yaml";
|
|
60
|
+
import { prettifyName } from "./util.js";
|
|
61
|
+
const ANTORA_YML = "antora.yml";
|
|
62
|
+
const ROOT_MODULE = "ROOT";
|
|
63
|
+
function walkPagesUnderModule(modulesDir, moduleName) {
|
|
64
|
+
const pagesDir = join(modulesDir, moduleName, "pages");
|
|
65
|
+
if (!existsSync(pagesDir))
|
|
66
|
+
return [];
|
|
67
|
+
const out = [];
|
|
68
|
+
function walk(dir) {
|
|
69
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
70
|
+
const full = join(dir, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
walk(full);
|
|
73
|
+
}
|
|
74
|
+
else if (entry.name.endsWith(".adoc")) {
|
|
75
|
+
const relFromPages = relative(pagesDir, full).replace(/\\/g, "/");
|
|
76
|
+
const pageSlug = relFromPages.replace(/\.adoc$/, "");
|
|
77
|
+
// ROOT pages get no module prefix; other modules get one.
|
|
78
|
+
const slug = moduleName === ROOT_MODULE ? pageSlug : `${moduleName}/${pageSlug}`;
|
|
79
|
+
out.push({ filePath: full, slug });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
walk(pagesDir);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function listModules(sourceDir) {
|
|
87
|
+
const modulesDir = join(sourceDir, "modules");
|
|
88
|
+
if (!existsSync(modulesDir))
|
|
89
|
+
return [];
|
|
90
|
+
try {
|
|
91
|
+
return readdirSync(modulesDir, { withFileTypes: true })
|
|
92
|
+
.filter((e) => e.isDirectory())
|
|
93
|
+
.map((e) => e.name);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse a nav.adoc file into a NavItem[] tree.
|
|
101
|
+
*
|
|
102
|
+
* Each line of the form `*+ <content>` is a nav item. The number of
|
|
103
|
+
* leading `*` characters is the depth (1-based). Content is either:
|
|
104
|
+
* - `xref:<path>.adoc[<label>]` → leaf; label may be empty (use filename)
|
|
105
|
+
* - `xref:<module>:<path>.adoc[<label>]` → leaf in another module
|
|
106
|
+
* - plain text → group header (no link)
|
|
107
|
+
*
|
|
108
|
+
* Algorithm: stack-of-parents per depth. Lines that aren't `*+ ...`
|
|
109
|
+
* are ignored (blank lines, `.Section` headings, prose).
|
|
110
|
+
*
|
|
111
|
+
* `moduleName` is the module the nav lives in — xrefs without an
|
|
112
|
+
* explicit module prefix resolve under this module's pages/.
|
|
113
|
+
* `hrefPrefix` prepends every leaf href.
|
|
114
|
+
*/
|
|
115
|
+
function parseNavAdoc(content, moduleName, hrefPrefix) {
|
|
116
|
+
const root = [];
|
|
117
|
+
// Stack[d] = the children-array we should push into for a depth-(d+1)
|
|
118
|
+
// item. stack[0] is `root` (depth 1 items go here).
|
|
119
|
+
const stack = [root];
|
|
120
|
+
const lineRe = /^(\*+)\s+(.+)$/;
|
|
121
|
+
for (const rawLine of content.split("\n")) {
|
|
122
|
+
const line = rawLine.replace(/\s+$/, "");
|
|
123
|
+
const m = line.match(lineRe);
|
|
124
|
+
if (!m)
|
|
125
|
+
continue;
|
|
126
|
+
const depth = m[1].length;
|
|
127
|
+
const text = m[2].trim();
|
|
128
|
+
const item = parseNavItem(text, moduleName, hrefPrefix);
|
|
129
|
+
// Find the parent array — if depth exceeds current stack length,
|
|
130
|
+
// we're going deeper than any actual parent; clamp to current top.
|
|
131
|
+
const parentIdx = Math.min(depth - 1, stack.length - 1);
|
|
132
|
+
const parent = stack[parentIdx];
|
|
133
|
+
parent.push(item);
|
|
134
|
+
// This item becomes the parent for depth+1 items. Allocate its
|
|
135
|
+
// children array up-front (so we can push later); if it stays
|
|
136
|
+
// empty, we strip it before returning.
|
|
137
|
+
if (!item.children)
|
|
138
|
+
item.children = [];
|
|
139
|
+
// Trim deeper-than-current entries; replace stack[depth] with our children.
|
|
140
|
+
stack.length = depth;
|
|
141
|
+
stack.push(item.children);
|
|
142
|
+
}
|
|
143
|
+
// Clean up: drop empty `children: []` arrays so leaves don't look
|
|
144
|
+
// like empty groups to downstream consumers.
|
|
145
|
+
function strip(items) {
|
|
146
|
+
for (const it of items) {
|
|
147
|
+
if (it.children && it.children.length === 0) {
|
|
148
|
+
delete it.children;
|
|
149
|
+
}
|
|
150
|
+
else if (it.children) {
|
|
151
|
+
strip(it.children);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
strip(root);
|
|
156
|
+
return root;
|
|
157
|
+
}
|
|
158
|
+
function parseNavItem(text, moduleName, hrefPrefix) {
|
|
159
|
+
// xref:<path>[<label>] or xref:<module>:<path>[<label>]
|
|
160
|
+
const xrefRe = /^xref:(?:([^:\s]+):)?([^\[\s]+)\[([^\]]*)\]$/;
|
|
161
|
+
const m = text.match(xrefRe);
|
|
162
|
+
if (!m) {
|
|
163
|
+
// Plain text — group header.
|
|
164
|
+
return { label: text };
|
|
165
|
+
}
|
|
166
|
+
const xrefModule = m[1] || moduleName;
|
|
167
|
+
const xrefPath = m[2].replace(/\.adoc$/, "");
|
|
168
|
+
const label = m[3].trim() || prettifyName(basename(xrefPath));
|
|
169
|
+
const slug = xrefModule === ROOT_MODULE ? xrefPath : `${xrefModule}/${xrefPath}`;
|
|
170
|
+
return { label, href: `${hrefPrefix}/${slug}` };
|
|
171
|
+
}
|
|
172
|
+
export const antoraLoader = {
|
|
173
|
+
name: "antora",
|
|
174
|
+
detect(sourceDir) {
|
|
175
|
+
if (!existsSync(sourceDir))
|
|
176
|
+
return false;
|
|
177
|
+
try {
|
|
178
|
+
const st = statSync(sourceDir);
|
|
179
|
+
if (!st.isDirectory())
|
|
180
|
+
return false;
|
|
181
|
+
return existsSync(join(sourceDir, ANTORA_YML));
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
async load(sourceDir, opts) {
|
|
188
|
+
const ymlPath = join(sourceDir, ANTORA_YML);
|
|
189
|
+
if (!existsSync(ymlPath)) {
|
|
190
|
+
throw new Error(`antora loader: ${ANTORA_YML} not found in ${sourceDir}`);
|
|
191
|
+
}
|
|
192
|
+
let parsed;
|
|
193
|
+
try {
|
|
194
|
+
parsed = YAML.parse(readFileSync(ymlPath, "utf-8"));
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
198
|
+
throw new Error(`antora loader: cannot parse ${ANTORA_YML}: ${msg}`);
|
|
199
|
+
}
|
|
200
|
+
if (!parsed || typeof parsed !== "object" || !parsed.name) {
|
|
201
|
+
throw new Error(`antora loader: ${ANTORA_YML} must declare at least a 'name' field`);
|
|
202
|
+
}
|
|
203
|
+
const hrefPrefix = opts.hrefPrefix ?? "";
|
|
204
|
+
const componentAttrs = parsed.asciidoc?.attributes ?? {};
|
|
205
|
+
// Walk every module's pages/ to assemble PageSources. We don't
|
|
206
|
+
// restrict to modules listed in nav: — a corpus can have pages
|
|
207
|
+
// not referenced by nav (orphans), and the audit will surface
|
|
208
|
+
// them; the loader's job is to enumerate them all.
|
|
209
|
+
const modulesDir = join(sourceDir, "modules");
|
|
210
|
+
const moduleNames = listModules(sourceDir);
|
|
211
|
+
const pages = [];
|
|
212
|
+
for (const moduleName of moduleNames) {
|
|
213
|
+
for (const { filePath, slug } of walkPagesUnderModule(modulesDir, moduleName)) {
|
|
214
|
+
const page = { filePath, slug };
|
|
215
|
+
if (Object.keys(componentAttrs).length > 0) {
|
|
216
|
+
page.attributes = { ...componentAttrs };
|
|
217
|
+
}
|
|
218
|
+
pages.push(page);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Build nav by parsing each referenced nav.adoc file. Concatenate
|
|
222
|
+
// multiple nav files at the top level — that's Antora's
|
|
223
|
+
// convention (each nav.adoc is a "section" of the overall nav).
|
|
224
|
+
const nav = [];
|
|
225
|
+
for (const navPath of parsed.nav ?? []) {
|
|
226
|
+
const navFullPath = join(sourceDir, navPath);
|
|
227
|
+
if (!existsSync(navFullPath)) {
|
|
228
|
+
// eslint-disable-next-line no-console
|
|
229
|
+
console.warn(`antora loader: nav file not found, skipping: ${navPath}`);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
// Module name from the nav file's path — `modules/<name>/nav.adoc`.
|
|
233
|
+
const navRelative = relative(modulesDir, navFullPath).replace(/\\/g, "/");
|
|
234
|
+
const moduleName = navRelative.split("/")[0] ?? ROOT_MODULE;
|
|
235
|
+
try {
|
|
236
|
+
const content = readFileSync(navFullPath, "utf-8");
|
|
237
|
+
nav.push(...parseNavAdoc(content, moduleName, hrefPrefix));
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
console.warn(`antora loader: cannot read ${navPath}: ${msg}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const result = { pages, nav };
|
|
246
|
+
result.component = {
|
|
247
|
+
name: parsed.name,
|
|
248
|
+
...(parsed.title ? { title: parsed.title } : {}),
|
|
249
|
+
...(parsed.version ? { version: String(parsed.version) } : {}),
|
|
250
|
+
};
|
|
251
|
+
return result;
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
//# sourceMappingURL=antora.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"antora.js","sourceRoot":"","sources":["../../src/loaders/antora.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asciibinder.d.ts","sourceRoot":"","sources":["../../src/loaders/asciibinder.ts"],"names":[],"mappings":"AA8CA,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,YAAY,CAAC;AA+GpB,eAAO,MAAM,iBAAiB,EAAE,YAwF/B,CAAC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AsciiBinder corpus loader (OpenShift docs convention).
|
|
3
|
+
*
|
|
4
|
+
* Detects: presence of `_topic_maps/_topic_map.yml` at the source
|
|
5
|
+
* root. Optionally consumes a sibling `_topic_maps/_distro_map.yml`
|
|
6
|
+
* to surface the corpus's distro set on the LoaderResult.
|
|
7
|
+
*
|
|
8
|
+
* Topology shape (YAML):
|
|
9
|
+
*
|
|
10
|
+
* - Name: Welcome
|
|
11
|
+
* Dir: welcome
|
|
12
|
+
* Topics:
|
|
13
|
+
* - Name: About OpenShift
|
|
14
|
+
* File: index
|
|
15
|
+
* - Name: Architecture
|
|
16
|
+
* File: architecture
|
|
17
|
+
* Distros: openshift-enterprise,openshift-online
|
|
18
|
+
* - Name: Getting Started
|
|
19
|
+
* Dir: getting-started
|
|
20
|
+
* Topics:
|
|
21
|
+
* - ...
|
|
22
|
+
*
|
|
23
|
+
* Translation:
|
|
24
|
+
* - Each entry is either a **group** (has `Topics`, no `File`) or a
|
|
25
|
+
* **leaf** (has `File`, no `Topics`). The plan allows both fields
|
|
26
|
+
* in theory, but the real-world OpenShift corpora always pick one.
|
|
27
|
+
* - `Dir` accumulates down the topic tree to form the page slug:
|
|
28
|
+
* a leaf at `Topics[0].Topics[1]` with the chain
|
|
29
|
+
* `Dir: welcome` → `Dir: gettingstarted` → `File: quickstart`
|
|
30
|
+
* becomes slug `welcome/gettingstarted/quickstart`.
|
|
31
|
+
* - `Distros` on a leaf is parsed (comma-separated) and surfaced as
|
|
32
|
+
* `PageSource.attributes.distros: string[]`. The aggregate union is
|
|
33
|
+
* also stashed on `LoaderResult.distros` for future distro-axis
|
|
34
|
+
* emission (Phase 9b doesn't yet wire that through format-astro;
|
|
35
|
+
* see plans/asciidoc-corpus-loaders.md open question #1).
|
|
36
|
+
*
|
|
37
|
+
* Missing files (topic references a file that's not on disk) get
|
|
38
|
+
* warn-and-skip semantics, matching the master-adoc loader. The
|
|
39
|
+
* audit catches the wider issue.
|
|
40
|
+
*
|
|
41
|
+
* See plans/asciidoc-corpus-loaders.md "AsciiBinder" section.
|
|
42
|
+
*/
|
|
43
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
44
|
+
import { join } from "node:path";
|
|
45
|
+
import YAML from "yaml";
|
|
46
|
+
const TOPIC_MAP_PATH = "_topic_maps/_topic_map.yml";
|
|
47
|
+
const DISTRO_MAP_PATH = "_topic_maps/_distro_map.yml";
|
|
48
|
+
/**
|
|
49
|
+
* Walk a TopicEntry[] subtree. Pushes leaves into `pages` + matching
|
|
50
|
+
* NavItem links into `nav`; pushes group entries (entries with
|
|
51
|
+
* `Topics`) as nested NavItem groups whose children come from a
|
|
52
|
+
* recursive walk.
|
|
53
|
+
*
|
|
54
|
+
* `parentDirSegments` is the directory chain accumulated from
|
|
55
|
+
* ancestor `Dir:` fields; concatenated with the entry's own `Dir:`
|
|
56
|
+
* (if any) and the leaf's `File:` to form the slug.
|
|
57
|
+
*
|
|
58
|
+
* `distroAcc` is the set of distros encountered anywhere in the
|
|
59
|
+
* tree — surfaced on LoaderResult.distros.
|
|
60
|
+
*/
|
|
61
|
+
function walkTopics(entries, parentDirSegments, pages, hrefPrefix, sourceRoot, distroAcc) {
|
|
62
|
+
const nav = [];
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
const label = entry.Name ?? "Untitled";
|
|
65
|
+
const dirSegments = entry.Dir
|
|
66
|
+
? [...parentDirSegments, entry.Dir]
|
|
67
|
+
: parentDirSegments;
|
|
68
|
+
if (entry.File !== undefined) {
|
|
69
|
+
const slug = [...dirSegments, entry.File].filter(Boolean).join("/");
|
|
70
|
+
const filePath = join(sourceRoot, `${slug}.adoc`);
|
|
71
|
+
if (!existsSync(filePath)) {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.warn(`asciibinder loader: topic_map references missing file, skipping: ${slug}.adoc`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const distros = parseDistros(entry.Distros);
|
|
77
|
+
for (const d of distros)
|
|
78
|
+
distroAcc.add(d);
|
|
79
|
+
const page = { filePath, slug };
|
|
80
|
+
if (distros.length > 0) {
|
|
81
|
+
page.attributes = { distros };
|
|
82
|
+
}
|
|
83
|
+
pages.push(page);
|
|
84
|
+
nav.push({ label, href: `${hrefPrefix}/${slug}` });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (entry.Topics && entry.Topics.length > 0) {
|
|
88
|
+
const children = walkTopics(entry.Topics, dirSegments, pages, hrefPrefix, sourceRoot, distroAcc);
|
|
89
|
+
nav.push({ label, children });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Entry has neither File nor Topics — malformed but tolerated.
|
|
93
|
+
// Emit a stub nav entry so the user notices the gap rather than
|
|
94
|
+
// a silent skip.
|
|
95
|
+
nav.push({ label });
|
|
96
|
+
}
|
|
97
|
+
return nav;
|
|
98
|
+
}
|
|
99
|
+
function parseDistros(raw) {
|
|
100
|
+
if (!raw || typeof raw !== "string")
|
|
101
|
+
return [];
|
|
102
|
+
return raw
|
|
103
|
+
.split(",")
|
|
104
|
+
.map((s) => s.trim())
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Read the optional _distro_map.yml to learn the corpus's full
|
|
109
|
+
* distro set. Treated as informational — we surface the union of
|
|
110
|
+
* what the topic map declares AND what the distro map declares,
|
|
111
|
+
* so consumers see every distro name the source author cared about.
|
|
112
|
+
*/
|
|
113
|
+
function readDistroMap(sourceDir) {
|
|
114
|
+
const path = join(sourceDir, DISTRO_MAP_PATH);
|
|
115
|
+
if (!existsSync(path))
|
|
116
|
+
return [];
|
|
117
|
+
try {
|
|
118
|
+
const yml = YAML.parse(readFileSync(path, "utf-8"));
|
|
119
|
+
if (yml && typeof yml === "object" && !Array.isArray(yml)) {
|
|
120
|
+
return Object.keys(yml);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Malformed map — ignore, fall back to topic-map-derived distros only.
|
|
125
|
+
}
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
export const asciibinderLoader = {
|
|
129
|
+
name: "asciibinder",
|
|
130
|
+
detect(sourceDir) {
|
|
131
|
+
if (!existsSync(sourceDir))
|
|
132
|
+
return false;
|
|
133
|
+
try {
|
|
134
|
+
const st = statSync(sourceDir);
|
|
135
|
+
if (!st.isDirectory())
|
|
136
|
+
return false;
|
|
137
|
+
return existsSync(join(sourceDir, TOPIC_MAP_PATH));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
async load(sourceDir, opts) {
|
|
144
|
+
const topicMapPath = join(sourceDir, TOPIC_MAP_PATH);
|
|
145
|
+
if (!existsSync(topicMapPath)) {
|
|
146
|
+
throw new Error(`asciibinder loader: ${TOPIC_MAP_PATH} not found in ${sourceDir}`);
|
|
147
|
+
}
|
|
148
|
+
// AsciiBinder's `_topic_map.yml` is a stream of YAML documents
|
|
149
|
+
// separated by `---` (each document is one top-level topic
|
|
150
|
+
// group). The file's own header documents this:
|
|
151
|
+
// "--- <= Record delimiter"
|
|
152
|
+
// Real-world corpora (OpenShift docs etc.) rely on it. We
|
|
153
|
+
// accept either shape:
|
|
154
|
+
// (a) the conventional multi-doc stream — each `---` group
|
|
155
|
+
// contributes one entry to the topic list, OR
|
|
156
|
+
// (b) a single doc with a top-level list — what our earlier
|
|
157
|
+
// tests / synthesized fixtures used.
|
|
158
|
+
let parsed;
|
|
159
|
+
try {
|
|
160
|
+
const raw = readFileSync(topicMapPath, "utf-8");
|
|
161
|
+
const docs = YAML.parseAllDocuments(raw);
|
|
162
|
+
// parseAllDocuments doesn't throw on bad YAML — it attaches
|
|
163
|
+
// errors to each Document. Surface the first one with a
|
|
164
|
+
// clear message.
|
|
165
|
+
for (const doc of docs) {
|
|
166
|
+
if (doc.errors && doc.errors.length > 0) {
|
|
167
|
+
throw new Error(doc.errors[0].message);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Skip empty documents (a trailing `---` produces a null one)
|
|
171
|
+
// and unwrap each Document to its JS value.
|
|
172
|
+
const values = docs
|
|
173
|
+
.map((d) => d.toJS())
|
|
174
|
+
.filter((v) => v !== null && v !== undefined);
|
|
175
|
+
if (values.length === 1 && Array.isArray(values[0])) {
|
|
176
|
+
// Shape (b) — single doc that already wraps the list.
|
|
177
|
+
parsed = values[0];
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// Shape (a) — each doc is a topic-group object; flatten
|
|
181
|
+
// into a list. Defensive: if any single doc is itself a
|
|
182
|
+
// list (mixed shape), spread it.
|
|
183
|
+
parsed = values.flatMap((v) => (Array.isArray(v) ? v : [v]));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
188
|
+
throw new Error(`asciibinder loader: cannot parse ${TOPIC_MAP_PATH}: ${msg}`);
|
|
189
|
+
}
|
|
190
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
191
|
+
throw new Error(`asciibinder loader: ${TOPIC_MAP_PATH} must contain at least one topic-group entry ` +
|
|
192
|
+
`(either as a single top-level list, or as a stream of YAML documents separated by \`---\`)`);
|
|
193
|
+
}
|
|
194
|
+
const hrefPrefix = opts.hrefPrefix ?? "";
|
|
195
|
+
const pages = [];
|
|
196
|
+
const distroAcc = new Set();
|
|
197
|
+
const nav = walkTopics(parsed, [], pages, hrefPrefix, sourceDir, distroAcc);
|
|
198
|
+
for (const d of readDistroMap(sourceDir))
|
|
199
|
+
distroAcc.add(d);
|
|
200
|
+
const result = { pages, nav };
|
|
201
|
+
if (distroAcc.size > 0)
|
|
202
|
+
result.distros = [...distroAcc].sort();
|
|
203
|
+
return result;
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=asciibinder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asciibinder.js","sourceRoot":"","sources":["../../src/loaders/asciibinder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"master-adoc.d.ts","sourceRoot":"","sources":["../../src/loaders/master-adoc.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,YAAY,CAAC;AAmDpB,eAAO,MAAM,gBAAgB,EAAE,YAiG9B,CAAC"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AAP master.adoc corpus loader.
|
|
3
|
+
*
|
|
4
|
+
* AAP (Ansible Automation Platform) and adjacent Red Hat docs use a
|
|
5
|
+
* convention where each "title" is a directory containing a single
|
|
6
|
+
* `master.adoc` file. That master.adoc is pure structure — typically
|
|
7
|
+
* an `:attribute:` block plus a sequence of `include::` directives
|
|
8
|
+
* pointing at module `.adoc` files (usually shared across titles
|
|
9
|
+
* under a sibling `modules/` tree).
|
|
10
|
+
*
|
|
11
|
+
* Phase 9a — first non-plain corpus loader, simplest topology
|
|
12
|
+
* (no central topo file like AsciiBinder's `_topic_map.yml`).
|
|
13
|
+
*
|
|
14
|
+
* Behaviour:
|
|
15
|
+
* - Walk the source dir for `master.adoc` files (recursive).
|
|
16
|
+
* - For each master, parse its `include::` directives in order;
|
|
17
|
+
* each include becomes a `PageSource` (filePath resolved relative
|
|
18
|
+
* to the master, slug derived from the resolved path so siblings
|
|
19
|
+
* sharing a module map to the same slug — last-write-wins is
|
|
20
|
+
* fine; the corpus is conceptually merged).
|
|
21
|
+
* - Build a per-title nav group: the title comes from the master's
|
|
22
|
+
* parent directory name (prettified); children are the includes
|
|
23
|
+
* in source order, labelled by their filename (prettified).
|
|
24
|
+
* - Master.adoc files themselves do NOT become pages — they're
|
|
25
|
+
* pure structure, not user-facing content.
|
|
26
|
+
*
|
|
27
|
+
* Lineage: md2md's `convert-all-titles.sh` is the shell prototype
|
|
28
|
+
* of this discovery pattern.
|
|
29
|
+
*
|
|
30
|
+
* See plans/asciidoc-corpus-loaders.md "AAP master.adoc" section.
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
33
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
34
|
+
import { prettifyName } from "./util.js";
|
|
35
|
+
function walkMasterAdocs(dir, out = []) {
|
|
36
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
37
|
+
const full = join(dir, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
walkMasterAdocs(full, out);
|
|
40
|
+
}
|
|
41
|
+
else if (entry.name === "master.adoc") {
|
|
42
|
+
out.push(full);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract include:: directives from an AsciiDoc source.
|
|
49
|
+
*
|
|
50
|
+
* Matches the shape `include::path[attr=value,...]`. Multi-line
|
|
51
|
+
* include blocks are not supported (the engine handles those at
|
|
52
|
+
* convert time; the loader only needs to enumerate top-level
|
|
53
|
+
* module references). Returns `null` for the `leveloffset` attribute
|
|
54
|
+
* when absent.
|
|
55
|
+
*/
|
|
56
|
+
function extractIncludes(adoc) {
|
|
57
|
+
const out = [];
|
|
58
|
+
const re = /^include::([^\[\s]+)\[([^\]]*)\]\s*$/gm;
|
|
59
|
+
for (const m of adoc.matchAll(re)) {
|
|
60
|
+
const path = m[1];
|
|
61
|
+
const attrs = m[2];
|
|
62
|
+
let leveloffset;
|
|
63
|
+
const lvl = /leveloffset\s*=\s*([+-]?\d+)/.exec(attrs);
|
|
64
|
+
if (lvl)
|
|
65
|
+
leveloffset = Number(lvl[1]);
|
|
66
|
+
out.push({ path, leveloffset });
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Resolve an include path against the directory containing the
|
|
72
|
+
* including file. AsciiDoc semantics: paths are relative to the
|
|
73
|
+
* source file, NOT to the document root. `..` and `.` segments are
|
|
74
|
+
* normalised by `node:path/resolve`.
|
|
75
|
+
*/
|
|
76
|
+
function resolveIncludePath(includingFile, includePath) {
|
|
77
|
+
return resolve(dirname(includingFile), includePath);
|
|
78
|
+
}
|
|
79
|
+
export const masterAdocLoader = {
|
|
80
|
+
name: "master-adoc",
|
|
81
|
+
detect(sourceDir) {
|
|
82
|
+
if (!existsSync(sourceDir))
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
const st = statSync(sourceDir);
|
|
86
|
+
if (!st.isDirectory())
|
|
87
|
+
return false;
|
|
88
|
+
// Walk subtree for any master.adoc — bail as soon as we find one.
|
|
89
|
+
function probe(dir) {
|
|
90
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
if (probe(join(dir, entry.name)))
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
else if (entry.name === "master.adoc") {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return probe(sourceDir);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async load(sourceDir, opts) {
|
|
108
|
+
if (!existsSync(sourceDir)) {
|
|
109
|
+
throw new Error(`Source directory not found: ${sourceDir}`);
|
|
110
|
+
}
|
|
111
|
+
const hrefPrefix = opts.hrefPrefix ?? "";
|
|
112
|
+
const masters = walkMasterAdocs(sourceDir).sort();
|
|
113
|
+
if (masters.length === 0) {
|
|
114
|
+
throw new Error(`master-adoc loader: no master.adoc files found under ${sourceDir}`);
|
|
115
|
+
}
|
|
116
|
+
// Track unique PageSources by resolved filePath so a module
|
|
117
|
+
// included from multiple titles produces ONE page (last-write-wins
|
|
118
|
+
// for the leveloffset / attributes hint — typically all titles use
|
|
119
|
+
// the same shape anyway).
|
|
120
|
+
const pageByPath = new Map();
|
|
121
|
+
const navGroups = [];
|
|
122
|
+
for (const masterPath of masters) {
|
|
123
|
+
const titleDir = dirname(masterPath);
|
|
124
|
+
const titleLabel = prettifyName(basename(titleDir));
|
|
125
|
+
let adoc;
|
|
126
|
+
try {
|
|
127
|
+
adoc = readFileSync(masterPath, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
131
|
+
throw new Error(`master-adoc loader: cannot read ${masterPath}: ${msg}`);
|
|
132
|
+
}
|
|
133
|
+
const includes = extractIncludes(adoc);
|
|
134
|
+
const navChildren = [];
|
|
135
|
+
for (const inc of includes) {
|
|
136
|
+
const resolvedPath = resolveIncludePath(masterPath, inc.path);
|
|
137
|
+
if (!existsSync(resolvedPath)) {
|
|
138
|
+
// Skip dangling includes; surface to the user via a warning
|
|
139
|
+
// rather than fail the whole load. The engine ALSO logs
|
|
140
|
+
// "Include file not found" so the user gets a second signal.
|
|
141
|
+
// eslint-disable-next-line no-console
|
|
142
|
+
console.warn(`master-adoc loader: include not found, skipping: ${inc.path} (from ${masterPath})`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
// Slug = resolved path relative to source root, without .adoc.
|
|
146
|
+
// Forward slashes for URL safety on Windows.
|
|
147
|
+
const slug = relative(sourceDir, resolvedPath)
|
|
148
|
+
.replace(/\\/g, "/")
|
|
149
|
+
.replace(/\.adoc$/, "");
|
|
150
|
+
if (!pageByPath.has(resolvedPath)) {
|
|
151
|
+
pageByPath.set(resolvedPath, {
|
|
152
|
+
filePath: resolvedPath,
|
|
153
|
+
slug,
|
|
154
|
+
...(inc.leveloffset !== undefined ? { leveloffset: inc.leveloffset } : {}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
navChildren.push({
|
|
158
|
+
label: prettifyName(basename(slug)),
|
|
159
|
+
href: `${hrefPrefix}/${slug}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
navGroups.push({ label: titleLabel, children: navChildren });
|
|
163
|
+
}
|
|
164
|
+
const pages = [...pageByPath.values()];
|
|
165
|
+
return { pages, nav: navGroups };
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=master-adoc.js.map
|