create-asciidoc-multi-book-workspace 0.1.0 → 0.1.6

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +30 -2
  3. package/dist/build-template-preview.d.ts +17 -0
  4. package/dist/build-template-preview.js +88 -0
  5. package/dist/create.d.ts +2 -0
  6. package/dist/create.js +2 -2
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +1 -0
  9. package/dist/init-workspace.d.ts +9 -0
  10. package/dist/init-workspace.js +1 -1
  11. package/dist/runtime/adoc-books.mjs +52 -12
  12. package/dist/template-manifest.d.ts +3 -0
  13. package/package.json +17 -2
  14. package/templates/default-workspace/README.md +1 -1
  15. package/templates/default-workspace/books/00-book-anatomy/backmatter/appendix-a.adoc +16 -5
  16. package/templates/default-workspace/books/00-book-anatomy/backmatter/bibliography.adoc +2 -0
  17. package/templates/default-workspace/books/00-book-anatomy/backmatter/glossary.adoc +3 -1
  18. package/templates/default-workspace/books/00-book-anatomy/backmatter/index.adoc +31 -3
  19. package/templates/default-workspace/books/00-book-anatomy/book.adoc +2 -0
  20. package/templates/default-workspace/books/00-book-anatomy/frontmatter/abstract.adoc +2 -2
  21. package/templates/default-workspace/books/00-book-anatomy/frontmatter/preface.adoc +2 -0
  22. package/templates/default-workspace/books/00-book-anatomy/parts/01-front-structure/01-frontmatter-map.adoc +2 -0
  23. package/templates/default-workspace/books/00-book-anatomy/parts/02-body-structure/01-body-map.adoc +7 -3
  24. package/templates/default-workspace/books/00-book-anatomy/parts/02-body-structure/02-inline-structure.adoc +32 -0
  25. package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/appendix-a.adoc +62 -0
  26. package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/bibliography.adoc +7 -0
  27. package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/glossary.adoc +14 -0
  28. package/templates/default-workspace/books/07-structured-writing-conventions/backmatter/index.adoc +37 -0
  29. package/templates/default-workspace/books/07-structured-writing-conventions/book.adoc +71 -0
  30. package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/abstract.adoc +3 -0
  31. package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/acknowledgments.adoc +3 -0
  32. package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/colophon.adoc +5 -0
  33. package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/dedication.adoc +3 -0
  34. package/templates/default-workspace/books/07-structured-writing-conventions/frontmatter/preface.adoc +7 -0
  35. package/templates/default-workspace/books/07-structured-writing-conventions/parts/100-source-surface/010-source-and-projection.adoc +44 -0
  36. package/templates/default-workspace/books/07-structured-writing-conventions/parts/100-source-surface/020-default-semantics.adoc +26 -0
  37. package/templates/default-workspace/books/07-structured-writing-conventions/parts/100-source-surface/030-source-order-notation.adoc +23 -0
  38. package/templates/default-workspace/books/07-structured-writing-conventions/parts/100-source-surface/_partintro.adoc +1 -0
  39. package/templates/default-workspace/books/07-structured-writing-conventions/parts/200-identity-and-relation/010-role-identity.adoc +37 -0
  40. package/templates/default-workspace/books/07-structured-writing-conventions/parts/200-identity-and-relation/020-relation-predicate.adoc +22 -0
  41. package/templates/default-workspace/books/07-structured-writing-conventions/parts/200-identity-and-relation/_partintro.adoc +1 -0
  42. package/templates/default-workspace/books/07-structured-writing-conventions/parts/300-fields-and-lookup/010-surface-fields.adoc +18 -0
  43. package/templates/default-workspace/books/07-structured-writing-conventions/parts/300-fields-and-lookup/020-index-and-glossary.adoc +14 -0
  44. package/templates/default-workspace/books/07-structured-writing-conventions/parts/300-fields-and-lookup/_partintro.adoc +1 -0
  45. package/templates/default-workspace/catalog.adoc +6 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,78 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6 - 2026-06-09
4
+
5
+ ### Added
6
+
7
+ - Accept typed explicit anchors such as `[#id.role, key=value]` when validating user-authored xrefs.
8
+ - Add optional `adoc-books.config.mjs` support so generated workspaces can customize the root redirect and book home-link copy.
9
+ - Add a structured writing conventions reference document and expand the generated sample's appendix with cross-file xref path guidance.
10
+
11
+ ### Changed
12
+
13
+ - Keep generated workspaces catalog-oriented by default while allowing user-owned navigation semantics through configuration.
14
+
15
+ ## 0.1.5 - 2026-06-07
16
+
17
+ ### Added
18
+
19
+ - Add a maintainer guide for Asciidoctor book part numbering, including the boundary between `sectnums`, `partnums`, and `part-signifier`.
20
+ - Add runtime and sample contract coverage for the structured writing conventions book's generated part labels.
21
+
22
+ ### Changed
23
+
24
+ - Enable native `Part I` style part numbering in `07-structured-writing-conventions`.
25
+ - Expand the structured writing conventions sample text with additional reader-facing context.
26
+
27
+ ## 0.1.4 - 2026-06-06
28
+
29
+ ### Added
30
+
31
+ - Add `07-structured-writing-conventions`, a default workspace sample book for structured writing conventions.
32
+ - Document how headings, stable IDs, role tokens, xrefs, rel predicates, and named attributes form a readable and projection-friendly source surface.
33
+ - Add sample contract coverage for the structured writing conventions book.
34
+
35
+ ### Changed
36
+
37
+ - Update the default workspace catalog and README to include structured writing conventions without tying README wording to a fixed sample count.
38
+
39
+ ## 0.1.3 - 2026-06-06
40
+
41
+ ### Added
42
+
43
+ - Expand the book anatomy sample with part directory guidance, chapter numbering notes, and inline structure markers.
44
+ - Add sample contract checks for semantic inline roles, path roles, explicit anchors, xrefs, and index term macro usage.
45
+ - Guard the npm package boundary against generated HTML output entering the published template.
46
+
47
+ ### Changed
48
+
49
+ - Clarify that the anatomy sample shows structure positions and combinations, not a required checklist for every book.
50
+ - Use explicit `indexterm2` and `indexterm` macro syntax in the anatomy sample.
51
+
52
+ ## 0.1.2 - 2026-06-06
53
+
54
+ ### Added
55
+
56
+ - Export the workspace initializer from the package root for host CLI integration.
57
+ - Publish TypeScript declaration files for the public initializer API.
58
+
59
+ ### Changed
60
+
61
+ - Return the resolved target directory and generated package name from `initWorkspace`.
62
+ - Document the library entrypoint while keeping command parsing and user-facing output owned by host CLIs.
63
+
64
+ ## 0.1.1 - 2026-06-06
65
+
66
+ ### Added
67
+
68
+ - Add maintainer preview commands for rebuilding the default sample workspace locally.
69
+ - Keep routine template previews fast by using the maintainer AsciiDoc toolchain, while `preview:fresh` still verifies the generated workspace's own dependency installation.
70
+
71
+ ### Changed
72
+
73
+ - Use `build/template-preview/workspace/build/html/index.html` as the single preview HTML output path.
74
+ - Keep preview source and tests out of the npm package boundary while publishing the compiled script referenced by maintainer commands.
75
+
3
76
  ## 0.1.0 - 2026-06-04
