@rse/specbook 1.2.14 → 1.2.16

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/README.md +98 -11
  2. package/dst/specbook-api.d.ts +22 -21
  3. package/dst/specbook-api.js +80 -27
  4. package/dst/specbook-cli.js +50 -23
  5. package/dst/specbook-cmd-describe.js +8 -4
  6. package/dst/specbook-cmd-export.d.ts +4 -1
  7. package/dst/specbook-cmd-export.js +22 -12
  8. package/dst/specbook-cmd-init.js +2 -8
  9. package/dst/specbook-cmd-lint.d.ts +2 -1
  10. package/dst/specbook-cmd-lint.js +31 -28
  11. package/dst/specbook-config.d.ts +7 -0
  12. package/dst/specbook-config.js +6 -1
  13. package/dst/specbook-coverage.js +3 -4
  14. package/dst/specbook-diagram.d.ts +3 -1
  15. package/dst/specbook-diagram.js +78 -27
  16. package/dst/specbook-export-ast.d.ts +2 -1
  17. package/dst/specbook-export-ast.js +16 -8
  18. package/dst/specbook-export-common.d.ts +7 -0
  19. package/dst/specbook-export-common.js +36 -3
  20. package/dst/specbook-export-html-search.js +27 -24
  21. package/dst/specbook-export-html.css +64 -18
  22. package/dst/specbook-export-html.d.ts +2 -1
  23. package/dst/specbook-export-html.js +519 -277
  24. package/dst/specbook-export-image.d.ts +3 -0
  25. package/dst/specbook-export-image.js +121 -0
  26. package/dst/specbook-export-md.d.ts +8 -2
  27. package/dst/specbook-export-md.js +67 -6
  28. package/dst/specbook-export-pdf.js +7 -4
  29. package/dst/specbook-format.md +35 -8
  30. package/dst/specbook-format.yaml +12 -9
  31. package/dst/specbook-ignore.js +5 -5
  32. package/dst/specbook-link.js +1 -1
  33. package/dst/specbook-mcp.js +51 -34
  34. package/dst/specbook-parse-common.d.ts +4 -1
  35. package/dst/specbook-parse-common.js +15 -3
  36. package/dst/specbook-parse-semantic.d.ts +1 -1
  37. package/dst/specbook-parse-syntax.d.ts +1 -1
  38. package/dst/specbook-parse-syntax.js +28 -8
  39. package/dst/specbook-parse.js +6 -2
  40. package/dst/specbook-project.d.ts +7 -0
  41. package/dst/specbook-project.js +93 -0
  42. package/dst/specbook-theme.d.ts +1 -1
  43. package/dst/specbook-theme.js +9 -8
  44. package/dst/specbook-verbose.js +4 -9
  45. package/package.json +14 -6
