@rse/specbook 0.9.5 → 0.9.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.
- package/README.md +58 -328
- package/dst/specbook-api.d.ts +13 -20
- package/dst/specbook-api.js +28 -25
- package/dst/specbook-cli.js +34 -50
- package/dst/specbook-cmd-describe.d.ts +5 -2
- package/dst/specbook-cmd-describe.js +32 -91
- package/dst/specbook-cmd-export.d.ts +3 -3
- package/dst/specbook-cmd-export.js +4 -3
- package/dst/specbook-cmd-init.d.ts +2 -2
- package/dst/specbook-cmd-init.js +35 -13
- package/dst/specbook-cmd-lint.d.ts +6 -6
- package/dst/specbook-cmd-lint.js +36 -23
- package/dst/specbook-config.d.ts +2 -2
- package/dst/specbook-config.js +2 -2
- package/dst/specbook-diagram.d.ts +4 -4
- package/dst/specbook-diagram.js +21 -15
- package/dst/specbook-export-ast.d.ts +3 -3
- package/dst/specbook-export-common.d.ts +10 -9
- package/dst/specbook-export-common.js +23 -9
- package/dst/specbook-export-html.css +13 -3
- package/dst/specbook-export-html.d.ts +4 -4
- package/dst/specbook-export-html.js +61 -32
- package/dst/specbook-export-logo-dark.svg +35 -0
- package/dst/specbook-export-logo-light.svg +35 -0
- package/dst/specbook-export-md.d.ts +3 -3
- package/dst/{specbook-struct-schema.d.ts → specbook-format-schema.d.ts} +3 -2
- package/dst/{specbook-struct-schema.js → specbook-format-schema.js} +2 -1
- package/dst/specbook-format-spec.d.ts +31 -0
- package/dst/{specbook-struct-spec.js → specbook-format-spec.js} +10 -10
- package/dst/specbook-format.md +734 -0
- package/dst/specbook-link.d.ts +2 -2
- package/dst/specbook-mcp.d.ts +2 -1
- package/dst/specbook-mcp.js +13 -52
- package/dst/specbook-parse-common.d.ts +5 -3
- package/dst/specbook-parse-common.js +8 -0
- package/dst/specbook-parse-semantic.d.ts +4 -4
- package/dst/specbook-parse-semantic.js +4 -65
- package/dst/specbook-parse-syntax.d.ts +2 -2
- package/dst/specbook-parse-syntax.js +18 -14
- package/dst/specbook-parse.d.ts +4 -4
- package/dst/specbook-parse.js +3 -3
- package/dst/specbook-verbose.d.ts +2 -0
- package/dst/specbook-verbose.js +16 -0
- package/package.json +3 -7
- package/dst/specbook-cmd-edit.d.ts +0 -9
- package/dst/specbook-cmd-edit.js +0 -74
- package/dst/specbook-cmd-import.d.ts +0 -9
- package/dst/specbook-cmd-import.js +0 -78
- package/dst/specbook-export-logo.svg +0 -35
- package/dst/specbook-llm.d.ts +0 -13
- package/dst/specbook-llm.js +0 -51
- package/dst/specbook-struct-spec.d.ts +0 -31
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Spec, SpecObject } from "./specbook-format-spec.js";
|
|
2
2
|
export declare const escapeHtml: (text: string) => string;
|
|
3
3
|
export declare const stylesheet: () => string;
|
|
4
4
|
export declare const searchScript: () => string;
|
|
5
|
-
export declare const fallbackLogo: () => string;
|
|
5
|
+
export declare const fallbackLogo: (theme: string) => string;
|
|
6
6
|
export declare const isTitleObject: (object: SpecObject) => boolean;
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
9
|
-
export declare const
|
|
10
|
-
export declare const
|
|
7
|
+
export declare const titleObject: (specification: Spec) => SpecObject | undefined;
|
|
8
|
+
export declare const documentLang: (specification: Spec) => string | undefined;
|
|
9
|
+
export declare const documentCharset: (specification: Spec) => string | undefined;
|
|
10
|
+
export declare const documentThemeStyle: (specification: Spec) => string | undefined;
|
|
11
|
+
export declare const documentThemeTone: (specification: Spec) => string | undefined;
|
|
11
12
|
export type PaperSetup = {
|
|
12
13
|
unit: "mm" | "in";
|
|
13
14
|
height: number;
|
|
@@ -21,12 +22,12 @@ export type PaperSetup = {
|
|
|
21
22
|
export declare const paperSizeDefault = "A4";
|
|
22
23
|
export declare const paperSetup: (paper: string) => PaperSetup;
|
|
23
24
|
export declare const paperLength: (setup: PaperSetup, value: number) => string;
|
|
24
|
-
export declare const documentPaperSize: (specification:
|
|
25
|
+
export declare const documentPaperSize: (specification: Spec) => string;
|
|
25
26
|
export declare const paperStylesheet: (paper: string) => string;
|
|
26
27
|
export declare const charsetCodepoints: (charset: string) => number[] | undefined;
|
|
27
28
|
export declare const subsetStylesheet: (charset?: string) => Promise<string>;
|
|
28
|
-
export declare const documentTitle: (specification:
|
|
29
|
+
export declare const documentTitle: (specification: Spec) => {
|
|
29
30
|
title: string;
|
|
30
31
|
subtitle?: string;
|
|
31
32
|
};
|
|
32
|
-
export declare const documentLogo: (specification:
|
|
33
|
+
export declare const documentLogo: (specification: Spec) => string;
|
|
@@ -14,13 +14,26 @@ export const escapeHtml = (text) => text.replace(/&/g, "&").replace(/</g, "&
|
|
|
14
14
|
export const stylesheet = () => fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "specbook-export-html.css"), "utf8");
|
|
15
15
|
/* provide the build-time bundled client-side fuzzy search script */
|
|
16
16
|
export const searchScript = () => fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), "specbook-export-html-search.js"), "utf8");
|
|
17
|
-
/* provide the build-time bundled fallback logo of
|
|
18
|
-
(as a self-contained data: URL, to keep its styles isolated) */
|
|
19
|
-
export const fallbackLogo = () => "data:image/svg+xml;base64," + fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)),
|
|
17
|
+
/* provide a theme variant of the build-time bundled fallback logo of
|
|
18
|
+
SpecBook itself (as a self-contained data: URL, to keep its styles isolated) */
|
|
19
|
+
export const fallbackLogo = (theme) => "data:image/svg+xml;base64," + fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), `specbook-export-logo-${theme}.svg`)).toString("base64");
|
|
20
20
|
/* check whether an object is the specification title object */
|
|
21
21
|
export const isTitleObject = (object) => object.kind === "META" && object.name.toUpperCase() === "TITLE";
|
|
22
|
-
/* determine the title object of the specification
|
|
23
|
-
|
|
22
|
+
/* determine the title object of the specification, searched at any
|
|
23
|
+
nesting level in document order, where the first match wins */
|
|
24
|
+
export const titleObject = (specification) => {
|
|
25
|
+
const search = (objects) => {
|
|
26
|
+
for (const object of objects) {
|
|
27
|
+
if (isTitleObject(object))
|
|
28
|
+
return object;
|
|
29
|
+
const found = search(object.childs);
|
|
30
|
+
if (found !== undefined)
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
return search(specification.artifacts.flatMap((artifact) => artifact.objects));
|
|
36
|
+
};
|
|
24
37
|
/* determine a property value of the title object */
|
|
25
38
|
const titleProperty = (specification, name) => titleObject(specification)?.properties.find((property) => property.key === name)?.value;
|
|
26
39
|
/* determine the document language (LANG) from the title object */
|
|
@@ -142,14 +155,15 @@ export const documentTitle = (specification) => ({
|
|
|
142
155
|
subtitle: titleProperty(specification, "SUBTITLE")
|
|
143
156
|
});
|
|
144
157
|
/* determine the document logo as a self-contained data: URL, taken from the
|
|
145
|
-
embedded image of the LOGO property of the title object
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
first embedded image of the LOGO property of the title object -- the light
|
|
159
|
+
variant of a "{theme}" reference -- and falling back onto the built-in
|
|
160
|
+
SpecBook logo (for use in isolated rendering contexts, like the print
|
|
161
|
+
header/footer, which load no external resources and are always light) */
|
|
148
162
|
export const documentLogo = (specification) => {
|
|
149
163
|
const content = titleObject(specification)
|
|
150
164
|
?.properties.find((property) => property.key === "LOGO")?.embedding?.[0];
|
|
151
165
|
if (content === undefined)
|
|
152
|
-
return fallbackLogo();
|
|
166
|
+
return fallbackLogo("light");
|
|
153
167
|
return content.startsWith("data:") ? content :
|
|
154
168
|
`data:image/svg+xml;base64,${Buffer.from(content, "utf8").toString("base64")}`;
|
|
155
169
|
};
|
|
@@ -94,7 +94,7 @@ html.theme-dark { --theme-color-specbook-background: var(--theme-co
|
|
|
94
94
|
--gradia-color-group-name: var(--theme-color-accent-15);
|
|
95
95
|
--gradia-color-group-box: var(--theme-color-accent-1);
|
|
96
96
|
--gradia-color-group-border: var(--theme-color-accent-7);
|
|
97
|
-
--gradia-color-edge-line: var(--theme-color-base-
|
|
97
|
+
--gradia-color-edge-line: var(--theme-color-base-20);
|
|
98
98
|
--gradia-color-edge-name: var(--theme-color-base-29);
|
|
99
99
|
--gradia-color-edge-arity: var(--theme-color-base-29);
|
|
100
100
|
--gradia-color-edge-halo: var(--theme-color-specbook-background); }
|
|
@@ -222,13 +222,24 @@ div.description li { margin: 0.1rem 0; }
|
|
|
222
222
|
div.embedding { margin: 0.5rem 0; }
|
|
223
223
|
div.embedding img,
|
|
224
224
|
div.embedding svg { max-width: 100%; height: auto; }
|
|
225
|
+
|
|
226
|
+
/* the two theme variants of a "{theme}" image embedding: the containers
|
|
227
|
+
are layout-neutral and only the variant matching the active theme stays
|
|
228
|
+
visible, where print (like the theme mapping above) always falls back
|
|
229
|
+
onto the light variant */
|
|
230
|
+
span.theme-light-only { display: contents; }
|
|
231
|
+
span.theme-dark-only { display: none; }
|
|
232
|
+
@media screen {
|
|
233
|
+
html.theme-dark span.theme-light-only { display: none; }
|
|
234
|
+
html.theme-dark span.theme-dark-only { display: contents; }
|
|
235
|
+
}
|
|
236
|
+
|
|
225
237
|
div.diagram { margin: 1.0rem 0; }
|
|
226
238
|
div.diagram svg { max-width: 100%; height: auto; }
|
|
227
239
|
span.rationale { color: var(--theme-color-specbook-muted); }
|
|
228
240
|
p.rationale { color: var(--theme-color-specbook-muted); margin: 0.5rem 0 0 0; }
|
|
229
241
|
span.keyword { font-weight: bold; }
|
|
230
242
|
code { font-family: "Source Code Pro", monospace; font-size: 1.0em; letter-spacing: -0.030rem; }
|
|
231
|
-
div.meta { color: var(--theme-color-specbook-muted); font-size: 0.8rem; }
|
|
232
243
|
|
|
233
244
|
div.titlepage { text-align: center; margin: 6rem 0 4rem 0;
|
|
234
245
|
page-break-after: always; }
|
|
@@ -261,7 +272,6 @@ div.search { display: none; }
|
|
|
261
272
|
/* the search result filtering and matched word highlighting */
|
|
262
273
|
.search-hide { display: none; }
|
|
263
274
|
body.searching nav.toc,
|
|
264
|
-
body.searching div.meta,
|
|
265
275
|
body.searching div.embedding { display: none; }
|
|
266
276
|
mark.search-hit { color: var(--theme-color-specbook-mark-text);
|
|
267
277
|
background-color: var(--theme-color-specbook-mark-back);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
1
|
+
import type { Spec } from "./specbook-format-spec.js";
|
|
2
|
+
import type { Schema } from "./specbook-format-schema.js";
|
|
3
3
|
export type OutlineEntry = {
|
|
4
4
|
title: string;
|
|
5
5
|
anchor: string;
|
|
6
6
|
childs: OutlineEntry[];
|
|
7
7
|
};
|
|
8
|
-
export declare const htmlOutline: (specification:
|
|
9
|
-
export declare const renderHtml: (specification:
|
|
8
|
+
export declare const htmlOutline: (specification: Spec, config?: Schema) => OutlineEntry[];
|
|
9
|
+
export declare const renderHtml: (specification: Spec, config?: Schema, tocPages?: Map<string, number>, css?: string) => Promise<string>;
|
|
@@ -11,8 +11,8 @@ import textframe from "textframe";
|
|
|
11
11
|
import { Gradia } from "@rse/gradia";
|
|
12
12
|
import { buildLinkIndex, resolveUnique, expandReferences, anchorPaths } from "./specbook-link.js";
|
|
13
13
|
import { compileValueExpr, splitItems } from "./specbook-parse-value.js";
|
|
14
|
-
import { embeddingRegex, embeddingMimeType } from "./specbook-parse-common.js";
|
|
15
|
-
import { escapeHtml, stylesheet, searchScript, fallbackLogo, isTitleObject, documentTitle, documentLang, documentThemeStyle } from "./specbook-export-common.js";
|
|
14
|
+
import { embeddingRegex, embeddingMimeType, embeddingThemes, embeddingVariants } from "./specbook-parse-common.js";
|
|
15
|
+
import { escapeHtml, stylesheet, searchScript, fallbackLogo, isTitleObject, titleObject, documentTitle, documentLang, documentThemeStyle } from "./specbook-export-common.js";
|
|
16
16
|
import { specDiagrams } from "./specbook-diagram.js";
|
|
17
17
|
/* ==== Templates ==== */
|
|
18
18
|
/* the built-in Nunjucks templates for the HTML export */
|
|
@@ -51,7 +51,6 @@ const templates = {
|
|
|
51
51
|
<body>
|
|
52
52
|
<div class="theme-switch" onclick="themeSwitch()" title="switch color theme">◐</div>
|
|
53
53
|
{{ Document.titlepage }}
|
|
54
|
-
{% if not Document.titlepage %}<div class="meta">created: {{ Document.created }}, modified: {{ Document.modified }}</div>{% endif %}
|
|
55
54
|
{{ Document.toc }}
|
|
56
55
|
{{ Document.artifacts }}
|
|
57
56
|
{% if Document.search %}<script>{{ Document.search }}</script>{% endif %}
|
|
@@ -242,16 +241,35 @@ const isBlock = (text) => {
|
|
|
242
241
|
const tokens = marked.lexer(text).filter((token) => token.type !== "space");
|
|
243
242
|
return tokens.length > 0 && !(tokens.length === 1 && tokens[0].type === "paragraph");
|
|
244
243
|
};
|
|
245
|
-
/* render
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
/* render a single embedded image file into HTML (SVG inlined as-is,
|
|
245
|
+
PNG/JPEG placed onto an <img> tag) */
|
|
246
|
+
const renderImage = (content, alt) => content.startsWith("data:") ?
|
|
247
|
+
`<img src="${content}" alt="${escapeHtml(alt)}"/>` :
|
|
248
|
+
content.replace(/^\s*<\?xml[^>]*\?>\s*(?:<!DOCTYPE[^>]*>\s*)?/i, "");
|
|
249
|
+
/* wrap the theme variants of an image into their layout-neutral theme
|
|
250
|
+
containers, of which the stylesheet shows just the one matching the
|
|
251
|
+
color theme currently active in the document */
|
|
252
|
+
const renderThemed = (images) => images.map((image, i) => `<span class="theme-${embeddingThemes[i]}-only">${image}</span>`).join("");
|
|
253
|
+
/* render the embedded image files of a text into HTML, taking the image
|
|
254
|
+
alternate texts from the corresponding "" markups and
|
|
255
|
+
pairing up the consecutive theme variants of a "{theme}" markup */
|
|
248
256
|
const renderEmbeddings = (text, embedding) => {
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
const result = new Array();
|
|
258
|
+
let i = 0;
|
|
259
|
+
for (const m of text.matchAll(embeddingRegex)) {
|
|
260
|
+
const reference = m[2].trim();
|
|
261
|
+
if (embeddingMimeType(reference) === undefined)
|
|
262
|
+
continue;
|
|
263
|
+
const variants = embeddingVariants(reference);
|
|
264
|
+
const images = embedding.slice(i, i + variants.length)
|
|
265
|
+
.map((content) => renderImage(content, m[1].trim()));
|
|
266
|
+
i += variants.length;
|
|
267
|
+
if (variants.length > 1 && images.length === variants.length)
|
|
268
|
+
result.push(renderThemed(images));
|
|
269
|
+
else
|
|
270
|
+
result.push(...images);
|
|
271
|
+
}
|
|
272
|
+
return result;
|
|
255
273
|
};
|
|
256
274
|
/* render a description into HTML, expanding its inline Markdown and
|
|
257
275
|
moving the file embeddings to the end of the description */
|
|
@@ -312,13 +330,10 @@ const collectSchemas = (specification, config) => {
|
|
|
312
330
|
}
|
|
313
331
|
return result;
|
|
314
332
|
};
|
|
315
|
-
/* strip a trailing parenthesized annotation from a
|
|
316
|
-
property key (e.g. "WHEN (Context)") */
|
|
317
|
-
const plainKey = (key) => key.replace(/\s*\([^)]*\)\s*$/, "").trim();
|
|
318
333
|
/* render a property value, badging the individual members of an
|
|
319
334
|
"enum(...)" (a single member) or "tags(...)" (a member set) value */
|
|
320
335
|
const inlineValue = (kind, key, value) => {
|
|
321
|
-
const member = members?.get(`${kind}${
|
|
336
|
+
const member = members?.get(`${kind}${key}`);
|
|
322
337
|
if (member === undefined)
|
|
323
338
|
return inline(value);
|
|
324
339
|
const items = member === "tags" ? splitItems(value) : [value.trim()];
|
|
@@ -340,7 +355,7 @@ const effectiveProperties = (object) => {
|
|
|
340
355
|
if (schema?.format?.withUnusedProps !== true)
|
|
341
356
|
return object.properties;
|
|
342
357
|
const merged = (schema.props ?? []).flatMap((prop) => {
|
|
343
|
-
const present = object.properties.filter((property) =>
|
|
358
|
+
const present = object.properties.filter((property) => property.key === prop.name);
|
|
344
359
|
return present.length > 0 ? present : [{ key: prop.name, value: "" }];
|
|
345
360
|
});
|
|
346
361
|
return [...merged, ...object.properties.filter((property) => !merged.includes(property))];
|
|
@@ -357,24 +372,29 @@ const tableShape = (childs) => {
|
|
|
357
372
|
const schema = schemas?.get(childs[0]);
|
|
358
373
|
if (schema?.format?.withUnusedProps === true) {
|
|
359
374
|
const merged = (schema.props ?? []).flatMap((prop) => {
|
|
360
|
-
const present = keys.filter((key) =>
|
|
375
|
+
const present = keys.filter((key) => key === prop.name);
|
|
361
376
|
return present.length > 0 ? present : [prop.name];
|
|
362
377
|
});
|
|
363
378
|
keys = [...merged, ...keys.filter((key) => !merged.includes(key))];
|
|
364
379
|
}
|
|
365
380
|
return { keys, desc: childs.some((child) => child.description !== undefined || child.childs.length > 0) };
|
|
366
381
|
};
|
|
382
|
+
/* provide the childs of an object taking part in the regular document
|
|
383
|
+
flow: a nested title object leaves the flow, as it is rendered as the
|
|
384
|
+
title page instead (a top-level one instead removes its whole artifact) */
|
|
385
|
+
const flowChilds = (object) => object.childs.filter((child) => !isTitleObject(child));
|
|
367
386
|
/* decide whether the childs of an object collapse into the concise
|
|
368
387
|
(tabular) rendering: an explicit "format" type wins, "auto" collapses
|
|
369
388
|
the deepest level only, and inside an already concise rendering
|
|
370
389
|
context "auto" childs implicitly stay concise, too */
|
|
371
390
|
const conciseChilds = (object, schemaMap, concise) => {
|
|
372
|
-
|
|
391
|
+
const childs = flowChilds(object);
|
|
392
|
+
if (childs.length === 0)
|
|
373
393
|
return false;
|
|
374
394
|
const type = schemaMap?.get(object)?.format?.type ?? "auto";
|
|
375
395
|
if (type !== "auto")
|
|
376
396
|
return type === "concise";
|
|
377
|
-
return concise ||
|
|
397
|
+
return concise || childs.every((child) => child.childs.length === 0);
|
|
378
398
|
};
|
|
379
399
|
/* group the childs of an object by their kind, preserving order */
|
|
380
400
|
const groupChilds = (childs) => {
|
|
@@ -394,10 +414,11 @@ const groupChilds = (childs) => {
|
|
|
394
414
|
ones as regular nested object renderings pressed into the cell) */
|
|
395
415
|
const renderCell = (child) => {
|
|
396
416
|
let html = child.description !== undefined ? renderDescription(child.description) : "";
|
|
397
|
-
|
|
417
|
+
const childs = flowChilds(child);
|
|
418
|
+
if (childs.length > 0)
|
|
398
419
|
html += conciseChilds(child, schemas, true) ?
|
|
399
|
-
groupChilds(
|
|
400
|
-
|
|
420
|
+
groupChilds(childs).map((group) => renderTable(group, maxColumnsOf(child))).join("") :
|
|
421
|
+
childs.map((sub) => renderObject(sub, 6, true)).join("");
|
|
401
422
|
return html;
|
|
402
423
|
};
|
|
403
424
|
/* render a single-kind group of childs into one compact table:
|
|
@@ -474,8 +495,8 @@ const renderObject = (object, level, concise) => {
|
|
|
474
495
|
description: object.description !== undefined ?
|
|
475
496
|
safe(renderDescription(object.description)) : "",
|
|
476
497
|
childs: conciseChilds(object, schemas, concise) ?
|
|
477
|
-
safe(groupChilds(object
|
|
478
|
-
safe(object.
|
|
498
|
+
safe(groupChilds(flowChilds(object)).map((group) => renderTable(group, maxColumnsOf(object))).join("")) :
|
|
499
|
+
safe(flowChilds(object).map((child) => renderObject(child, level + 1, concise)).join(""))
|
|
479
500
|
} });
|
|
480
501
|
};
|
|
481
502
|
/* render the title object into a title page */
|
|
@@ -489,11 +510,13 @@ const renderTitlePage = (object, created, modified) => {
|
|
|
489
510
|
"LANG", "CHARSET", "PAPER-SIZE", "THEME-STYLE", "THEME-TONE"].includes(property.key));
|
|
490
511
|
/* the logo is rendered above the title, from the embedded image content
|
|
491
512
|
of the LOGO property or, for a non-embeddable reference, as its inline
|
|
492
|
-
Markdown; without a LOGO property the built-in SpecBook logo is used
|
|
513
|
+
Markdown; without a LOGO property the built-in SpecBook logo is used,
|
|
514
|
+
in both its theme variants */
|
|
493
515
|
const logo = object.properties.find((property) => property.key === "LOGO");
|
|
494
516
|
const image = logo !== undefined ? renderEmbeddings(logo.value, logo.embedding ?? []) : [];
|
|
495
517
|
return render("TitlePage", { TitlePage: {
|
|
496
|
-
logo: logo === undefined ?
|
|
518
|
+
logo: logo === undefined ?
|
|
519
|
+
safe(renderThemed(embeddingThemes.map((theme) => `<img src="${fallbackLogo(theme)}" alt="SpecBook"/>`))) :
|
|
497
520
|
(image.length > 0 ? safe(image.join("")) : inline(logo.value)),
|
|
498
521
|
title: inline(prop("TITLE") ?? object.name),
|
|
499
522
|
subtitle: inlineProp("SUBTITLE"),
|
|
@@ -518,13 +541,22 @@ export const htmlOutline = (specification, config) => {
|
|
|
518
541
|
const entry = (object) => ({
|
|
519
542
|
title: (object.kind !== "" ? `${object.kind}: ` : "") + object.name,
|
|
520
543
|
anchor: paths.get(object) ?? object.id,
|
|
521
|
-
childs: conciseChilds(object, schemaMap, false) ? [] : object.
|
|
544
|
+
childs: conciseChilds(object, schemaMap, false) ? [] : flowChilds(object).map(entry)
|
|
522
545
|
});
|
|
523
546
|
return specification.artifacts
|
|
524
547
|
.filter((artifact) => !artifact.objects.some(isTitleObject))
|
|
525
548
|
.flatMap((artifact) => artifact.objects)
|
|
526
549
|
.map(entry);
|
|
527
550
|
};
|
|
551
|
+
/* determine the object a title page is rendered from: the title object
|
|
552
|
+
has to carry a non-empty "TITLE" property, as without it there is
|
|
553
|
+
nothing to render a title page from (the object stays suppressed in
|
|
554
|
+
the regular flow nevertheless) */
|
|
555
|
+
const titlePageObject = (specification) => {
|
|
556
|
+
const object = titleObject(specification);
|
|
557
|
+
const title = object?.properties.find((property) => property.key === "TITLE");
|
|
558
|
+
return title !== undefined && title.value.trim() !== "" ? object : undefined;
|
|
559
|
+
};
|
|
528
560
|
/* render the entire specification into a self-contained HTML document,
|
|
529
561
|
with the build-time pre-assembled stylesheet embedded inline, the
|
|
530
562
|
artifact timestamps aggregated into min(Created)/max(Modified), and
|
|
@@ -577,8 +609,7 @@ export const renderHtml = async (specification, config, tocPages, css) => {
|
|
|
577
609
|
const created = new Date(Math.min(...specification.artifacts.map((artifact) => artifact.created.getTime())));
|
|
578
610
|
const modified = new Date(Math.max(...specification.artifacts.map((artifact) => artifact.modified.getTime())));
|
|
579
611
|
/* a "META: Title" object becomes the title page and leaves the regular flow */
|
|
580
|
-
const title = specification
|
|
581
|
-
.flatMap((artifact) => artifact.objects).find(isTitleObject);
|
|
612
|
+
const title = titlePageObject(specification);
|
|
582
613
|
const artifacts = specification.artifacts
|
|
583
614
|
.filter((artifact) => !artifact.objects.some(isTitleObject));
|
|
584
615
|
const entries = artifacts.flatMap((artifact) => artifact.objects)
|
|
@@ -589,8 +620,6 @@ export const renderHtml = async (specification, config, tocPages, css) => {
|
|
|
589
620
|
lang,
|
|
590
621
|
theme: documentThemeStyle(specification)?.toLowerCase(),
|
|
591
622
|
css: safe(css ?? stylesheet()),
|
|
592
|
-
created: created.toISOString(),
|
|
593
|
-
modified: modified.toISOString(),
|
|
594
623
|
titlepage: title !== undefined ?
|
|
595
624
|
safe(renderTitlePage(title, created.toISOString().slice(0, 10), modified.toISOString().slice(0, 10))) : "",
|
|
596
625
|
search: title !== undefined ? safe(searchScript()) : "",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 260.8 110.5">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1 {
|
|
11
|
+
fill: #69c;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.st2 {
|
|
15
|
+
fill: #9fc9eb;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</defs>
|
|
19
|
+
<g>
|
|
20
|
+
<rect class="st0" x="128" y="5.9" width="297.7" height="245.4"/>
|
|
21
|
+
<path class="st2" d="M132.6,41.3c2.6,1.3,6.7,2.7,10.8,2.7s6.8-1.9,6.8-4.7-2-4.2-7.2-6c-7.2-2.6-11.9-6.5-11.9-12.8s6.2-12.9,16.3-12.9,8.4,1,11,2.2l-2.2,7.8c-1.7-.8-4.8-2-9-2s-6.3,2-6.3,4.2,2.4,4,8,6.1c7.6,2.8,11.1,6.8,11.1,12.9s-5.5,13.4-17.3,13.4-9.8-1.3-12.2-2.7l2-8Z"/>
|
|
22
|
+
<path class="st2" d="M163.3,30.5c0-4.1-.1-7.6-.3-10.5h8.4l.4,4.4h.1c2.3-3.3,5.9-5.1,10.4-5.1,6.8,0,13,6,13,15.9s-7.2,16.8-14.2,16.8-6.7-1.5-8.1-3.6h-.1v15.6h-9.7V30.5ZM173,37.9c0,.8,0,1.4.2,2,.6,2.6,2.9,4.6,5.7,4.6,4.2,0,6.7-3.5,6.7-8.8s-2.2-8.8-6.6-8.8-5.2,2-5.8,4.9c-.1.5-.2,1.2-.2,1.7v4.3Z"/>
|
|
23
|
+
<path class="st2" d="M206,38.8c.3,4,4.3,6,8.8,6s6-.4,8.6-1.3l1.3,6.6c-3.2,1.3-7.1,2-11.3,2-10.6,0-16.7-6.1-16.7-15.9s4.9-16.7,15.8-16.7,14,7.9,14,15.6-.2,3.1-.3,3.8h-20.2ZM217.5,32.1c0-2.4-1-6.3-5.5-6.3s-5.8,3.8-6,6.3h11.5Z"/>
|
|
24
|
+
<path class="st2" d="M252.6,50.5c-1.7.8-5,1.5-8.7,1.5-10.1,0-16.6-6.2-16.6-16s6.3-16.6,17.9-16.6,5.4.4,7.4,1.2l-1.5,7.2c-1.2-.5-2.9-1-5.4-1-5.1,0-8.4,3.6-8.4,8.8,0,5.7,3.8,8.8,8.6,8.8s4.1-.4,5.6-1l1.2,7.2Z"/>
|
|
25
|
+
</g>
|
|
26
|
+
<path class="st2" d="M25.4,18.5h0c-1,0-2,.4-2.7,1.1-.7.7-1.1,1.7-1.1,2.8v58.4c0,2.1,1.7,3.9,3.9,3.9,9.1,0,24.3,1.9,34.7,12.9v-61.1c0-.7-.2-1.4-.5-2-8.6-13.9-25.1-16-34.2-16Z"/>
|
|
27
|
+
<path class="st2" d="M104.8,80.9V22.4c0-1-.4-2-1.1-2.8-.7-.7-1.7-1.1-2.7-1.1h0c-9.1,0-25.6,2.1-34.2,16-.4.6-.5,1.2-.5,2v61.1c10.5-11,25.7-12.9,34.7-12.9,2.2,0,3.9-1.8,3.9-3.9Z"/>
|
|
28
|
+
<path class="st1" d="M113.7,32h-2.8v48.8c0,5.5-4.5,9.9-9.9,9.9-7.7,0-20.4,1.5-29.4,10,15.5-3.8,31.9-1.3,41.2.8,1.2.3,2.4,0,3.3-.8.9-.7,1.5-1.9,1.5-3v-61.9c0-2.1-1.7-3.9-3.9-3.9Z"/>
|
|
29
|
+
<path class="st1" d="M15.4,80.9v-48.8h-2.8c-2.1,0-3.9,1.7-3.9,3.9v61.9c0,1.2.5,2.3,1.5,3,.9.7,2.1,1,3.3.8,9.3-2.1,25.7-4.6,41.2-.8-9-8.5-21.7-10-29.4-10-5.5,0-9.9-4.5-9.9-9.9Z"/>
|
|
30
|
+
<g>
|
|
31
|
+
<path class="st1" d="M131.7,55.6c2.6-.6,6.8-1,11-1,6,0,9.9,1,12.7,3.4,2.4,1.8,3.9,4.6,3.9,8.2s-3,8.5-7.9,10.2v.2c4.4,1,9.6,4.7,9.6,11.6s-1.6,7.1-4,9.3c-3.2,3-8.4,4.4-16,4.4s-7.3-.3-9.3-.6v-45.7ZM137.7,74.6h5.5c6.3,0,10-3.4,10-7.9s-4.1-7.6-10.2-7.6-4.4.2-5.3.4v15.1ZM137.7,96.9c1.2.2,2.9.3,5,.3,6.2,0,12-2.3,12-9.1s-5.5-9-12-9h-5v17.8Z"/>
|
|
32
|
+
<path class="st1" d="M206.1,66.9c-9.3,0-16.6,6.6-16.6,17.8s0,.2,0,.4h0c-.3,7.2-4.5,12.3-10.1,12.3s-10.2-5.5-10.2-12.8,3.1-13,10.3-13,7,2.6,8.5,5.8c.8-2.1,1.9-4,3.4-5.5-2.8-3.1-6.9-4.9-11.8-4.9-9.3,0-16.6,6.6-16.6,17.8s7,17.2,16,17.2,16.2-5.1,16.6-16.8h0c0-.2,0-.4,0-.6,0-6.4,3.1-13,10.3-13s10,7.2,10,12.8-4.4,13-10.2,13-7.3-2.6-8.9-6.5c-.7,2.3-1.9,4.2-3.3,5.8,2.9,3.4,7.1,5.3,12,5.3,8.1,0,16.6-5.4,16.6-17.8s-6.5-17.2-16.1-17.2Z"/>
|
|
33
|
+
<path class="st1" d="M230.6,83.8h.1c.8-1.2,1.9-2.6,2.9-3.8l9.9-11.5h7.3l-12.9,13.7,14.7,19.7h-7.5l-11.5-16-3.1,3.5v12.6h-6v-49.1h6v31Z"/>
|
|
34
|
+
</g>
|
|
35
|
+
</svg>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 260.8 110.5">
|
|
3
|
+
<!-- Generator: Adobe Illustrator 30.7.0, SVG Export Plug-In . SVG Version: 2.1.4 Build 114) -->
|
|
4
|
+
<defs>
|
|
5
|
+
<style>
|
|
6
|
+
.st0 {
|
|
7
|
+
fill: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.st1 {
|
|
11
|
+
fill: #69c;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.st2 {
|
|
15
|
+
fill: #33669a;
|
|
16
|
+
}
|
|
17
|
+
</style>
|
|
18
|
+
</defs>
|
|
19
|
+
<g>
|
|
20
|
+
<rect class="st0" x="128" y="5.9" width="297.7" height="245.4"/>
|
|
21
|
+
<path class="st2" d="M132.6,41.3c2.6,1.3,6.7,2.7,10.8,2.7s6.8-1.9,6.8-4.7-2-4.2-7.2-6c-7.2-2.6-11.9-6.5-11.9-12.8s6.2-12.9,16.3-12.9,8.4,1,11,2.2l-2.2,7.8c-1.7-.8-4.8-2-9-2s-6.3,2-6.3,4.2,2.4,4,8,6.1c7.6,2.8,11.1,6.8,11.1,12.9s-5.5,13.4-17.3,13.4-9.8-1.3-12.2-2.7l2-8Z"/>
|
|
22
|
+
<path class="st2" d="M163.3,30.5c0-4.1-.1-7.6-.3-10.5h8.4l.4,4.4h.1c2.3-3.3,5.9-5.1,10.4-5.1,6.8,0,13,6,13,15.9s-7.2,16.8-14.2,16.8-6.7-1.5-8.1-3.6h-.1v15.6h-9.7V30.5ZM173,37.9c0,.8,0,1.4.2,2,.6,2.6,2.9,4.6,5.7,4.6,4.2,0,6.7-3.5,6.7-8.8s-2.2-8.8-6.6-8.8-5.2,2-5.8,4.9c-.1.5-.2,1.2-.2,1.7v4.3Z"/>
|
|
23
|
+
<path class="st2" d="M206,38.8c.3,4,4.3,6,8.8,6s6-.4,8.6-1.3l1.3,6.6c-3.2,1.3-7.1,2-11.3,2-10.6,0-16.7-6.1-16.7-15.9s4.9-16.7,15.8-16.7,14,7.9,14,15.6-.2,3.1-.3,3.8h-20.2ZM217.5,32.1c0-2.4-1-6.3-5.5-6.3s-5.8,3.8-6,6.3h11.5Z"/>
|
|
24
|
+
<path class="st2" d="M252.6,50.5c-1.7.8-5,1.5-8.7,1.5-10.1,0-16.6-6.2-16.6-16s6.3-16.6,17.9-16.6,5.4.4,7.4,1.2l-1.5,7.2c-1.2-.5-2.9-1-5.4-1-5.1,0-8.4,3.6-8.4,8.8,0,5.7,3.8,8.8,8.6,8.8s4.1-.4,5.6-1l1.2,7.2Z"/>
|
|
25
|
+
</g>
|
|
26
|
+
<path class="st2" d="M25.4,18.5h0c-1,0-2,.4-2.7,1.1-.7.7-1.1,1.7-1.1,2.8v58.4c0,2.1,1.7,3.9,3.9,3.9,9.1,0,24.3,1.9,34.7,12.9v-61.1c0-.7-.2-1.4-.5-2-8.6-13.9-25.1-16-34.2-16Z"/>
|
|
27
|
+
<path class="st2" d="M104.8,80.9V22.4c0-1-.4-2-1.1-2.8-.7-.7-1.7-1.1-2.7-1.1h0c-9.1,0-25.6,2.1-34.2,16-.4.6-.5,1.2-.5,2v61.1c10.5-11,25.7-12.9,34.7-12.9,2.2,0,3.9-1.8,3.9-3.9Z"/>
|
|
28
|
+
<path class="st1" d="M113.7,32h-2.8v48.8c0,5.5-4.5,9.9-9.9,9.9-7.7,0-20.4,1.5-29.4,10,15.5-3.8,31.9-1.3,41.2.8,1.2.3,2.4,0,3.3-.8.9-.7,1.5-1.9,1.5-3v-61.9c0-2.1-1.7-3.9-3.9-3.9Z"/>
|
|
29
|
+
<path class="st1" d="M15.4,80.9v-48.8h-2.8c-2.1,0-3.9,1.7-3.9,3.9v61.9c0,1.2.5,2.3,1.5,3,.9.7,2.1,1,3.3.8,9.3-2.1,25.7-4.6,41.2-.8-9-8.5-21.7-10-29.4-10-5.5,0-9.9-4.5-9.9-9.9Z"/>
|
|
30
|
+
<g>
|
|
31
|
+
<path class="st1" d="M131.7,55.6c2.6-.6,6.8-1,11-1,6,0,9.9,1,12.7,3.4,2.4,1.8,3.9,4.6,3.9,8.2s-3,8.5-7.9,10.2v.2c4.4,1,9.6,4.7,9.6,11.6s-1.6,7.1-4,9.3c-3.2,3-8.4,4.4-16,4.4s-7.3-.3-9.3-.6v-45.7ZM137.7,74.6h5.5c6.3,0,10-3.4,10-7.9s-4.1-7.6-10.2-7.6-4.4.2-5.3.4v15.1ZM137.7,96.9c1.2.2,2.9.3,5,.3,6.2,0,12-2.3,12-9.1s-5.5-9-12-9h-5v17.8Z"/>
|
|
32
|
+
<path class="st1" d="M206.1,66.9c-9.3,0-16.6,6.6-16.6,17.8s0,.2,0,.4h0c-.3,7.2-4.5,12.3-10.1,12.3s-10.2-5.5-10.2-12.8,3.1-13,10.3-13,7,2.6,8.5,5.8c.8-2.1,1.9-4,3.4-5.5-2.8-3.1-6.9-4.9-11.8-4.9-9.3,0-16.6,6.6-16.6,17.8s7,17.2,16,17.2,16.2-5.1,16.6-16.8h0c0-.2,0-.4,0-.6,0-6.4,3.1-13,10.3-13s10,7.2,10,12.8-4.4,13-10.2,13-7.3-2.6-8.9-6.5c-.7,2.3-1.9,4.2-3.3,5.8,2.9,3.4,7.1,5.3,12,5.3,8.1,0,16.6-5.4,16.6-17.8s-6.5-17.2-16.1-17.2Z"/>
|
|
33
|
+
<path class="st1" d="M230.6,83.8h.1c.8-1.2,1.9-2.6,2.9-3.8l9.9-11.5h7.3l-12.9,13.7,14.7,19.7h-7.5l-11.5-16-3.1,3.5v12.6h-6v-49.1h6v31Z"/>
|
|
34
|
+
</g>
|
|
35
|
+
</svg>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
export declare const renderMarkdown: (specification:
|
|
1
|
+
import type { Spec } from "./specbook-format-spec.js";
|
|
2
|
+
import type { Schema } from "./specbook-format-schema.js";
|
|
3
|
+
export declare const renderMarkdown: (specification: Spec, config?: Schema) => Promise<string>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as v from "valibot";
|
|
2
2
|
import { type Config as GradiaConfig } from "@rse/gradia";
|
|
3
|
-
export type
|
|
3
|
+
export type Schema = SchemaObject[];
|
|
4
4
|
export type SchemaObject = {
|
|
5
5
|
kind: string;
|
|
6
6
|
name?: string;
|
|
@@ -19,6 +19,7 @@ export type SchemaDiagram = {
|
|
|
19
19
|
edges?: string;
|
|
20
20
|
center?: string;
|
|
21
21
|
links?: "props" | "all";
|
|
22
|
+
edgeSource?: string;
|
|
22
23
|
edgeTarget?: string;
|
|
23
24
|
edgeArity?: string;
|
|
24
25
|
hierarchy?: boolean;
|
|
@@ -39,4 +40,4 @@ export type SchemaProperty = {
|
|
|
39
40
|
value?: string;
|
|
40
41
|
optional?: boolean;
|
|
41
42
|
};
|
|
42
|
-
export declare const
|
|
43
|
+
export declare const Schema: v.GenericSchema<Schema>;
|
|
@@ -25,6 +25,7 @@ const SchemaDiagram = v.strictObject({
|
|
|
25
25
|
edges: v.optional(v.string()),
|
|
26
26
|
center: v.optional(v.string()),
|
|
27
27
|
links: v.optional(v.picklist(["props", "all"])),
|
|
28
|
+
edgeSource: v.optional(v.string()),
|
|
28
29
|
edgeTarget: v.optional(v.string()),
|
|
29
30
|
edgeArity: v.optional(v.string()),
|
|
30
31
|
hierarchy: v.optional(v.boolean()),
|
|
@@ -51,4 +52,4 @@ const SchemaObject = v.strictObject({
|
|
|
51
52
|
props: v.optional(v.array(SchemaProperty)),
|
|
52
53
|
childs: v.optional(v.array(v.lazy(() => SchemaObject)))
|
|
53
54
|
});
|
|
54
|
-
export const
|
|
55
|
+
export const Schema = v.pipe(v.array(SchemaObject), v.minLength(1));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
export type Spec = {
|
|
3
|
+
artifacts: SpecArtifact[];
|
|
4
|
+
};
|
|
5
|
+
export type SpecArtifact = {
|
|
6
|
+
created: Date;
|
|
7
|
+
modified: Date;
|
|
8
|
+
objects: SpecObject[];
|
|
9
|
+
};
|
|
10
|
+
export type SpecObject = {
|
|
11
|
+
kind: string;
|
|
12
|
+
id: string;
|
|
13
|
+
anchor?: string;
|
|
14
|
+
paren?: string;
|
|
15
|
+
name: string;
|
|
16
|
+
primary?: boolean;
|
|
17
|
+
description?: SpecDescription;
|
|
18
|
+
properties: SpecProperty[];
|
|
19
|
+
childs: SpecObject[];
|
|
20
|
+
};
|
|
21
|
+
export type SpecDescription = {
|
|
22
|
+
description: string;
|
|
23
|
+
rationale?: string;
|
|
24
|
+
embedding?: string[];
|
|
25
|
+
};
|
|
26
|
+
export type SpecProperty = {
|
|
27
|
+
key: string;
|
|
28
|
+
value: string;
|
|
29
|
+
embedding?: string[];
|
|
30
|
+
};
|
|
31
|
+
export declare const Spec: v.GenericSchema<Spec>;
|
|
@@ -5,32 +5,32 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as v from "valibot";
|
|
7
7
|
/* ==== Schema ==== */
|
|
8
|
-
const
|
|
8
|
+
const SpecProperty = v.object({
|
|
9
9
|
key: v.string(),
|
|
10
10
|
value: v.string(),
|
|
11
11
|
embedding: v.optional(v.array(v.string()))
|
|
12
12
|
});
|
|
13
|
-
const
|
|
13
|
+
const SpecDescription = v.object({
|
|
14
14
|
description: v.string(),
|
|
15
15
|
rationale: v.optional(v.string()),
|
|
16
16
|
embedding: v.optional(v.array(v.string()))
|
|
17
17
|
});
|
|
18
|
-
const
|
|
18
|
+
const SpecObject = v.object({
|
|
19
19
|
kind: v.string(),
|
|
20
20
|
id: v.string(),
|
|
21
21
|
anchor: v.optional(v.string()),
|
|
22
22
|
paren: v.optional(v.string()),
|
|
23
23
|
name: v.string(),
|
|
24
24
|
primary: v.optional(v.boolean()),
|
|
25
|
-
description: v.optional(
|
|
26
|
-
properties: v.array(
|
|
27
|
-
childs: v.array(v.lazy(() =>
|
|
25
|
+
description: v.optional(SpecDescription),
|
|
26
|
+
properties: v.array(SpecProperty),
|
|
27
|
+
childs: v.array(v.lazy(() => SpecObject))
|
|
28
28
|
});
|
|
29
|
-
const
|
|
29
|
+
const SpecArtifact = v.object({
|
|
30
30
|
created: v.date(),
|
|
31
31
|
modified: v.date(),
|
|
32
|
-
objects: v.pipe(v.array(
|
|
32
|
+
objects: v.pipe(v.array(SpecObject), v.minLength(1))
|
|
33
33
|
});
|
|
34
|
-
export const
|
|
35
|
-
artifacts: v.pipe(v.array(
|
|
34
|
+
export const Spec = v.object({
|
|
35
|
+
artifacts: v.pipe(v.array(SpecArtifact), v.minLength(1))
|
|
36
36
|
});
|