4
77
 
5
78
  ### Added
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  `create-asciidoc-multi-book-workspace` is an npm initializer for a complete AsciiDoc multi-book workspace.
4
4
 
5
- It creates a user-owned workspace with seven sample books, a catalog, shared assets, explicit AsciiDoc dependencies, and one local build script at `tools/adoc-books.mjs`.
5
+ It creates a user-owned workspace with sample books, a catalog, shared assets, explicit AsciiDoc dependencies, and one local build script at `tools/adoc-books.mjs`.
6
6
 
7
7
  ## Usage
8
8
 
@@ -19,6 +19,23 @@ npm users can run:
19
19
  npm create asciidoc-multi-book-workspace@latest my-books
20
20
  ```
21
21
 
22
+ ```bash
23
+ npx create-asciidoc-multi-book-workspace@latest my-books
24
+ ```
25
+
26
+ ## Library API
27
+
28
+ Host CLIs can create the same workspace through the package entrypoint:
29
+
30
+ ```ts
31
+ import { initWorkspace } from "create-asciidoc-multi-book-workspace";
32
+
33
+ const result = await initWorkspace({ targetDir: "my-books", force: false });
34
+ console.log(result.targetDir);
35
+ ```
36
+
37
+ The host CLI owns command parsing and user-facing output. This package owns the workspace files it creates.
38
+
22
39
  The generated workspace contains:
23
40
 
24
41
  ```text
@@ -34,6 +51,7 @@ books/03-technical-book-workflow
34
51
  books/04-reference-manual
35
52
  books/05-upper-volume
36
53
  books/06-lower-volume
54
+ books/07-structured-writing-conventions
37
55
  shared/attributes.adoc
38
56
  shared/images/workspace-map.svg
39
57
  ```
@@ -68,13 +86,15 @@ DESIGN.adoc # maintainer design specification
68
86
  .github/ # maintainer CI
69
87
  ```
70
88
 
71
- The seven sample books live only under `templates/default-workspace/books/`.
89
+ The sample books live only under `templates/default-workspace/books/`.
72
90
 
73
91
  ## Maintainer Commands
74
92
 