@@ -17,6 +17,24 @@ export const searchScript = () => fs.readFileSync(new URL("specbook-export-html-
17
17
  SpecBook itself (as a self-contained data: URL, to keep its styles isolated) */
18
18
  export const fallbackLogo = (theme) => "data:image/svg+xml;base64," +
19
19
  fs.readFileSync(new URL(`specbook-export-logo-${theme}.svg`, import.meta.url)).toString("base64");
20
+ /* the content aspects an export can omit, each matching the set of a
21
+ folding control of the HTML export: the diagrams of a type, the
22
+ diagrams from an object tree nesting level on, and the long texts */
23
+ export const omitAspects = ["diagram:graph", "diagram:hub", "diagram:grid",
24
+ "diagram:1", "diagram:2", "diagram:3", "text:long"];
25
+ /* parse the (comma-separated) aspects to omit, where the
26
+ plain "diagram" is the alias of "diagram:1", hence of all diagrams */
27
+ export const parseOmit = (omit = []) => {
28
+ const aspects = new Set();
29
+ for (const item of omit.flatMap((list) => list.split(",")).map((item) => item.trim())) {
30
+ const aspect = item === "diagram" ? "diagram:1" : item;
31
+ if (!omitAspects.includes(aspect))
32
+ throw new Error(`unknown omit aspect "${item}" ` +
33
+ `(expected "diagram" or one of "${omitAspects.join("\", \"")}")`);
34
+ aspects.add(aspect);
35
+ }
36
+ return aspects;
37
+ };
20
38
  /* check whether an object is the specification title object */
21
39
  export const isTitleObject = (object) => object.kind === "META" && object.name.toUpperCase() === "TITLE";
22
40
  /* determine the title object of the specification, searched at any
@@ -34,8 +52,14 @@ export const titleObject = (specification) => {
34
52
  };
35
53
  return search(specification.artifacts.flatMap((artifact) => artifact.objects));
36
54
  };
37
- /* determine a property value of the title object */
38
- const titleProperty = (specification, name) => titleObject(specification)?.properties.find((property) => property.key === name)?.value;
55
+ /* determine a property value of the title object (a present but empty
56
+ value counting as an absent property, so every consumer falls back
57
+ onto its own default) */
58
+ const titleProperty = (specification, name) => {
59
+ const value = titleObject(specification)
60
+ ?.properties.find((property) => property.key === name)?.value;
61
+ return value !== undefined && value.trim() !== "" ? value : undefined;
62
+ };
39
63
  /* determine the document language (LANG) from the title object */
40
64
  export const documentLang = (specification) => titleProperty(specification, "LANG")?.trim();
41
65
  /* determine the document character set (CHARSET) from the title object */
@@ -76,10 +100,18 @@ export const documentPaperSize = (specification) => {
76
100
  sized diagram could not share its page with the heading, which
77
101
  would defeat the "break-after: avoid" bundling of the stylesheet */
78
102
  const headingReserve = 6;
103
+ /* the vertical room (in rem) the "Diagram of Contents" heading claims
104
+ above its diagram: this <h1> opens its page and hence carries its
105
+ full margins (3rem above, 2rem below) plus its own 2.4rem line and
106
+ the 2rem margins of the diagram, which the flat reserve above
107
+ underestimates, so the diagram would be pushed onto the next page */
108
+ const docHeadingReserve = 10;
79
109
  /* provide the paper-dependent print stylesheet: a diagram is scaled
80
110
  down to still fit onto a single page (the paper height less the
81
111
  print margins, the own vertical margins of the diagram, and the
82
- heading reserve above it) and is never broken across a page boundary */
112
+ heading reserve above it) and is never broken across a page boundary,
113
+ where the diagram of the "Diagram of Contents" page gets the larger
114
+ reserve its own heading demands */
83
115
  export const paperStylesheet = (paper) => {
84
116
  const setup = paperSetup(paper);
85
117
  const avail = setup.height - setup.margin.top - setup.margin.bottom;
@@ -87,6 +119,7 @@ export const paperStylesheet = (paper) => {
87
119
  "div.diagram { break-inside: avoid; }\n" +
88
120
  `div.diagram svg { max-height: calc(${paperLength(setup, avail)} - ${headingReserve}rem);` +
89
121
  " width: auto; height: auto; }\n" +
122
+ `nav.doc div.diagram svg { max-height: calc(${paperLength(setup, avail)} - ${docHeadingReserve}rem); }\n` +
90
123
  "}\n";
91
124
  };
92
125
  /* generate a contiguous codepoint range */
@@ -50,34 +50,37 @@
50
50
  let indexed = false
51
51
 
52
52
  /* fill the unit and vocabulary indices once on first use: a unit
53
- is an outermost paragraph-level element within an article, i.e.
54
- one not nested inside another unit (like a paragraph or diagram
55
- in a table cell, which is covered by its table row), where a
56
- diagram is searched by its SVG text labels, joined by spaces,
57
- as its raw text content would run them together, and where the
58
- explicit anchor id of an object heading or table row joins the
59
- searched text */
53
+ is an outermost paragraph-level element within an article or
54
+ the title page abstract (a lone paragraph being the unit itself),
55
+ i.e. one not nested inside another unit (like a paragraph in a
56
+ table cell, covered by its table row), where a diagram is
57
+ searched by its space-joined SVG text labels (its raw text
58
+ content runs them together), and where the explicit anchor id
59
+ of an object heading or table row joins the searched text */
60
60
  const index = () => {
61
61
  if (indexed)
62
62
  return
63
63
  indexed = true
64
- document.querySelectorAll("article").forEach((article) => {
65
- article.querySelectorAll("p, li, tr, pre, blockquote, h1, h2, h3, h4, h5, h6, div.diagram")
66
- .forEach((el) => {
67
- let parent = el.parentElement
68
- while (parent !== null && parent.tagName !== "ARTICLE") {
69
- if (/^(TR|TD|TH|LI|P|BLOCKQUOTE)$/.test(parent.tagName))
70
- return
71
- parent = parent.parentElement
72
- }
73
- const id = (el.dataset.id ?? "").toLowerCase()
74
- const text = ((el.classList.contains("diagram") ?
75
- Array.from(el.querySelectorAll("text")).map((t) => t.textContent ?? "").join(" ") :
76
- (el.textContent ?? "")) + " " + id).toLowerCase()
77
- const words = new Set(text.split(/[^\p{L}\p{N}]+/u).filter((w) => w !== ""))
78
- words.forEach((w) => { vocab.add(w) })
79
- units.push({ el, id, text, words, keep: false })
80
- })
64
+ document.querySelectorAll("article, div.titlepage > .description").forEach((root) => {
65
+ const els = Array.from(root.querySelectorAll(
66
+ "p, li, tr, pre, blockquote, h1, h2, h3, h4, h5, h6, div.diagram"))
67
+ if (root.tagName === "P")
68
+ els.unshift(root)
69
+ els.forEach((el) => {
70
+ let parent = el.parentElement
71
+ while (parent !== null && parent !== root) {
72
+ if (/^(TR|TD|TH|LI|P|BLOCKQUOTE)$/.test(parent.tagName))
73
+ return
74
+ parent = parent.parentElement
75
+ }
76
+ const id = (el.dataset.id ?? "").toLowerCase()
77
+ const text = ((el.classList.contains("diagram") ?
78
+ Array.from(el.querySelectorAll("text")).map((t) => t.textContent ?? "").join(" ") :
79
+ (el.textContent ?? "")) + " " + id).toLowerCase()
80
+ const words = new Set(text.split(/[^\p{L}\p{N}]+/u).filter((w) => w !== ""))
81
+ words.forEach((w) => { vocab.add(w) })
82
+ units.push({ el, id, text, words, keep: false })
83
+ })
81
84
  })
82
85
 
83
86
  /* let a hyperlink whose target the filtering hid leave the
@@ -73,8 +73,8 @@ html.theme-light {
73
73
  --theme-color-specbook-tint-deep: var(--theme-color-accent-28);
74
74
  --theme-color-specbook-accent: var(--theme-color-accent-12);
75
75
  --theme-color-specbook-accent-soft: var(--theme-color-accent-18);
76
- --theme-color-specbook-mark-text: var(--theme-color-search-32);
77
- --theme-color-specbook-mark-back: var(--theme-color-search-16);
76
+ --theme-color-specbook-mark-text: var(--theme-color-signal-32);
77
+ --theme-color-specbook-mark-back: var(--theme-color-signal-16);
78
78
  --theme-color-specbook-field: var(--theme-color-accent-8);
79
79
  --theme-color-specbook-error: #c00;
80
80
  --theme-color-specbook-popup-schema-path: var(--theme-color-accent-30);
@@ -84,6 +84,15 @@ html.theme-light {
84
84
  --theme-color-specbook-progress-done: var(--theme-color-accent-12);
85
85
  --theme-color-specbook-progress-todo: var(--theme-color-accent-31);
86
86
  --gradia-font-family: "Source Sans 3";
87
+ /* the font sizes the diagram layout was sized for (kept in sync
88
+ with "diagramPresets" of specbook-diagram.ts, which feeds the
89
+ layout): an override here keeps that layout and hence has to
90
+ stay close to them */
91
+ --gradia-size-font-node: 30px;
92
+ --gradia-size-font-type: 19px;
93
+ --gradia-size-font-prop: 22px;
94
+ --gradia-size-font-edge: 19px;
95
+ --gradia-size-font-arity: 19px;
87
96
  --gradia-color-node-regular-name: var(--theme-color-accent-12);
88
97
  --gradia-color-node-regular-box: var(--theme-color-accent-30);
89
98
  --gradia-color-node-regular-border: var(--theme-color-accent-26);
@@ -93,6 +102,9 @@ html.theme-light {
93
102
  --gradia-color-node-ghost-name: var(--theme-color-base-22);
94
103
  --gradia-color-node-ghost-box: var(--theme-color-base-31);
95
104
  --gradia-color-node-ghost-border: var(--theme-color-base-24);
105
+ --gradia-color-node-self-name: var(--theme-color-base-14);
106
+ --gradia-color-node-self-box: var(--theme-color-base-28);
107
+ --gradia-color-node-self-border: var(--theme-color-base-18);
96
108
  --gradia-color-group-name: var(--theme-color-accent-18);
97
109
  --gradia-color-group-box: var(--theme-color-accent-32);
98
110
  --gradia-color-group-border: var(--theme-color-accent-26);
@@ -117,27 +129,30 @@ html.theme-light {
117
129
  --theme-color-specbook-tint: var(--theme-color-accent-1);
118
130
  --theme-color-specbook-tint-stripe: var(--theme-color-accent-3);
119
131
  --theme-color-specbook-tint-deep: var(--theme-color-accent-5);
120
- --theme-color-specbook-accent: var(--theme-color-accent-24);
132
+ --theme-color-specbook-accent: var(--theme-color-accent-22);
121
133
  --theme-color-specbook-accent-soft: var(--theme-color-accent-16);
122
- --theme-color-specbook-mark-text: var(--theme-color-search-32);
123
- --theme-color-specbook-mark-back: var(--theme-color-search-4);
134
+ --theme-color-specbook-mark-text: var(--theme-color-signal-32);
135
+ --theme-color-specbook-mark-back: var(--theme-color-signal-4);
124
136
  --theme-color-specbook-field: var(--theme-color-accent-16);
125
137
  --theme-color-specbook-error: #c00;
126
138
  --theme-color-specbook-popup-schema-path: var(--theme-color-accent-4);
127
139
  --theme-color-specbook-popup-schema-desc: var(--theme-color-base-7);
128
- --theme-color-specbook-popup-spec-path: var(--theme-color-accent-24);
140
+ --theme-color-specbook-popup-spec-path: var(--theme-color-accent-22);
129
141
  --theme-color-specbook-popup-spec-desc: var(--theme-color-base-29);
130
- --theme-color-specbook-progress-done: var(--theme-color-accent-24);
142
+ --theme-color-specbook-progress-done: var(--theme-color-accent-22);
131
143
  --theme-color-specbook-progress-todo: var(--theme-color-accent-4);
132
144
  --gradia-color-node-regular-name: var(--theme-color-accent-21);
133
145
  --gradia-color-node-regular-box: var(--theme-color-accent-3);
134
146
  --gradia-color-node-regular-border: var(--theme-color-accent-7);
135
147
  --gradia-color-node-primary-name: var(--theme-color-base-1);
136
- --gradia-color-node-primary-box: var(--theme-color-accent-21);
137
- --gradia-color-node-primary-border: var(--theme-color-accent-25);
148
+ --gradia-color-node-primary-box: var(--theme-color-accent-20);
149
+ --gradia-color-node-primary-border: var(--theme-color-accent-24);
138
150
  --gradia-color-node-ghost-name: var(--theme-color-base-24);
139
151
  --gradia-color-node-ghost-box: var(--theme-color-base-12);
140
152
  --gradia-color-node-ghost-border: var(--theme-color-base-16);
153
+ --gradia-color-node-self-name: var(--theme-color-base-28);
154
+ --gradia-color-node-self-box: var(--theme-color-base-15);
155
+ --gradia-color-node-self-border: var(--theme-color-base-22);
141
156
  --gradia-color-group-name: var(--theme-color-accent-15);
142
157
  --gradia-color-group-box: var(--theme-color-accent-1);
143
158
  --gradia-color-group-border: var(--theme-color-accent-7);
@@ -413,12 +428,13 @@ div.info-popup.spec a.link-compact::before {
413
428
  /* the folding tab (screen-only): the tab riding right of the brand
414
429
  bar between the search tab and the table of contents tab (as wide
415
430
  as those, fixed independent of the content scrolling), showing a
416
- fold icon (drawn like the search icon), which slides the two
431
+ fold icon (drawn like the search icon), which slides the
417
432
  controls out to the right (and back in), where the tab follows the
418
433
  bar when the table of contents panel shifts it aside; the controls
419
- fold and unfold all diagrams and all over-long cell texts of the
420
- content at once and carry, like the fold icon itself, the mark
421
- colors in a rounded box while anything of their kind is folded */
434
+ fold and unfold all diagrams of a type, all diagrams from a nesting
435
+ level on, and all over-long cell texts of the content at once and
436
+ carry, like the fold icon itself, the mark colors in a rounded box
437
+ while anything of their kind is folded */
422
438
  div.fold-switch {
423
439
  position: fixed;
424
440
  top: 10.66rem;
@@ -465,7 +481,7 @@ div.fold-switch div.fold-controls {
465
481
  transition: max-width 0.3s ease, padding 0.3s ease;
466
482
  }
467
483
  div.fold-switch.open div.fold-controls {
468
- max-width: 6rem;
484
+ max-width: 15rem;
469
485
  padding: 0 0.5rem 0 0.2rem;
470
486
  }
471
487
  div.fold-switch div.fold-controls > div {
@@ -1155,10 +1171,18 @@ table.objects table.chunks th:not(.property-name) {
1155
1171
  }
1156
1172
  /* the diagram leading the chunks of an object, kept in one piece */
1157
1173
  table.objects table.chunks div.diagram {
1158
- margin: 0.3rem 0;
1174
+ margin: 0.15rem 0;
1159
1175
  page-break-inside: avoid;
1160
1176
  break-inside: avoid;
1161
1177
  }
1178
+ /* bundle a chunk header row with its value row, so neither the
1179
+ "Diagram" header nor the property names are orphaned at a page end */
1180
+ @media print {
1181
+ table.objects table.chunks tr:has(> th) {
1182
+ page-break-after: avoid;
1183
+ break-after: avoid;
1184
+ }
1185
+ }
1162
1186
  /* keep the cells of empty property values one text line high */
1163
1187
  table.objects td:empty::after {
1164
1188
  content: "\00A0";
@@ -1182,6 +1206,11 @@ table.chunks {
1182
1206
  table-layout: fixed;
1183
1207
  width: 100%;
1184
1208
  }
1209
+ table.objects table.chunks th {
1210
+ font-size: 80%;
1211
+ padding-top: 0.27rem;
1212
+ padding-bottom: 0.27rem;
1213
+ }
1185
1214
  table.objects table.chunks tr td {
1186
1215
  background-color: transparent;
1187
1216
  }
@@ -1298,13 +1327,19 @@ div.diagram {
1298
1327
  margin: 1rem 0;
1299
1328
  }
1300
1329
  div.diagram svg {
1330
+ display: block;
1301
1331
  max-width: 100%;
1302
1332
  height: auto;
1303
- }
1304
- div.diagram svg.hub {
1305
- display: block;
1306
1333
  margin: 0 auto;
1307
1334
  }
1335
+ /* the halo of the diagram edge labels is dropped for print, as Chromium
1336
+ prints a stroked text as the curve outlines of its glyphs instead of
1337
+ as text, which alone made up nearly half of the size of the PDF */
1338
+ @media print {
1339
+ div.diagram svg text {
1340
+ stroke: none;
1341
+ }
1342
+ }
1308
1343
  span.rationale {
1309
1344
  color: var(--theme-color-specbook-muted);
1310
1345
  }
@@ -1579,6 +1614,17 @@ nav.doc h1 {
1579
1614
  color: var(--theme-color-specbook-accent);
1580
1615
  font-weight: normal;
1581
1616
  }
1617
+ /* in print, both front matter headings and the first artifact one
1618
+ open a fresh page, where their top margin is kept after the forced
1619
+ page break */
1620
+ @media print {
1621
+ nav.toc h1,
1622
+ nav.doc h1,
1623
+ article:first-of-type > section:first-child,
1624
+ article:first-of-type > section:first-child > h1 {
1625
+ margin-top: 0;
1626
+ }
1627
+ }
1582
1628
  @keyframes realtime-blink {
1583
1629
  from {
1584
1630
  background-color: var(--theme-color-specbook-accent-soft);
@@ -1,5 +1,6 @@
1
1
  import type { Spec, SpecObject } from "./specbook-format-spec.js";
2
2
  import type { Schema } from "./specbook-format-schema.js";
3
+ import { type ExportOptions } from "./specbook-export-common.js";
3
4
  import type { Verbose } from "./specbook-verbose.js";
4
5
  export type OutlineEntry = {
5
6
  title: string;
@@ -9,4 +10,4 @@ export type OutlineEntry = {
9
10
  export declare const htmlOutline: (specification: Spec, config?: Schema) => OutlineEntry[];
10
11
  export declare const titlePageObject: (specification: Spec) => SpecObject | undefined;
11
12
  export declare const renderPlaceholder: (message: string) => string;
12
- export declare const renderHtml: (specification: Spec, config?: Schema, tocPages?: Map<string, number>, css?: string, realtime?: boolean, verbose?: Verbose) => Promise<string>;
13
+ export declare const renderHtml: (specification: Spec, config?: Schema, tocPages?: Map<string, number>, css?: string, options?: ExportOptions, verbose?: Verbose, print?: boolean) => Promise<string>;