@heroiclands/package-build 22.4.5 → 22.4.7
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/CHANGELOG.md +16 -0
- package/CONTENT.md +109 -0
- package/bin/content-build.mjs +36 -8
- package/content-config.mjs +8 -3
- package/docs/api.md +86 -46
- package/docs/commands.md +64 -24
- package/docs/configuration.md +20 -1
- package/docs/content-format.md +71 -0
- package/engine/frontmatter-lint.mjs +2 -0
- package/engine/holdings.mjs +391 -0
- package/engine/index.mjs +9 -0
- package/engine/map-build.mjs +41 -11
- package/engine/map-dot.mjs +84 -24
- package/engine/map-graphviz.mjs +12 -3
- package/engine/map-layout.mjs +283 -101
- package/engine/map-places.mjs +33 -3
- package/engine/market-class.mjs +178 -0
- package/engine/metadata-index.mjs +7 -4
- package/engine/note-vocabulary.mjs +19 -0
- package/engine/site-build.mjs +88 -10
- package/engine/site-maps.mjs +165 -0
- package/package.json +1 -1
- package/types/engine/holdings.d.mts +146 -0
- package/types/engine/index.d.mts +3 -0
- package/types/engine/map-build.d.mts +13 -7
- package/types/engine/map-dot.d.mts +22 -9
- package/types/engine/map-graphviz.d.mts +8 -1
- package/types/engine/map-layout.d.mts +117 -49
- package/types/engine/map-places.d.mts +26 -0
- package/types/engine/market-class.d.mts +62 -0
- package/types/engine/note-vocabulary.d.mts +10 -0
- package/types/engine/site-build.d.mts +37 -6
- package/types/engine/site-maps.d.mts +81 -0
package/engine/map-dot.mjs
CHANGED
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The
|
|
15
|
+
* The drawings of `content-build map`, as DOT.
|
|
16
16
|
*
|
|
17
17
|
* Every drawing is emitted as DOT text and kept beside its rendering, so a
|
|
18
18
|
* picture can be re-rendered, diffed or read without the content index.
|
|
19
|
-
*
|
|
19
|
+
* The drawings share one style table: **shape, colour and size are by
|
|
20
20
|
* `subType`**, in one place, so the containment tree, the map from a place,
|
|
21
|
-
* the route graph and the legend all follow an edit to a
|
|
21
|
+
* the chart from one, the route graph and the legend all follow an edit to a
|
|
22
|
+
* row. An anomaly
|
|
22
23
|
* overrides fill and border colour only, so what kind of place a node is and
|
|
23
24
|
* whether something is wrong with it read as two separate questions.
|
|
24
25
|
*
|
|
@@ -45,6 +46,46 @@ const POLITY_BORDER = "#616161";
|
|
|
45
46
|
/** The colour of a ring, and of anything two hops out. */
|
|
46
47
|
const FAINT = "#B0BEC5";
|
|
47
48
|
const DIM = "#78909C";
|
|
49
|
+
/** The fill of a place two or more hops out. */
|
|
50
|
+
const DIM_FILL = "#ECEFF1";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How much of the second hop's dimness a further hop keeps: each hop beyond
|
|
54
|
+
* the second steps down by this much.
|
|
55
|
+
*/
|
|
56
|
+
const HOP_FADE_STEP = 0.25;
|
|
57
|
+
|
|
58
|
+
/** The least a place may fade to, so the farthest hop still reads. */
|
|
59
|
+
const HOP_FADE_FLOOR = 0.5;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The opacity of a place by its hop count: the neighbours and the second hop
|
|
63
|
+
* are drawn full — the second in the dim palette — and each hop beyond
|
|
64
|
+
* steps down, never below the floor.
|
|
65
|
+
*
|
|
66
|
+
* @param {number} hop - 1 for a neighbour, 2 and up beyond.
|
|
67
|
+
* @returns {number} 0 to 1.
|
|
68
|
+
*/
|
|
69
|
+
export function hopOpacity(hop) {
|
|
70
|
+
return Math.max(HOP_FADE_FLOOR, 1 - HOP_FADE_STEP * Math.max(0, hop - 2));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A colour at an opacity, as DOT reads it: `#RRGGBB` at full, `#RRGGBBAA`
|
|
75
|
+
* otherwise.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} colour - `#RRGGBB`.
|
|
78
|
+
* @param {number} opacity - 0 to 1.
|
|
79
|
+
* @returns {string} The colour.
|
|
80
|
+
*/
|
|
81
|
+
function faded(colour, opacity) {
|
|
82
|
+
if (opacity >= 1) return colour;
|
|
83
|
+
const alpha = Math.round(Math.max(0, opacity) * 255)
|
|
84
|
+
.toString(16)
|
|
85
|
+
.padStart(2, "0")
|
|
86
|
+
.toUpperCase();
|
|
87
|
+
return `${colour}${alpha}`;
|
|
88
|
+
}
|
|
48
89
|
|
|
49
90
|
/**
|
|
50
91
|
* One row of the style table.
|
|
@@ -511,7 +552,7 @@ export function treeDot({
|
|
|
511
552
|
}
|
|
512
553
|
|
|
513
554
|
/* --------------------------------------------------------------------- */
|
|
514
|
-
/* The map from a place
|
|
555
|
+
/* The map from a place, and the chart */
|
|
515
556
|
/* --------------------------------------------------------------------- */
|
|
516
557
|
|
|
517
558
|
/**
|
|
@@ -532,32 +573,39 @@ function edgeLabel(edge) {
|
|
|
532
573
|
}
|
|
533
574
|
|
|
534
575
|
/**
|
|
535
|
-
* The map from a place as DOT
|
|
576
|
+
* The map from a place, or the chart from one, as DOT for `neato -n2`.
|
|
536
577
|
*
|
|
537
578
|
* Every node is pinned at the position the layout computed, so the engine
|
|
538
579
|
* only draws. The rings are circles pinned at the origin, drawn faintly,
|
|
539
|
-
* each labelled with its days marker at the top; the rim is one more
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
580
|
+
* each labelled with its days marker at the top; the rim is one more,
|
|
581
|
+
* labelled `beyond` where the layout names what lies past the horizon there
|
|
582
|
+
* and `unknown` where it holds only the places whose days nobody stated.
|
|
583
|
+
* The centre is drawn in its `subType` style at full size, a neighbour at
|
|
584
|
+
* the layout's scale, a second hop dimmer, and each hop beyond fainter
|
|
585
|
+
* still, down to a floor. A place at the rim is a name, its label carrying
|
|
586
|
+
* its days or `?`. A border edge is dashed; every edge is labelled with its
|
|
587
|
+
* bearing and days, and dimmed with its far end.
|
|
544
588
|
*
|
|
545
|
-
* @param {
|
|
589
|
+
* @param {import("./map-layout.mjs").FromLayout} layout - The layout.
|
|
546
590
|
* @param {Map<string, import("./map-places.mjs").MapPlace>} places - Every place.
|
|
547
591
|
* @param {object} [opts]
|
|
548
592
|
* @param {number} [opts.scale=1] - Multiplies every size.
|
|
549
|
-
* @param {string} [opts.title] - Defaults to `From <name
|
|
593
|
+
* @param {string} [opts.title] - Defaults to `From <name>`, or to
|
|
594
|
+
* `Chart from <name>` for a layout with no rim beyond the horizon.
|
|
550
595
|
* @returns {string} The DOT text.
|
|
551
596
|
*/
|
|
552
597
|
export function fromDot(layout, places, { scale = 1, title } = {}) {
|
|
553
598
|
const centre = places.get(layout.centre);
|
|
599
|
+
const name = centre?.name ?? layout.centre;
|
|
554
600
|
const lines = [];
|
|
555
601
|
lines.push("digraph from {");
|
|
556
602
|
lines.push(" splines=false; overlap=true; outputorder=nodesfirst;");
|
|
557
603
|
lines.push(' graph [fontname="Helvetica"];');
|
|
558
604
|
lines.push(' node [fontname="Helvetica"];');
|
|
559
605
|
lines.push(` edge [fontname="Helvetica", fontsize=${Math.max(6, Math.round(8 * scale))}];`);
|
|
560
|
-
lines.push(
|
|
606
|
+
lines.push(
|
|
607
|
+
` label=${dotString(title ?? (layout.beyond ? `From ${name}` : `Chart from ${name}`))};`,
|
|
608
|
+
);
|
|
561
609
|
lines.push(' labelloc=t; fontsize=18; fontname="Helvetica-Bold";');
|
|
562
610
|
lines.push("");
|
|
563
611
|
|
|
@@ -578,10 +626,15 @@ export function fromDot(layout, places, { scale = 1, title } = {}) {
|
|
|
578
626
|
`pos="0,${(ring.radius + 6).toFixed(1)}!", width=0, height=0, margin=0];`,
|
|
579
627
|
);
|
|
580
628
|
}
|
|
581
|
-
ringNode(
|
|
629
|
+
ringNode(
|
|
630
|
+
"rim",
|
|
631
|
+
layout.rim,
|
|
632
|
+
layout.beyond ? "beyond the horizon, or days unknown" : "days unknown",
|
|
633
|
+
);
|
|
582
634
|
lines.push(
|
|
583
|
-
` rimlabel [label
|
|
584
|
-
`
|
|
635
|
+
` rimlabel [label=${dotString(layout.beyond ? "beyond" : "unknown")}, shape=plaintext, ` +
|
|
636
|
+
`fontsize=${Math.max(6, Math.round(7 * scale))}, fontcolor=${dotString(DIM)}, ` +
|
|
637
|
+
`pos="0,${(layout.rim + 6).toFixed(1)}!", width=0, height=0, margin=0];`,
|
|
585
638
|
);
|
|
586
639
|
lines.push("");
|
|
587
640
|
|
|
@@ -590,27 +643,29 @@ export function fromDot(layout, places, { scale = 1, title } = {}) {
|
|
|
590
643
|
if (!place) continue;
|
|
591
644
|
const pos = `pos="${node.x.toFixed(1)},${node.y.toFixed(1)}!"`;
|
|
592
645
|
const id = nodeId("p", node.shortcode);
|
|
593
|
-
const
|
|
594
|
-
|
|
646
|
+
const opacity = hopOpacity(node.hop);
|
|
647
|
+
const atRim = node.unknown || node.radius >= layout.rim;
|
|
648
|
+
if (atRim) {
|
|
595
649
|
const label = dotLines([place.name, node.days === undefined ? "?" : `${node.days} d`]);
|
|
596
650
|
lines.push(
|
|
597
651
|
` ${id} [label=${label}, shape=plaintext, fontsize=${Math.max(6, Math.round(9 * scale))}, ` +
|
|
598
|
-
`fontcolor=${dotString(DIM)}, tooltip=${dotString(place.address ?? place.shortcode)}` +
|
|
652
|
+
`fontcolor=${dotString(faded(DIM, opacity))}, tooltip=${dotString(place.address ?? place.shortcode)}` +
|
|
599
653
|
(place.url ? `, URL=${dotString(place.url)}` : "") +
|
|
600
654
|
`, ${pos}];`,
|
|
601
655
|
);
|
|
602
656
|
continue;
|
|
603
657
|
}
|
|
604
658
|
const nodeScale = node.hop === 0 ? scale : scale * 0.8;
|
|
659
|
+
const dim = node.hop >= 2;
|
|
605
660
|
lines.push(
|
|
606
661
|
` ${id} ${styleAttrs(place.subType ?? "settlement", {
|
|
607
662
|
scale: nodeScale,
|
|
608
663
|
label: place.name,
|
|
609
664
|
tooltip: place.address ?? place.shortcode,
|
|
610
665
|
url: place.url,
|
|
611
|
-
border:
|
|
612
|
-
fontcolor:
|
|
613
|
-
extra: pos + (
|
|
666
|
+
border: dim ? faded(DIM, opacity) : undefined,
|
|
667
|
+
fontcolor: dim ? faded(DIM, opacity) : undefined,
|
|
668
|
+
extra: pos + (dim ? `, fillcolor=${dotString(faded(DIM_FILL, opacity))}` : ""),
|
|
614
669
|
})};`,
|
|
615
670
|
);
|
|
616
671
|
}
|
|
@@ -619,8 +674,13 @@ export function fromDot(layout, places, { scale = 1, title } = {}) {
|
|
|
619
674
|
for (const edge of layout.edges) {
|
|
620
675
|
const attrs = [`label=${dotString(edgeLabel(edge))}`, "arrowhead=none"];
|
|
621
676
|
if (edge.kind === "border") attrs.push("style=dashed");
|
|
622
|
-
if (edge.hop
|
|
623
|
-
|
|
677
|
+
if (edge.hop >= 2) {
|
|
678
|
+
const opacity = hopOpacity(edge.hop);
|
|
679
|
+
attrs.push(
|
|
680
|
+
`color=${dotString(faded(FAINT, opacity))}`,
|
|
681
|
+
`fontcolor=${dotString(faded(DIM, opacity))}`,
|
|
682
|
+
);
|
|
683
|
+
} else attrs.push(`color=${dotString(DEFAULT_BORDER)}`);
|
|
624
684
|
lines.push(` ${nodeId("p", edge.from)} -> ${nodeId("p", edge.to)} [${attrs.join(", ")}];`);
|
|
625
685
|
}
|
|
626
686
|
lines.push("}");
|
package/engine/map-graphviz.mjs
CHANGED
|
@@ -89,9 +89,19 @@ export function findGraphviz(engine, { env = process.env, fallbacks = KNOWN_DIRE
|
|
|
89
89
|
return undefined;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* What installs GraphViz on the two platforms a contributor is likely to be
|
|
94
|
+
* on, as the clause every "not installed" message ends with.
|
|
95
|
+
*
|
|
96
|
+
* @type {string}
|
|
97
|
+
*/
|
|
98
|
+
export const GRAPHVIZ_INSTALL =
|
|
99
|
+
"install GraphViz (`brew install graphviz` on macOS, " +
|
|
100
|
+
"`apt-get install graphviz` on Debian or Ubuntu)";
|
|
101
|
+
|
|
92
102
|
/**
|
|
93
103
|
* What to say when an engine is not installed: the name, and what installs
|
|
94
|
-
* it
|
|
104
|
+
* it.
|
|
95
105
|
*
|
|
96
106
|
* @param {string} engine - The engine that was wanted.
|
|
97
107
|
* @returns {string} The message.
|
|
@@ -99,8 +109,7 @@ export function findGraphviz(engine, { env = process.env, fallbacks = KNOWN_DIRE
|
|
|
99
109
|
export function graphvizMissingMessage(engine) {
|
|
100
110
|
return (
|
|
101
111
|
`GraphViz's \`${engine}\` is not installed, and \`content-build map\` draws with it; ` +
|
|
102
|
-
|
|
103
|
-
`\`apt-get install graphviz\` on Debian or Ubuntu) and run the command again`
|
|
112
|
+
`${GRAPHVIZ_INSTALL} and run the command again`
|
|
104
113
|
);
|
|
105
114
|
}
|
|
106
115
|
|