75
93
  ```bash
76
94
  pnpm install
77
95
  pnpm run build
96
+ pnpm run preview:build
97
+ pnpm run preview:fresh
78
98
  pnpm run test
79
99
  pnpm run check
80
100
  pnpm pack --dry-run
@@ -82,6 +102,14 @@ pnpm pack --dry-run
82
102
 
83
103
  `pnpm run check` builds TypeScript, runs maintainer tests, and checks the npm package boundary.
84
104
 
105
+ `pnpm run preview:build` updates a generated sample workspace under `build/template-preview/workspace`,
106
+ builds the sample books with the maintainer repository's installed AsciiDoc toolchain, and writes HTML to
107
+ `build/template-preview/workspace/build/html/index.html`.
108
+
109
+ `pnpm run preview:fresh` recompiles the maintainer runtime and recreates the preview workspace from scratch.
110
+ It installs the generated workspace's own dependencies, so use it after changing `src/`, dependency declarations,
111
+ or the initializer contract. Use `pnpm run preview:build` for normal template edits under `templates/default-workspace/`.
112
+
85
113
  ## Releases
86
114
 
87
115
  Release Please manages release pull requests and `CHANGELOG.md`.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ export interface RunCommandOptions {
3
+ cwd: string;
4
+ }
5
+ export type RunCommand = (command: string, args: string[], options: RunCommandOptions) => Promise<void>;
6
+ export interface BuildTemplatePreviewOptions {
7
+ previewRoot?: string;
8
+ runCommand?: RunCommand;
9
+ fresh?: boolean;
10
+ install?: boolean;
11
+ }
12
+ export interface BuildTemplatePreviewResult {
13
+ previewRoot: string;
14
+ workspaceDir: string;
15
+ htmlDir: string;
16
+ }
17
+ export declare function buildTemplatePreview(options?: BuildTemplatePreviewOptions): Promise<BuildTemplatePreviewResult>;
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { spawn } from "node:child_process";
6
+ import { initWorkspace } from "./init-workspace.js";
7
+ const sourceDir = path.dirname(fileURLToPath(import.meta.url));
8
+ const packageRoot = path.resolve(sourceDir, "..");
9
+ const defaultPreviewRoot = path.join(packageRoot, "build", "template-preview");
10
+ const templateWorkspacePaths = ["README.md", "catalog.adoc", "package.json", ".gitignore", "books", "shared", "tools"];
11
+ async function exists(filePath) {
12
+ try {
13
+ await stat(filePath);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ async function defaultRunCommand(command, args, options) {
21
+ await new Promise((resolve, reject) => {
22
+ const child = spawn(command, args, {
23
+ cwd: options.cwd,
24
+ shell: process.platform === "win32",
25
+ stdio: "inherit"
26
+ });
27
+ child.on("error", reject);
28
+ child.on("exit", (code) => {
29
+ if (code === 0) {
30
+ resolve();
31
+ return;
32
+ }
33
+ reject(new Error(`${command} ${args.join(" ")} exited with code ${code ?? "unknown"}`));
34
+ });
35
+ });
36
+ }
37
+ export async function buildTemplatePreview(options = {}) {
38
+ const previewRoot = path.resolve(options.previewRoot ?? defaultPreviewRoot);
39
+ const workspaceDir = path.join(previewRoot, "workspace");
40
+ const htmlDir = path.join(workspaceDir, "build", "html");
41
+ const legacyHtmlMirrorDir = path.join(previewRoot, "html");
42
+ const installStamp = path.join(previewRoot, ".installed-package.json");
43
+ const runCommand = options.runCommand ?? defaultRunCommand;
44
+ const installDependencies = options.install === true || options.fresh === true;
45
+ if (options.fresh)
46
+ await rm(previewRoot, { force: true, recursive: true });
47
+ await mkdir(previewRoot, { recursive: true });
48
+ await mkdir(workspaceDir, { recursive: true });
49
+ for (const name of templateWorkspacePaths) {
50
+ await rm(path.join(workspaceDir, name), { force: true, recursive: true });
51
+ }
52
+ await initWorkspace({ targetDir: workspaceDir, force: true });
53
+ if (installDependencies) {
54
+ const packageJson = await readFile(path.join(workspaceDir, "package.json"), "utf8");
55
+ const installedPackageJson = await exists(installStamp) ? await readFile(installStamp, "utf8") : "";
56
+ if (!await exists(path.join(workspaceDir, "node_modules")) ||
57
+ !await exists(path.join(workspaceDir, "pnpm-lock.yaml")) ||
58
+ installedPackageJson !== packageJson) {
59
+ await runCommand("pnpm", ["install", "--ignore-workspace"], { cwd: workspaceDir });
60
+ await writeFile(installStamp, packageJson, "utf8");
61
+ }
62
+ }
63
+ if (installDependencies) {
64
+ await runCommand("pnpm", ["run", "build"], { cwd: workspaceDir });
65
+ }
66
+ else {
67
+ await runCommand("node", ["tools/adoc-books.mjs", "build"], { cwd: workspaceDir });
68
+ }
69
+ await rm(legacyHtmlMirrorDir, { force: true, recursive: true });
70
+ return { previewRoot, workspaceDir, htmlDir };
71
+ }
72
+ async function main() {
73
+ const fresh = process.argv.includes("--fresh");
74
+ const result = await buildTemplatePreview({
75
+ fresh,
76
+ install: fresh || process.argv.includes("--install")
77
+ });
78
+ const displayIndex = path.relative(process.cwd(), path.join(result.htmlDir, "index.html")) || "index.html";
79
+ console.log(`Built template preview at ${displayIndex}`);
80
+ }
81
+ const executedPath = process.argv[1] ? path.resolve(process.argv[1]) : "";
82
+ const modulePath = fileURLToPath(import.meta.url);
83
+ if (executedPath === modulePath) {
84
+ main().catch((error) => {
85
+ console.error(error instanceof Error ? error.message : error);
86
+ process.exitCode = 1;
87
+ });
88
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/create.js CHANGED
@@ -23,8 +23,8 @@ async function main() {
23
23
  process.exitCode = 1;
24
24
  return;
25
25
  }
26
- const created = await initWorkspace({ targetDir: target, force: args.includes("--force") });
27
- const displayTarget = path.relative(process.cwd(), created) || ".";
26
+ const result = await initWorkspace({ targetDir: target, force: args.includes("--force") });
27
+ const displayTarget = path.relative(process.cwd(), result.targetDir) || ".";
28
28
  console.log(`Created AsciiDoc multi-book workspace in ${displayTarget}`);
29
29
  console.log("");
30
30
  console.log("Next:");
@@ -0,0 +1,2 @@
1
+ export { initWorkspace } from "./init-workspace.js";
2
+ export type { InitWorkspaceOptions, InitWorkspaceResult } from "./init-workspace.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { initWorkspace } from "./init-workspace.js";
@@ -0,0 +1,9 @@
1
+ export interface InitWorkspaceOptions {
2
+ targetDir: string;
3
+ force?: boolean;
4
+ }
5
+ export interface InitWorkspaceResult {
6
+ targetDir: string;
7
+ packageName: string;
8
+ }
9
+ export declare function initWorkspace(options: InitWorkspaceOptions): Promise<InitWorkspaceResult>;
@@ -54,5 +54,5 @@ export async function initWorkspace(options) {
54
54
  await writeFile(path.join(targetDir, ".gitignore"), await readFile(path.join(templateRoot, "gitignore.template"), "utf8"), "utf8");
55
55
  await mkdir(path.join(targetDir, "tools"), { recursive: true });
56
56
  await cp(runtimeScript, path.join(targetDir, "tools", "adoc-books.mjs"), { force: true });
57
- return targetDir;
57
+ return { targetDir, packageName };
58
58
  }
@@ -2,16 +2,54 @@
2
2
  import { createRequire } from "node:module";
3
3
  import { cp, mkdir, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  const require = createRequire(import.meta.url);
7
7
  const DIAGRAM_BLOCK_PATTERN = /^\[(?:actdiag|blockdiag|bpmn|bytefield|c4plantuml|d2|dbml|ditaa|erd|excalidraw|graphviz|mermaid|nomnoml|nwdiag|packetdiag|pikchr|plantuml|rackdiag|seqdiag|svgbob|symbolator|umlet|vega|vegalite|wavedrom|structurizr|diagramsnet|wireviz)(?:,|\])/m;
8
8
  const CATALOG_BOOK_XREF_PATTERN = /xref:books\/([^/\]]+)\/book\.adoc(?:#[^\[]+)?\[/g;
9
9
  const XREF_PATTERN = /xref:([^\[#]+)(?:#([A-Za-z0-9_-]+))?\[/g;
10
- const ANCHOR_PATTERN = /^\[#([A-Za-z0-9_-]+)\]$/gm;
10
+ const ANCHOR_PATTERN = /^\[#([A-Za-z0-9_-]+)(?:[.,][^\]]*)?\]$/gm;
11
11
  const LOCAL_TARGET_PATTERN = /\b(?:href|src)="([^"]+)"/g;
12
12
  const SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
13
13
  const HOME_MARKER = "data-multi-book-home";
14
14
  const FETCH_DIAGRAMS_ENV = "ADOC_BOOKS_FETCH_DIAGRAMS";
15
+ const CONFIG_FILE = "adoc-books.config.mjs";
16
+ const DEFAULT_CONFIG = {
17
+ rootIndex: {
18
+ redirectTo: "catalog.html",
19
+ title: "AsciiDoc Multi-Book Workspace"
20
+ },
21
+ homeLink: {
22
+ label: "Back to catalog",
23
+ subtitle: "AsciiDoc multi-book workspace"
24
+ }
25
+ };
26
+ function objectValue(value) {
27
+ return typeof value === "object" && value !== null ? value : {};
28
+ }
29
+ function stringValue(value, fallback) {
30
+ return typeof value === "string" && value.trim() !== "" ? value : fallback;
31
+ }
32
+ async function loadRuntimeConfig(rootDir) {
33
+ const configPath = path.join(rootDir, CONFIG_FILE);
34
+ if (!await existsFile(configPath))
35
+ return DEFAULT_CONFIG;
36
+ const configUrl = pathToFileURL(configPath);
37
+ configUrl.search = `mtime=${(await stat(configPath)).mtimeMs}`;
38
+ const module = await import(configUrl.href);
39
+ const rawConfig = objectValue(module.default ?? module);
40
+ const rawRootIndex = objectValue(rawConfig.rootIndex);
41
+ const rawHomeLink = objectValue(rawConfig.homeLink);
42
+ return {
43
+ rootIndex: {
44
+ redirectTo: stringValue(rawRootIndex.redirectTo, DEFAULT_CONFIG.rootIndex.redirectTo),
45
+ title: stringValue(rawRootIndex.title, DEFAULT_CONFIG.rootIndex.title)
46
+ },
47
+ homeLink: {
48
+ label: stringValue(rawHomeLink.label, DEFAULT_CONFIG.homeLink.label),
49
+ subtitle: stringValue(rawHomeLink.subtitle, DEFAULT_CONFIG.homeLink.subtitle)
50
+ }
51
+ };
52
+ }
15
53
  async function existsFile(filePath) {
16
54
  try {
17
55
  const stats = await stat(filePath);
@@ -170,7 +208,7 @@ function escapeHtmlAttribute(value) {
170
208
  .replaceAll("<", "&lt;")
171
209
  .replaceAll(">", "&gt;");
172
210
  }
173
- function addHomeLinkToBookHtml(html, href) {
211
+ function addHomeLinkToBookHtml(html, href, homeLink) {
174
212
  if (html.includes(HOME_MARKER))
175
213
  return html;
176
214
  const marker = '<div id="toc" class="toc2">';
@@ -205,31 +243,32 @@ function addHomeLinkToBookHtml(html, href) {
205
243
  }
206
244
  </style>
207
245
  <div class="multi-book-home" ${HOME_MARKER}>
208
- <a href="${escapeHtmlAttribute(href)}">Back to catalog<span>AsciiDoc multi-book workspace</span></a>
246
+ <a href="${escapeHtmlAttribute(href)}">${escapeHtmlAttribute(homeLink.label)}<span>${escapeHtmlAttribute(homeLink.subtitle)}</span></a>
209
247
  </div>`;
210
248
  return `${html.slice(0, insertAt)}${homeBlock}${html.slice(insertAt)}`;
211
249
  }
212
- async function addHomeLinks(rootDir, books) {
250
+ async function addHomeLinks(rootDir, books, homeLink) {
213
251
  const catalog = path.join(rootDir, "build", "html", "catalog.html");
214
252
  for (const book of books) {
215
253
  const htmlFile = path.join(book.htmlOutputDir, "book.html");
216
254
  const html = await readFile(htmlFile, "utf8");
217
255
  const href = path.relative(path.dirname(htmlFile), catalog);
218
- await writeFile(htmlFile, addHomeLinkToBookHtml(html, href), "utf8");
256
+ await writeFile(htmlFile, addHomeLinkToBookHtml(html, href, homeLink), "utf8");
219
257
  }
220
258
  }
221
- async function writeRootIndex(rootDir) {
259
+ async function writeRootIndex(rootDir, rootIndex) {
222
260
  const outputDir = path.join(rootDir, "build", "html");
223
261
  await mkdir(outputDir, { recursive: true });
262
+ const redirectTo = rootIndex.redirectTo;
224
263
  await writeFile(path.join(outputDir, "index.html"), `<!doctype html>
225
264
  <html lang="en">
226
265
  <head>
227
266
  <meta charset="utf-8">
228
- <meta http-equiv="refresh" content="0; url=catalog.html">
229
- <title>AsciiDoc Multi-Book Workspace</title>
267
+ <meta http-equiv="refresh" content="0; url=${escapeHtmlAttribute(redirectTo)}">
268
+ <title>${escapeHtmlAttribute(rootIndex.title)}</title>
230
269
  </head>
231
270
  <body>
232
- <p><a href="catalog.html">catalog.html</a></p>
271
+ <p><a href="${escapeHtmlAttribute(redirectTo)}">${escapeHtmlAttribute(redirectTo)}</a></p>
233
272
  </body>
234
273
  </html>
235
274
  `, "utf8");
@@ -351,14 +390,15 @@ export async function buildWorkspace(rootDir = process.cwd()) {
351
390
  const books = await discoverBooks(rootDir);
352
391
  if (books.length === 0)
353
392
  throw new Error(`missing book.adoc entries in ${path.join(rootDir, "books")}`);
393
+ const config = await loadRuntimeConfig(rootDir);
354
394
  const useKroki = await workspaceUsesDiagrams(rootDir, books);
355
395
  const fetchDiagrams = shouldFetchDiagrams();
356
396
  const asciidoctor = createAsciidoctor(useKroki);
357
397
  await buildReducedAdoc(rootDir, books, asciidoctor);
358
398
  await buildHtml(rootDir, books, asciidoctor, useKroki, fetchDiagrams);
359
399
  await copyAssets(rootDir, books);
360
- await addHomeLinks(rootDir, books);
361
- await writeRootIndex(rootDir);
400
+ await addHomeLinks(rootDir, books, config.homeLink);
401
+ await writeRootIndex(rootDir, config.rootIndex);
362
402
  await assertNoIssues("HTML local resource check", await missingLocalResources(rootDir));
363
403
  await assertNoIssues("workspace contract check", await workspaceContractIssues(rootDir, books));
364
404
  }
@@ -0,0 +1,3 @@
1
+ export declare const defaultWorkspaceTemplate = "default-workspace";
2
+ export declare const templatePackageNamePlaceholder = "{{name}}";
3
+ export declare const fixedTemplateOutputPaths: readonly ["README.md", "catalog.adoc", "package.json", ".gitignore", "tools/adoc-books.mjs", "books", "shared"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-asciidoc-multi-book-workspace",
3
- "version": "0.1.0",
3
+ "version": "0.1.6",
4
4
  "description": "Create a complete AsciiDoc multi-book workspace.",
5
5
  "license": "Apache-2.0",
6
6
  "author": {
@@ -17,6 +17,13 @@
17
17
  },
18
18
  "homepage": "https://michengliang.github.io/asciidoc-multi-book-workspace/",
19
19
  "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "types": "./dist/index.d.ts",
20
27
  "keywords": [
21
28
  "asciidoc",
22
29
  "asciidoctor",
@@ -29,7 +36,13 @@
29
36
  "create-asciidoc-multi-book-workspace": "dist/create.js"
30
37
  },
31
38
  "files": [
32
- "dist/",
39
+ "dist/build-template-preview.js",
40
+ "dist/*.d.ts",
41
+ "dist/create.js",
42
+ "dist/index.js",
43
+ "dist/init-workspace.js",
44
+ "dist/runtime/adoc-books.mjs",
45
+ "dist/template-manifest.js",
33
46
  "templates/",
34
47
  "README.md",
35
48
  "CHANGELOG.md",
@@ -47,6 +60,8 @@
47
60
  },
48
61
  "scripts": {
49
62
  "build": "tsc -p tsconfig.json",
63
+ "preview:build": "node dist/build-template-preview.js",
64
+ "preview:fresh": "pnpm run build && node dist/build-template-preview.js --fresh",
50
65
  "test": "pnpm run build && node --test test/*.test.mjs",
51
66
  "pack:check": "pnpm pack --dry-run",
52
67
  "check": "pnpm run test && pnpm run pack:check",
@@ -1,6 +1,6 @@
1
1
  # My Books
2
2
 
3
- This is an AsciiDoc multi-book workspace. It starts with seven sample books that show common ways to organize independent books, multipart books, technical books, reference material, and split volumes.
3
+ This is an AsciiDoc multi-book workspace. It starts with sample books that show common ways to organize independent books, multipart books, technical books, reference material, split volumes, and structured writing conventions.
4
4
 
5
5
  ## Install
6
6
 
@@ -5,14 +5,25 @@
5
5
  |写法 |含义
6
6
 
7
7
  |`= 书名`
8
- |document title。
8
+ |Document title(文档标题)。在 book 文档类型中作为整本书标题。
9
9
 
10
- |`= 第一部`
11
- |book doctype 中的 part。
10
+ |`= 部标题`
11
+ |Document title 之后的 Level 0 Section。在 book 文档类型中可作为 Part(部)。
12
12
 
13
13
  |`== 章`
14
- |chapter。
14
+ |Level 1 Section(一级章节)。在 book 文档类型中通常作为 Chapter(章)。
15
15
 
16
16
  |`=== 节`
17
- |section。
17
+ |Level 2 Section(二级章节 / 节)。
18
+
19
+ |`==== 小节`
20
+ |Level 3 Section(三级章节 / 小节)。
21
+
22
+ |`===== 次小节`
23
+ |Level 4 Section(四级章节)。
24
+
25
+ |`====== 最底层级`
26
+ |Level 5 Section(五级章节)。AsciiDoc 支持的最深层级。
18
27
  |===
28
+
29
+ `[appendix]`、`[glossary]`、`[bibliography]` 和 `[index]` 是 section style。它们改变紧随其后 section 的语义;标题层级仍由 `=` 数量决定。
@@ -1,4 +1,6 @@
1
1
  = 参考资料
2
2
 
3
+ 每条参考资料可使用 bibliography anchor 作为稳定引用目标。
4
+
3
5
  * [[[asciidoctor-section-styles]]] Asciidoctor Docs, Section Styles for Articles and Books, https://docs.asciidoctor.org/asciidoc/latest/sections/styles/
4
6
  * [[[asciidoctor-parts]]] Asciidoctor Docs, Book Parts, https://docs.asciidoctor.org/asciidoc/latest/sections/parts/
@@ -1,5 +1,7 @@
1
1
  = 术语表
2
2
 
3
+ 术语表通常使用定义列表记录术语和解释。
4
+
3
5
  book:: 具有独立 `book.adoc`、章节顺序和构建产物的书籍单位。
4
6
  part:: 同一本 book 内部的 level 0 大分部。
5
7
  chapter:: book 中的主要正文单位,通常使用 `==` 标题。
@@ -7,4 +9,4 @@ section:: chapter 内部的层级标题。
7
9
  appendix:: 放置补充材料的后置章节。
8
10
  glossary:: 使用定义列表解释术语的后置章节。
9
11
  bibliography:: 放置参考资料的后置章节。
10
- index:: 放置索引入口的后置章节。
12
+ index:: 放置索引目录种子或索引说明的后置章节;自动索引目录依赖转换器支持。
@@ -1,9 +1,37 @@
1
1
  = 索引
2
2
 
3
- 索引 section 位于书籍后置区域。正文中的索引词宏是索引词来源:
3
+ 索引 section 位于书籍后置区域。正文中的索引词宏是索引词来源。
4
+
5
+ AsciiDoc 区分正文可见索引词和正文隐藏索引词:
6
+
7
+ [cols="1,2", options="header"]
8
+ |===
9
+ |写法 |含义
10
+
11
+ |`indexterm2:[<primary>]`
12
+ |正文可见,同时作为一级索引项。
13
+
14
+ |`indexterm:[<primary>]`
15
+ |正文不可见,作为一级索引项。
16
+
17
+ |`indexterm:[<primary>, <secondary>]`
18
+ |正文不可见,作为二级索引项。
19
+
20
+ |`indexterm:[<primary>, <secondary>, <tertiary>]`
21
+ |正文不可见,作为三级索引项。
22
+ |===
23
+
24
+ 正文中已经出现该词时,优先使用正文可见索引词。正文没有出现该词,但当前位置需要进入索引时,才使用正文隐藏索引词。
25
+
26
+ 隐藏索引词应贴近它标记的段落内容。本书把隐藏索引词放在相关句子末尾,避免形成独立空段落。
27
+
28
+ Asciidoctor 的内置 HTML5 转换器不会自动生成索引目录。`[index]` section 是 PDF 和 DocBook 工具链自动填充索引目录的种子位置;在本 HTML 样本中,它展示后置索引章节的位置和索引词写法。
29
+
30
+ 本书正文中的索引词示例:
4
31
 
5
32
  [source,asciidoc]
6
33
  ----
7
- (((section)))
8
- (((discrete heading)))
34
+ indexterm2:[section]
35
+ indexterm:[section, hierarchy]
36
+ indexterm:[heading, discrete]
9
37
  ----
@@ -41,6 +41,8 @@ include::parts/02-body-structure/_partintro.adoc[]
41
41
 
42
42
  include::parts/02-body-structure/01-body-map.adoc[]
43
43
 
44
+ include::parts/02-body-structure/02-inline-structure.adoc[]
45
+
44
46
  [appendix]
45
47
  include::backmatter/appendix-a.adoc[]
46
48
 
@@ -1,5 +1,5 @@
1
1
  == 摘要
2
2
 
3
- 本书是 AsciiDoc book 的结构标本。它集中展示前置内容、正文分部、章节层级、附录、术语表、参考资料和索引在书稿中的位置。
3
+ 本书是 AsciiDoc book 的常见结构标本。它集中展示前置内容、正文分部、章节层级、附录、术语表、参考资料和索引在书稿中的位置及组合规则。
4
4
 
5
- 本书用于学习完整结构,不作为日常新书的复制起点。
5
+ 本书用于学习常见结构组合,不作为日常新书的复制起点。
@@ -2,4 +2,6 @@
2
2
 
3
3
  这本标本书按结构位置组织,而不是按主题展开。阅读时先看 `book.adoc`,再对照各个被 include 的文件。
4
4
 
5
+ 读者可以观察结构位置,再按实际书稿需要选择进入自己书中的结构。实际书稿的结构清单由写作目标、读者任务和发布边界决定;本书展示结构位置和组合关系,不作为必选清单。
6
+
5
7
  日常写作起点可以参考样本 `01-starter-book`。
@@ -3,6 +3,8 @@
3
3
 
4
4
  前置内容用于建立读者进入正文之前需要知道的说明、来源和阅读位置。
5
5
 
6
+ 方括号中的名称是 section style。它作用于紧随其后的 section;当下一行是 include 指令时,style 作用于被 include 文件中的 section 标题。
7
+
6
8
  [cols="1,2", options="header"]
7
9
  |===
8
10
  |结构 |职责
@@ -1,13 +1,17 @@
1
1
  [#body-map]
2
2
  == 正文结构地图
3
3
 
4
- part 是 book doctype 中的 level 0 section。chapter 是 part 下的 level 1 section,也就是 `==` 标题。
4
+ part 是 book doctype 中 document title 之后的 level 0 section。level 1 section 通常形成 chapter;multipart book 中 chapter 位于 part 下,普通 book 中 chapter 可以直接位于 book 下。
5
+
6
+ 本模板把每个 part 的源文件放在 `parts/<part-id>/` 下。这个目录约定服务维护边界;part 本身由 `book.adoc` 中的 level 0 section 声明。
7
+
8
+ 本书启用 `:sectnums:`。part 标题不占用 chapter 编号;part 下的 level 1 section 按 `book.adoc` 展开后的顺序形成连续 chapter 编号。
5
9
 
6
10
  === 小节层级
7
11
 
8
- 小节用于组织 chapter 内部内容。(((section)))
12
+ indexterm2:[section] 是 chapter 内部的层级单位。indexterm:[section, hierarchy]
9
13
 
10
14
  [discrete]
11
15
  === 离散标题
12
16
 
13
- 离散标题提供视觉分隔,但不进入书籍目录层级。(((discrete heading)))
17
+ 离散标题显示为标题样式,但不创建 section,不参与章节编号,也不进入目录。indexterm:[heading, discrete]
@@ -0,0 +1,32 @@
1
+ [#inline-structure-map]
2
+ == 内联结构标记
3
+
4
+ 内联结构标记用于给正文中的术语、路径、引用目标和索引入口提供稳定身份。它不改变 book 的章节层级,但会提高正文片段的可维护性和可检索性。
5
+
6
+ [cols="1,2", options="header"]
7
+ |===
8
+ |写法 |结构作用
9
+
10
+ |`[#stable-anchor]`
11
+ |声明稳定引用目标。
12
+
13
+ |`xref:#body-map[正文结构地图]`
14
+ |引用同一文档内的稳定目标。
15
+
16
+ |`indexterm2:[term]`
17
+ |把正文中已经出现的词作为可见索引词。
18
+
19
+ |`indexterm:[term, detail]`
20
+ |正文没有出现该词时,补充隐藏索引入口。
21
+
22
+ |`[.term]#inline role#`
23
+ |给术语片段提供语义身份。
24
+
25
+ |`[.path]` + monospace path
26
+ |给路径文本提供语义身份。
27
+
28
+ |`{series-name}`
29
+ |引用共享属性,减少多处重复文本。
30
+ |===
31
+
32
+ 语义 inline role 提供结构身份;具体视觉效果取决于输出样式。本书使用 [.term]#inline role# 标记术语片段,使用 [.path]`parts/02-body-structure/` 标记路径片段。
@@ -0,0 +1,62 @@
1
+ = 附录 A:结构化写法速查
2
+
3
+ [cols="1,2", options="header"]
4
+ |===
5
+ |写法 |含义
6
+
7
+ |`= 书名`
8
+ |Document title(文档标题)。在 book 文档类型中作为整本书标题。
9
+
10
+ |`= 部标题`
11
+ |Document title 之后的 Level 0 Section。在 book 文档类型中可作为 Part(部)。
12
+
13
+ |`== 章 / 标题`
14
+ |Level 1 Section(一级章节)。创建书稿阅读结构,在 book 文档类型中通常作为 Chapter(章)。
15
+
16
+ |`=== 节`
17
+ |Level 2 Section(二级章节 / 节)。
18
+
19
+ |`==== 小节`
20
+ |Level 3 Section(三级章节 / 小节)。
21
+
22
+ |`===== 次小节`
23
+ |Level 4 Section(四级章节)。
24
+
25
+ |`====== 最底层级`
26
+ |Level 5 Section(五级章节)。AsciiDoc 支持的最深层级。
27
+
28
+ |`parts/100-topic/`
29
+ |part 目录的源文件编排号。
30
+
31
+ |`010-opening.adoc`
32
+ |chapter 文件的源文件编排号。
33
+
34
+ |`[#stable-id]`
35
+ |为重要标题声明稳定 ID。
36
+
37
+ |`[#stable-id.concept]`
38
+ |声明稳定 ID,并用 role 标明标题身份。
39
+
40
+ |`[#example.example, owner=writing-team]`
41
+ |role 声明标题身份;named attributes 保留附加字段。
42
+
43
+ |`xref:stable-id[显示文本]`
44
+ |显示为链接,投影为 references。
45
+
46
+ |`xref:stable-id[显示文本, rel=depends-on]`
47
+ |显示为链接,同时声明 depends-on 关系。
48
+
49
+ |`indexterm2:[role]`
50
+ |正文可见索引词。
51
+
52
+ |`indexterm:[relation predicate]`
53
+ |正文隐藏索引词。
54
+ |===
55
+
56
+ role 描述标题身份。rel 描述 xref 边上的关系谓词。named attributes 是附加字段。
57
+
58
+ `[appendix]`、`[glossary]`、`[bibliography]` 和 `[index]` 是 section style。它们改变紧随其后 section 的语义;标题层级仍由 `=` 数量决定。
59
+
60
+ 对于跨文件的交叉引用,为了避免重复编写相对路径,你可以在文档的头部(Header)定义一个文档属性(Document Attribute)来存储这个基础路径。
61
+
62
+ 你可以在主文档的开头定义一个变量(比如 `:book-b-dir:`),后续引用时通过 `{book-b-dir}` 来调用它。
@@ -0,0 +1,7 @@
1
+ = 参考坐标
2
+
3
+ 以下资料提供 AsciiDoc 标题、交叉引用和元素属性的参考坐标。
4
+
5
+ * [[[asciidoctor-xref]]] Asciidoctor Docs, Cross References, https://docs.asciidoctor.org/asciidoc/latest/macros/xref/
6
+ * [[[asciidoctor-attributes]]] Asciidoctor Docs, Element Attributes, https://docs.asciidoctor.org/asciidoc/latest/attributes/element-attributes/
7
+ * [[[asciidoctor-sections]]] Asciidoctor Docs, Sections, https://docs.asciidoctor.org/asciidoc/latest/sections/
@@ -0,0 +1,14 @@
1
+ = 术语表
2
+
3
+ 本术语表记录结构化书写约定中的核心术语。
4
+
5
+ heading:: 使用等号标题创建的书稿结构单位。
6
+ stable ID:: 作者声明的稳定引用地址,例如 `#stable-heading`。
7
+ source-order notation:: 写在源文件路径表面的排序符号。
8
+ role:: 标题身份标记,例如 `.concept` 或 `.rule`。
9
+ ordinary reference:: 普通 xref。投影时使用默认谓词 `aat:references`。
10
+ relation predicate:: xref `rel` 字段选择的边谓词。
11
+ source heading:: xref 所在的标题。
12
+ target heading:: xref selector 指向的标题。
13
+ surface field:: named attributes 携带的源文档表面字段。
14
+ index term:: 服务书后索引和读者检索的索引入口。
@@ -0,0 +1,37 @@
1
+ = 索引
2
+
3
+ 索引 section 位于书籍后置区域。正文中的索引词宏是索引词来源。
4
+
5
+ AsciiDoc 区分正文可见索引词和正文隐藏索引词:
6
+
7
+ [cols="1,2", options="header"]
8
+ |===
9
+ |写法 |含义
10
+
11
+ |`indexterm2:[<primary>]`
12
+ |正文可见,同时作为一级索引项。
13
+
14
+ |`indexterm:[<primary>]`
15
+ |正文不可见,作为一级索引项。
16
+
17
+ |`indexterm:[<primary>, <secondary>]`
18
+ |正文不可见,作为二级索引项。
19
+
20
+ |`indexterm:[<primary>, <secondary>, <tertiary>]`
21
+ |正文不可见,作为三级索引项。
22
+ |===
23
+
24
+ 正文中已经出现该词时,优先使用正文可见索引词。需要补充未出现在正文里的索引入口时,使用正文隐藏索引词。
25
+
26
+ 隐藏索引词应贴近它标记的段落内容。本书把隐藏索引词放在相关句子末尾。
27
+
28
+ Asciidoctor 的内置 HTML5 转换器不会自动生成索引目录。`[index]` section 是 PDF 和 DocBook 工具链自动填充索引目录的种子位置;在本 HTML 样本中,它展示后置索引章节的位置和索引词写法。
29
+
30
+ 本书正文中的索引词示例:
31
+
32
+ [source,asciidoc]
33
+ ----
34
+ indexterm2:[role]
35
+ indexterm:[relation predicate]
36
+ indexterm:[源文件编排号]
37
+ ----
@@ -0,0 +1,71 @@
1
+ = 结构化书写约定标本
2
+ 作者 <author@example.com>
3
+ v0.1, 2026-05
4
+ :doctype: book
5
+ :toc: left
6
+ :toclevels: 3
7
+ :sectnums:
8
+ :partnums:
9
+ :part-signifier: Part
10
+ :icons: font
11
+ :experimental:
12
+ :idprefix:
13
+ :idseparator: -
14
+
15
+ include::../../shared/attributes.adoc[]
16
+
17
+ [abstract]
18
+ include::frontmatter/abstract.adoc[]
19
+
20
+ [colophon]
21
+ include::frontmatter/colophon.adoc[]
22
+
23
+ [dedication]
24
+ include::frontmatter/dedication.adoc[]
25
+
26
+ [preface]
27
+ include::frontmatter/preface.adoc[]
28
+
29
+ [acknowledgments]
30
+ include::frontmatter/acknowledgments.adoc[]
31
+
32
+ = 从源文档看结构
33
+
34
+ [partintro]
35
+ include::parts/100-source-surface/_partintro.adoc[]
36
+
37
+ include::parts/100-source-surface/010-source-and-projection.adoc[]
38
+
39
+ include::parts/100-source-surface/020-default-semantics.adoc[]
40
+
41
+ include::parts/100-source-surface/030-source-order-notation.adoc[]
42
+
43
+ = 给标题和引用加意图
44
+
45
+ [partintro]
46
+ include::parts/200-identity-and-relation/_partintro.adoc[]
47
+
48
+ include::parts/200-identity-and-relation/010-role-identity.adoc[]
49
+
50
+ include::parts/200-identity-and-relation/020-relation-predicate.adoc[]
51
+
52
+ = 字段、索引与术语
53
+
54
+ [partintro]
55
+ include::parts/300-fields-and-lookup/_partintro.adoc[]
56
+
57
+ include::parts/300-fields-and-lookup/010-surface-fields.adoc[]
58
+
59
+ include::parts/300-fields-and-lookup/020-index-and-glossary.adoc[]
60
+
61
+ [appendix]
62
+ include::backmatter/appendix-a.adoc[]
63
+
64
+ [glossary]
65
+ include::backmatter/glossary.adoc[]
66
+
67
+ [bibliography]
68
+ include::backmatter/bibliography.adoc[]
69
+
70
+ [index]
71
+ include::backmatter/index.adoc[]
@@ -0,0 +1,3 @@
1
+ == 摘要
2
+
3
+ 本书会通过展示标题、稳定 ID、role、xref、rel 和 named attributes 如何形成可读、可维护、可投影的源文档,让你能够理解,你可以有这样一种表达方式,按照某种约定,能够提升你书籍的质量,能够让下游的工具链继续消费你的书。 你可以参考你当前写书所需要的结构,合适的结构能够提升书籍的可阅读,可维护性。你可以按写作目的选择其中写法。
@@ -0,0 +1,3 @@
1
+ == 致谢
2
+
3
+ 感谢 Asciidoctor 和 RDF 投影相关文档提供稳定术语和结构坐标。
@@ -0,0 +1,5 @@
1
+ == 版本说明
2
+
3
+ 本文档属于 `{series-name}`。colophon 位于书籍前置区域,记录本样本书的版本、来源或出版信息。
4
+
5
+ 示例版本:v0.1。
@@ -0,0 +1,3 @@
1
+ == 献辞
2
+
3
+ 献给每一位愿意为自己写下的标题、引用和标记负责的作者。
@@ -0,0 +1,7 @@
1
+ == 前言
2
+
3
+ 这本标本书按源文档写法组织。阅读时先看源文档片段和投影片段,再对照后续章节理解每个标记承担的职责。
4
+
5
+ 实际书稿按写作目的选择这些写法。长期引用的标题使用稳定 ID;带有明确身份的标题使用 role;表达关系的引用使用 rel;随源稿保留的字段使用 named attributes。
6
+
7
+ 业务含义由书稿自己的约定承载。本书展示同一份 AsciiDoc 源文本如何同时服务阅读、维护和工具链投影。
@@ -0,0 +1,44 @@
1
+ [#source-and-projection]
2
+ == 源文本与投影
3
+
4
+ 本章用一段示例展示结构化书写约定。Turtle 片段列出关键投影事实;完整投影还会保留 raw、路径、行号和边证据。
5
+ 希望你能通过本章节的演示来理解你写下的文字能够给未来的你提供稳定的查询与导航结构。
6
+
7
+ [source,asciidoc]
8
+ ----
9
+ = 结构化写作样例
10
+
11
+ [#stable-heading.concept]
12
+ == 稳定标题
13
+
14
+ 稳定标题提供可引用的阅读单位。
15
+
16
+ [#xref-rule.rule]
17
+ == 引用规则
18
+
19
+ 引用规则参见 xref:stable-heading[稳定标题]。
20
+ 引用规则依赖 xref:stable-heading[稳定标题, rel=depends-on, weight=strong]。
21
+ ----
22
+
23
+ 源文档把 `稳定标题` 写成可引用的阅读单位,把 `引用规则` 写成规则说明,并在正文里写出参见和依赖两种引用。
24
+
25
+ [source,turtle]
26
+ ----
27
+ :stable-heading a aat:Heading ;
28
+ aat:headline "稳定标题" ;
29
+ aat:addressLabel "stable-heading" ;
30
+ aat:role "concept" .
31
+
32
+ :xref-rule a aat:Heading ;
33
+ aat:headline "引用规则" ;
34
+ aat:addressLabel "xref-rule" ;
35
+ aat:role "rule" ;
36
+ aat:references :stable-heading ;
37
+ rel:depends-on :stable-heading .
38
+
39
+ :xref-edge-depends-on a aat:XrefEdge ;
40
+ aat:rel "depends-on" ;
41
+ aat:weight "strong" .
42
+ ----
43
+
44
+ 读者看源文档,能直接看到 [.concept]#稳定标题# 是被引用的阅读单位, [.rule]#引用规则# 说明引用写法。下游投影工具链读取同一份源文档,得到 `aat:Heading`、`aat:addressLabel`、`aat:role`、`aat:references` 和 `rel:depends-on`。
@@ -0,0 +1,26 @@
1
+ [#heading-and-xref]
2
+ == 标题与引用
3
+
4
+ 等号行是标题。标题前一行的 attrlist 承载地址和 role。
5
+
6
+ [source,asciidoc]
7
+ ----
8
+ [#stable-heading.concept]
9
+ == 稳定标题
10
+ ----
11
+
12
+ `#stable-heading` 给标题一个稳定地址;`.concept` 标明标题身份;`稳定标题` 是读者看到的标题文本。
13
+
14
+ [source,asciidoc]
15
+ ----
16
+ 引用规则参见 xref:stable-heading[稳定标题]。
17
+ ----
18
+
19
+ 这条 xref 表示参见目标标题。投影时,这条引用使用默认谓词 `aat:references`。
20
+
21
+ [source,asciidoc]
22
+ ----
23
+ 引用规则依赖 xref:stable-heading[稳定标题, rel=depends-on]。
24
+ ----
25
+
26
+ 写入 `rel=depends-on` 时,作者声明当前标题到目标标题的依赖关系。投影时,这条关系使用 `rel:depends-on`。
@@ -0,0 +1,23 @@
1
+ [#source-order-notation]
2
+ == 源文件编排号
3
+
4
+ 源文件编排号写在 part 目录名和 chapter 文件名前部。indexterm:[源文件编排号]
5
+
6
+ part 目录使用 `100-`、`200-`、`300-`。chapter 文件在所属 part 内使用 `010-`、`020-`、`030-`。
7
+
8
+ 中间插入新文件时,使用相邻编号之间的空位。
9
+
10
+ [source,text]
11
+ ----
12
+ parts/100-source-surface/
13
+ parts/150-new-topic/
14
+ parts/200-identity-and-relation/
15
+
16
+ 010-opening.adoc
17
+ 015-new-context.adoc
18
+ 020-main-flow.adoc
19
+ ----
20
+
21
+ 标题文本保留语义名称。长期引用的标题使用显式 ID。
22
+
23
+ 编排号帮助维护者观察源文件顺序;显式 ID 帮助读者和工具链稳定引用标题。
@@ -0,0 +1 @@
1
+ 本部从一段源文档出发,展示读者看到的写作意图和投影得到的结构事实。
@@ -0,0 +1,37 @@
1
+ [#role-identity]
2
+ == role 身份
3
+
4
+ `[#xref-rule.rule]` 里的 `.rule` 是 indexterm2:[role]。它把这个标题标成规则说明。
5
+
6
+ role 写在标题 attrlist 的点号 token 位置。
7
+
8
+ [source,asciidoc]
9
+ ----
10
+ [#stable-heading.concept]
11
+ == 稳定标题
12
+
13
+ [#xref-rule.rule]
14
+ == 引用规则
15
+ ----
16
+
17
+ `#stable-heading` 和 `#xref-rule` 是稳定 ID。`.concept` 和 `.rule` 标明标题身份。
18
+
19
+ [cols="1,2", options="header"]
20
+ |===
21
+ |写法 |读者看到的身份
22
+
23
+ |`[.concept]`
24
+ |标记概念说明。
25
+
26
+ |`[.rule]`
27
+ |标记规则说明。
28
+
29
+ |`[.example]`
30
+ |标记示例。
31
+ |===
32
+
33
+ role 的职责,是在全书范围内保持身份含义的稳定。
34
+
35
+ 一旦一个 role 被定义——例如 `.rule` 代表规则说明——它在全书任何地方都只能代表这个身份,不能产生歧义。一本书需要多少种 role,取决于它所要描述的领域固有的本质复杂度。无论三种还是十种,每一种都必须是确定的。
36
+
37
+ 读者和工具链依赖这种稳定理解来导航文本和提取结构。
@@ -0,0 +1,22 @@
1
+ [#relation-predicate]
2
+ == rel 关系谓词
3
+
4
+ indexterm:[relation predicate] `rel` 是 xref 边上的关系谓词。xref 写在哪个标题下面,哪个标题就是关系起点;xref 指向哪个标题,哪个标题就是关系终点。本书把这两个位置称为 source heading 和 target heading。
5
+
6
+ [source,asciidoc]
7
+ ----
8
+ 引用规则参见 xref:stable-heading[稳定标题]。
9
+ 引用规则依赖 xref:stable-heading[稳定标题, rel=depends-on]。
10
+ ----
11
+
12
+ 第一行表示参见 `稳定标题`,投影为 `aat:references`。第二行写入 `rel=depends-on`,声明 `引用规则` 到 `稳定标题` 的依赖关系,投影为 `rel:depends-on`。
13
+
14
+ `rel` 会被读者和工具当作关系谓词。同一本书使用 rel 时,应保持谓词含义稳定。
15
+
16
+ [horizontal]
17
+ depends-on:: 当前标题的判断、规则或操作以目标标题为依据。阅读顺序和先读建议使用普通 xref 表达。
18
+ illustrates:: 当前标题提供目标标题的示例。
19
+ defines:: 当前标题给出目标标题所代表对象的定义。
20
+ constrains:: 当前标题对目标标题的合法写法或范围施加约束。
21
+
22
+ 关系谓词描述标题之间的边。标题自身的身份使用 role 表达。
@@ -0,0 +1 @@
1
+ 本部展示标题身份和引用关系。role 标明标题身份;rel 标明引用关系。 你应该在自己书籍里增加各类词表约定的章节。
@@ -0,0 +1,18 @@
1
+ [#surface-fields]
2
+ == 附加字段
3
+
4
+ named attributes 是写在源文档表面的字段。
5
+
6
+ [source,asciidoc]
7
+ ----
8
+ [#example.example, owner=writing-team]
9
+ == 示例
10
+
11
+ 示例说明 xref:stable-heading[稳定标题, rel=illustrates, weight=strong]。
12
+ ----
13
+
14
+ `.example` 是 role。`owner=writing-team` 是标题附加字段。`weight=strong` 是 xref 边证据上的附加字段。
15
+
16
+ `owner=writing-team` 写在标题 attrlist 中,投影时成为标题字段。`weight=strong` 写在 xref 中,投影时成为边证据字段。
17
+
18
+ 字段名和字段值来自本书自己的约定。同一本书使用附加字段时,应保持字段写法和字段含义稳定。
@@ -0,0 +1,14 @@
1
+ [#index-and-glossary]
2
+ == 索引词与术语表
3
+
4
+ 索引词帮助读者在书后找到词。术语表解释本书采用的词。role 标明标题身份,rel 标明引用关系。
5
+
6
+ 正文里已经自然出现该词时,使用可见索引词标记。需要补充未出现在正文里的索引入口时,使用隐藏索引词。
7
+
8
+ [source,asciidoc]
9
+ ----
10
+ indexterm2:[role]
11
+ indexterm:[relation predicate]
12
+ ----
13
+
14
+ 稳定术语帮助作者、读者和维护者使用同一组词理解源文档。
@@ -0,0 +1 @@
1
+ 本部展示字段、索引词和术语表。字段随源文档保留;索引词和术语表帮助读者找词、解词。
@@ -43,6 +43,10 @@ v0.1, 2026-05
43
43
  |组织可独立发布的下册
44
44
  |xref:books/06-lower-volume/book.adoc[06 下册]
45
45
  |它用文本示例说明跨卷引用写法,但不真实依赖上册。
46
+
47
+ |让源书稿保持可读,同时保留可被工具链读取的结构关系
48
+ |xref:books/07-structured-writing-conventions/book.adoc[07 结构化书写约定标本]
49
+ |它展示标题、稳定 ID、role、xref、rel 和附加字段如何形成结构化书稿表面。
46
50
  |===
47
51
 
48
52
  == 样本书
@@ -54,6 +58,7 @@ v0.1, 2026-05
54
58
  * xref:books/04-reference-manual/book.adoc[04 参考手册]:结构选择、路径规则和引用模式。
55
59
  * xref:books/05-upper-volume/book.adoc[05 上册]:独立分卷的上册。
56
60
  * xref:books/06-lower-volume/book.adoc[06 下册]:独立分卷的下册,并用示例说明跨卷引用写法。
61
+ * xref:books/07-structured-writing-conventions/book.adoc[07 结构化书写约定标本]:标题、稳定 ID、role、xref、rel 和附加字段的结构化写法。
57
62
 
58
63
  == 工作区地图
59
64
 
@@ -66,3 +71,4 @@ image::shared/images/workspace-map.svg[多书工作区结构]
66
71
  * 下册需要说明跨卷引用写法但不制造默认依赖:看 xref:books/06-lower-volume/book.adoc[06 下册]。
67
72
  * 新增一本普通书:从 xref:books/01-starter-book/book.adoc[01 日常独立单书] 开始。
68
73
  * 查路径、引用和增长规则:看 xref:books/04-reference-manual/book.adoc[04 参考手册]。
74
+ * 写需要被读者和工具同时理解的结构化源文本:看 xref:books/07-structured-writing-conventions/book.adoc[07 结构化书写约定标本]。