aontu 0.54.0 → 0.55.0
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/dist/aontu.d.ts +1 -1
- package/dist/aontu.js +1 -1
- package/dist/cli.js +80 -7
- package/dist/cli.js.map +1 -1
- package/dist/hints.js +4 -0
- package/dist/hints.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/mcp.js +21 -1
- package/dist/mcp.js.map +1 -1
- package/dist/std.js +4 -1
- package/dist/std.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vet.d.ts +1 -0
- package/dist/vet.js +5 -0
- package/dist/vet.js.map +1 -1
- package/dist/view.d.ts +6 -1
- package/dist/view.js +276 -56
- package/dist/view.js.map +1 -1
- package/package.json +1 -1
- package/src/aontu.ts +1 -1
- package/src/cli.ts +89 -9
- package/src/hints.ts +4 -0
- package/src/mcp.ts +22 -1
- package/src/std.ts +4 -1
- package/src/vet.ts +5 -1
- package/src/view.ts +376 -57
package/dist/view.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* Copyright (c) 2026 Richard Rodger, MIT License */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.viewDefaultProfile = viewDefaultProfile;
|
|
4
5
|
exports.view = view;
|
|
5
6
|
exports.viewTree = viewTree;
|
|
6
7
|
exports.viewSet = viewSet;
|
|
@@ -41,7 +42,7 @@ exports.viewSet = viewSet;
|
|
|
41
42
|
// emitted order, not of the model) -- and leave the verdict `rendered`. Every
|
|
42
43
|
// other code makes it `lossy`, which `--strict` refuses: a figure that
|
|
43
44
|
// quietly omits things is the failure this capability exists to avoid.
|
|
44
|
-
const
|
|
45
|
+
const node_path_1 = require("node:path");
|
|
45
46
|
const aontu_1 = require("./aontu");
|
|
46
47
|
const vet_1 = require("./vet");
|
|
47
48
|
const graph_1 = require("./graph");
|
|
@@ -49,10 +50,27 @@ const keyorder_1 = require("./keyorder");
|
|
|
49
50
|
const provenance_1 = require("./provenance");
|
|
50
51
|
const query_1 = require("./query");
|
|
51
52
|
const subsume_1 = require("./subsume");
|
|
53
|
+
// The text profile's mechanism: the eight named colours, `bold` and
|
|
54
|
+
// `dim`, and nothing else. `label` is unstyled -- an entity's own name
|
|
55
|
+
// is the figure's content, not a mark about it.
|
|
56
|
+
const SGR = {
|
|
57
|
+
label: '', muted: '2', rule: '2', direct: '1', closure: '36',
|
|
58
|
+
unmirrored: '33', upward: '31', repeat: '2', bar: '36', hole: '2',
|
|
59
|
+
};
|
|
60
|
+
const PLAIN = (_role, text) => text;
|
|
61
|
+
const ANSI = (role, text) => '' === SGR[role] || '' === text
|
|
62
|
+
? text : `\x1b[${SGR[role]}m${text}\x1b[0m`;
|
|
63
|
+
const painter = (style) => 'ansi' === style ? ANSI : PLAIN;
|
|
64
|
+
// The style a figure gets when the caller named none. An SVG carries
|
|
65
|
+
// its stylesheet, which is what makes it standalone and what every
|
|
66
|
+
// pinned golden holds; everything else carries no mechanism, since a
|
|
67
|
+
// library cannot see whether its output is a terminal.
|
|
68
|
+
const styleOf = (style, as) => style ?? ('svg' === as ? 'css' : 'none');
|
|
52
69
|
// Each kind's profiles, the first being its default. There is no
|
|
53
70
|
// global default, because there is no sensible text form of a
|
|
54
71
|
// node-link drawing and no sensible Mermaid form of a matrix.
|
|
55
72
|
const PROFILES = {
|
|
73
|
+
doc: ['text', 'svg'],
|
|
56
74
|
tree: ['text', 'svg'],
|
|
57
75
|
matrix: ['text', 'svg'],
|
|
58
76
|
graph: ['mermaid', 'dot', 'er'],
|
|
@@ -62,6 +80,12 @@ const PROFILES = {
|
|
|
62
80
|
ladder: ['mermaid', 'dot'],
|
|
63
81
|
poset: ['mermaid', 'dot'],
|
|
64
82
|
};
|
|
83
|
+
// The profile a kind draws into when none is asked for. The CLI needs
|
|
84
|
+
// it to resolve `--style auto` BEFORE the library runs, since the
|
|
85
|
+
// mechanism is the profile's.
|
|
86
|
+
function viewDefaultProfile(kind) {
|
|
87
|
+
return PROFILES[kind]?.[0];
|
|
88
|
+
}
|
|
65
89
|
// Loss codes that describe the drawing rather than a gap in it.
|
|
66
90
|
const INFORMATIONAL = ['edges_deduped', 'inverse_suppressed', 'crossings'];
|
|
67
91
|
const DEFAULT_MAX_ROWS = 60;
|
|
@@ -323,11 +347,16 @@ const SVG_STYLE = '<style>' +
|
|
|
323
347
|
const svgEsc = (s) => escape(s, SVG_ESC);
|
|
324
348
|
// The document: a viewBox the size of the figure, the style, and the
|
|
325
349
|
// parts, one per line, so the bytes read as a figure and diff as one.
|
|
326
|
-
function svgDoc(w, h, about, parts) {
|
|
350
|
+
function svgDoc(w, h, about, parts, style) {
|
|
351
|
+
// The CLASSES are structure and are always written -- a rect that
|
|
352
|
+
// does not say whether it is a direct cell or a closure cell is not
|
|
353
|
+
// a figure. What `--style none` drops is the STYLESHEET, for a host
|
|
354
|
+
// page that has already bound the variables and would otherwise
|
|
355
|
+
// carry one copy of these rules per embedded figure.
|
|
327
356
|
return [
|
|
328
357
|
`<svg xmlns="http://www.w3.org/2000/svg" class="av" viewBox="0 0 ${w} ${h}" ` +
|
|
329
358
|
`width="${w}" height="${h}" role="img" aria-label="${svgEsc(about)}">`,
|
|
330
|
-
SVG_STYLE,
|
|
359
|
+
...('css' === style ? [SVG_STYLE] : []),
|
|
331
360
|
...parts,
|
|
332
361
|
'</svg>',
|
|
333
362
|
].join('\n');
|
|
@@ -394,7 +423,8 @@ function collapse(triples, relation) {
|
|
|
394
423
|
// One winner per pair, so (from, to) is unique and orders the set.
|
|
395
424
|
return out.sort((x, y) => (0, keyorder_1.cmpCodePoint)(x.from, y.from) || (0, keyorder_1.cmpCodePoint)(x.to, y.to));
|
|
396
425
|
}
|
|
397
|
-
function drawTree(all, relation, roots, max, as) {
|
|
426
|
+
function drawTree(all, relation, roots, max, as, style) {
|
|
427
|
+
const paint = painter(style);
|
|
398
428
|
// With a relation named, the tree is OVER THAT RELATION. A node-link
|
|
399
429
|
// diagram can label each edge and so draw every relation at once; a
|
|
400
430
|
// tree cannot without becoming unreadable, and walking two relations
|
|
@@ -479,7 +509,8 @@ function drawTree(all, relation, roots, max, as) {
|
|
|
479
509
|
const grown = 0 < kids.get(edge.to).length;
|
|
480
510
|
const text = label(edge.to) + (many ? ' (' + edge.label + ')' : '');
|
|
481
511
|
const mark = loop ? ' (cycle)' : (seen && grown ? ' (*)' : '');
|
|
482
|
-
out.push(frame.prefix + (last ? '└── ' : '├── ')
|
|
512
|
+
out.push(paint('rule', frame.prefix + (last ? '└── ' : '├── '))
|
|
513
|
+
+ text + paint('repeat', mark));
|
|
483
514
|
rows.push({ depth: stack.length, text, mark, parent: frame.row });
|
|
484
515
|
if (loop || seen) {
|
|
485
516
|
continue;
|
|
@@ -511,13 +542,17 @@ function drawTree(all, relation, roots, max, as) {
|
|
|
511
542
|
}
|
|
512
543
|
}
|
|
513
544
|
}
|
|
514
|
-
return {
|
|
545
|
+
return {
|
|
546
|
+
text: 'svg' === as
|
|
547
|
+
? treeSvg(rows, `Dependency tree: ${nodes.length} nodes`, style)
|
|
548
|
+
: out.join('\n'),
|
|
549
|
+
};
|
|
515
550
|
}
|
|
516
551
|
// The tree as SVG: one line per row, each node indented one unit per
|
|
517
552
|
// depth, joined to its parent by a path that drops from the parent's
|
|
518
553
|
// row and turns in to the child. The marks are muted text after the
|
|
519
554
|
// label.
|
|
520
|
-
function treeSvg(rows,
|
|
555
|
+
function treeSvg(rows, about, style) {
|
|
521
556
|
const U = 24;
|
|
522
557
|
const parts = [];
|
|
523
558
|
let width = 0;
|
|
@@ -537,7 +572,138 @@ function treeSvg(rows, count) {
|
|
|
537
572
|
`</tspan><tspan class="av-m">${svgEsc(r.mark)}</tspan></text>`);
|
|
538
573
|
width = Math.max(width, x + (r.text.length + r.mark.length) * CH);
|
|
539
574
|
});
|
|
540
|
-
return svgDoc(width + PAD, rows.length * LH + PAD,
|
|
575
|
+
return svgDoc(width + PAD, rows.length * LH + PAD, about, parts, style);
|
|
576
|
+
}
|
|
577
|
+
// ---------------------------------------------------------------------
|
|
578
|
+
// The document tree
|
|
579
|
+
// THE SHAPE OF THE MODEL ITSELF, which no other kind draws. Every
|
|
580
|
+
// other figure here reads a REPORT -- the edge set, the provenance
|
|
581
|
+
// record, the subsumption order -- and so can only draw a document
|
|
582
|
+
// that has links, contributions or peers. A reader meeting a model for
|
|
583
|
+
// the first time wants the plainer thing first: what is in it, and how
|
|
584
|
+
// it is arranged.
|
|
585
|
+
//
|
|
586
|
+
// This is `get --keys --types` as a picture, and it reads the same
|
|
587
|
+
// walk: map keys in code-point order, list indices in order, and a
|
|
588
|
+
// leaf's KIND rather than its value -- the canon of a scalar's type,
|
|
589
|
+
// not the scalar. Values are what the document is for; the shape is
|
|
590
|
+
// what a reader needs before any of them mean anything.
|
|
591
|
+
//
|
|
592
|
+
// DEPTH IS A BOUND, NOT AN ELISION MARK. Below it the subtree is not
|
|
593
|
+
// drawn and the row says how many keys were not drawn, because a tree
|
|
594
|
+
// that stops without saying so is the one thing a structural drawing
|
|
595
|
+
// must not be.
|
|
596
|
+
const DEFAULT_DOC_DEPTH = 3;
|
|
597
|
+
// A node's own children, as the anchor walk sees them: map keys sorted
|
|
598
|
+
// by code point, list indices in order, and nothing for a leaf.
|
|
599
|
+
function docKids(v) {
|
|
600
|
+
const node = throughDoc(v);
|
|
601
|
+
if (true === node?.isMap) {
|
|
602
|
+
// AN ALIAS DECLARATION IS NOT PART OF THE DOCUMENT
|
|
603
|
+
// (docs/reference-language.md, "Aliases"): it does not generate
|
|
604
|
+
// and it does not appear in canon. It IS a key of the root map in
|
|
605
|
+
// the value tree, which `get --keys` reports and this does not --
|
|
606
|
+
// a figure of the document's shape that showed `%Cents` beside
|
|
607
|
+
// `customers` would be drawing the declaration as data
|
|
608
|
+
// (use-cases/BUGS.md 74).
|
|
609
|
+
return Object.keys(node.peg)
|
|
610
|
+
.filter((k) => !k.startsWith('%')).sort(keyorder_1.cmpCodePoint);
|
|
611
|
+
}
|
|
612
|
+
if (true === node?.isList) {
|
|
613
|
+
return Object.keys(node.peg).filter((k) => /^[0-9]+$/.test(k));
|
|
614
|
+
}
|
|
615
|
+
return [];
|
|
616
|
+
}
|
|
617
|
+
// A preference wraps its value without being a level of its own, and
|
|
618
|
+
// `anchorAt` already steps through a sizing residue; this is the same
|
|
619
|
+
// unwrapping, for the shape walk.
|
|
620
|
+
function throughDoc(v) {
|
|
621
|
+
// Every caller reaches this with a Val the anchor walk handed over,
|
|
622
|
+
// so the node is never absent and the optional chain that would say
|
|
623
|
+
// otherwise is an arm no test can take.
|
|
624
|
+
const node = (0, vet_1.throughResidue)(v);
|
|
625
|
+
return true === node.isPref ? throughDoc(node.peg) : node;
|
|
626
|
+
}
|
|
627
|
+
// What a leaf IS, in one short word: its canon, which for a constraint
|
|
628
|
+
// is the constraint and for a scalar its value. Long canons are cut,
|
|
629
|
+
// since the figure is the shape and not the data.
|
|
630
|
+
function docLeaf(v) {
|
|
631
|
+
// A CONTAINER WITH NOTHING IN IT IS NOT A LEAF, and calling it one
|
|
632
|
+
// by writing nothing after the key would make it read as a value the
|
|
633
|
+
// figure declined to describe. Its canon says what it is -- `{}`,
|
|
634
|
+
// `[]`, or a template a spread wrote and no member filled.
|
|
635
|
+
//
|
|
636
|
+
// `canon` is a string on every Val, so there is no other-type arm to
|
|
637
|
+
// take; the cut is the only decision here.
|
|
638
|
+
const canon = throughDoc(v).canon;
|
|
639
|
+
return 32 < canon.length ? canon.slice(0, 29) + '...' : canon;
|
|
640
|
+
}
|
|
641
|
+
function drawDoc(root, o, max, loss) {
|
|
642
|
+
const paint = painter(o.style);
|
|
643
|
+
const at = o.at ?? '$';
|
|
644
|
+
const anchor = (0, vet_1.anchorAt)(root, at);
|
|
645
|
+
if (null == anchor) {
|
|
646
|
+
return {
|
|
647
|
+
// The same code and the same sentence `get` answers with: the
|
|
648
|
+
// question is identical, so a caller that already handles one
|
|
649
|
+
// handles the other.
|
|
650
|
+
errors: [finding('no_path', 'reference', at, `The path ${at} names nothing in this document.`)],
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
const depth = o.depth ?? DEFAULT_DOC_DEPTH;
|
|
654
|
+
const out = [];
|
|
655
|
+
const rows = [];
|
|
656
|
+
let elided = 0;
|
|
657
|
+
out.push(at);
|
|
658
|
+
rows.push({ depth: 0, text: at, mark: '', parent: 0 });
|
|
659
|
+
const stack = [
|
|
660
|
+
{ node: anchor, kids: docKids(anchor), at: 0, prefix: '', row: 0 },
|
|
661
|
+
];
|
|
662
|
+
while (0 < stack.length) {
|
|
663
|
+
const frame = stack[stack.length - 1];
|
|
664
|
+
if (frame.at >= frame.kids.length) {
|
|
665
|
+
stack.pop();
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
const key = frame.kids[frame.at++];
|
|
669
|
+
const last = frame.at === frame.kids.length;
|
|
670
|
+
const child = throughDoc(throughDoc(frame.node).peg[key]);
|
|
671
|
+
const kids = docKids(child);
|
|
672
|
+
const under = stack.length < depth;
|
|
673
|
+
// A container the depth bound stops at says how many keys are not
|
|
674
|
+
// drawn; a leaf says what it is.
|
|
675
|
+
// A leaf says what it is and a stopped container says how many
|
|
676
|
+
// keys it holds; both are written after the key with one space,
|
|
677
|
+
// and neither is ever empty (a canon has at least one character).
|
|
678
|
+
const mark = 0 === kids.length ? ' ' + docLeaf(child)
|
|
679
|
+
: under ? '' : ` (${kids.length})`;
|
|
680
|
+
if (0 < kids.length && !under) {
|
|
681
|
+
elided += kids.length;
|
|
682
|
+
}
|
|
683
|
+
out.push(paint('rule', frame.prefix + (last ? '└── ' : '├── ')) + key +
|
|
684
|
+
paint('muted', mark));
|
|
685
|
+
rows.push({ depth: stack.length, text: key, mark, parent: frame.row });
|
|
686
|
+
if (max < rows.length) {
|
|
687
|
+
return {
|
|
688
|
+
errors: [rowsFinding(rows.length, max, '--at or --depth')],
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
if (0 < kids.length && under) {
|
|
692
|
+
stack.push({
|
|
693
|
+
node: child, kids, at: 0,
|
|
694
|
+
prefix: frame.prefix + (last ? ' ' : '│ '),
|
|
695
|
+
row: rows.length - 1,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if (0 < elided) {
|
|
700
|
+
loss.push({ code: 'depth_elided', count: elided });
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
text: 'svg' === o.as
|
|
704
|
+
? treeSvg(rows, `Document tree at ${at}: ${rows.length - 1} keys to depth ${depth}`, o.style)
|
|
705
|
+
: out.join('\n'),
|
|
706
|
+
};
|
|
541
707
|
}
|
|
542
708
|
// ---------------------------------------------------------------------
|
|
543
709
|
// The matrix (Ghoniem et al. 2004; Sangal et al. 2005)
|
|
@@ -597,6 +763,7 @@ function pickRelation(relation, keys) {
|
|
|
597
763
|
return { relation: keys[0] ?? '' };
|
|
598
764
|
}
|
|
599
765
|
function drawMatrix(triples, decls, o, max, loss) {
|
|
766
|
+
const paint = painter(o.style);
|
|
600
767
|
const picked = pickRelation(o.relation, keysOf(triples));
|
|
601
768
|
if (undefined !== picked.error) {
|
|
602
769
|
return { errors: [picked.error] };
|
|
@@ -634,7 +801,7 @@ function drawMatrix(triples, decls, o, max, loss) {
|
|
|
634
801
|
// column stays one character wide.
|
|
635
802
|
for (let d = 0; d < iw; d++) {
|
|
636
803
|
lines.push(' '.repeat(w + 1 + iw + 1) +
|
|
637
|
-
idx.map((s) => lpad(s, iw)[d]).join(' '));
|
|
804
|
+
paint('muted', idx.map((s) => lpad(s, iw)[d]).join(' ')));
|
|
638
805
|
}
|
|
639
806
|
let above = 0;
|
|
640
807
|
const grid = [];
|
|
@@ -652,14 +819,15 @@ function drawMatrix(triples, decls, o, max, loss) {
|
|
|
652
819
|
: o.closure && reach.get(r).has(c) ? '+' : '.';
|
|
653
820
|
});
|
|
654
821
|
grid.push(cells);
|
|
655
|
-
lines.push(pad(label(r), w) + ' ' + lpad(idx[ri], iw) + ' ' +
|
|
822
|
+
lines.push(pad(label(r), w) + ' ' + paint('muted', lpad(idx[ri], iw)) + ' ' +
|
|
823
|
+
cells.map((g) => paint(CELL_ROLE[g], g)).join(' '));
|
|
656
824
|
});
|
|
657
825
|
const footer = `# above-diagonal direct cells: ${above}`;
|
|
658
|
-
lines.push(footer);
|
|
826
|
+
lines.push(paint('muted', footer));
|
|
659
827
|
if ('svg' === o.as) {
|
|
660
828
|
return {
|
|
661
829
|
text: matrixSvg(order.map(label), idx, grid, footer, `Dependency matrix${over(relation)}: ${order.length} rows, ` +
|
|
662
|
-
`${above} direct cells above the diagonal
|
|
830
|
+
`${above} direct cells above the diagonal`, o.style),
|
|
663
831
|
};
|
|
664
832
|
}
|
|
665
833
|
return { text: lines.join('\n') };
|
|
@@ -670,7 +838,16 @@ const CELL_CLASS = {
|
|
|
670
838
|
X: 'av-direct', '!': 'av-unmirrored', '+': 'av-closure',
|
|
671
839
|
'.': 'av-cell', '\\': 'av-cell',
|
|
672
840
|
};
|
|
673
|
-
|
|
841
|
+
// The same five states as ROLES, for the text profile. One table per
|
|
842
|
+
// mechanism rather than one shared one, because the two vocabularies
|
|
843
|
+
// are not in step: SVG needs a class for the empty cell (it draws a
|
|
844
|
+
// rect there) and the text profile has nothing to say about a `.`
|
|
845
|
+
// beyond that it is not a mark.
|
|
846
|
+
const CELL_ROLE = {
|
|
847
|
+
X: 'direct', '!': 'unmirrored', '+': 'closure',
|
|
848
|
+
'.': 'muted', '\\': 'rule',
|
|
849
|
+
};
|
|
850
|
+
function matrixSvg(labels, idx, grid, footer, about, style) {
|
|
674
851
|
const S = 20;
|
|
675
852
|
const w = widest(labels);
|
|
676
853
|
const iw = widest(idx);
|
|
@@ -695,7 +872,7 @@ function matrixSvg(labels, idx, grid, footer, about) {
|
|
|
695
872
|
const n = labels.length;
|
|
696
873
|
parts.push(svgText(4, y0 + n * S + 16, 'av-m', footer));
|
|
697
874
|
const width = Math.max(gutter + n * S, 4 + footer.length * CH) + PAD;
|
|
698
|
-
return svgDoc(width, y0 + n * S + LH + PAD, about, parts);
|
|
875
|
+
return svgDoc(width, y0 + n * S + LH + PAD, about, parts, style);
|
|
699
876
|
}
|
|
700
877
|
// A node's field, as label text: the value of a scalar leaf at
|
|
701
878
|
// `path.field`, taken as its canon for anything but a string. A value
|
|
@@ -1001,19 +1178,32 @@ function drawLayer(triples, root, o, max, loss) {
|
|
|
1001
1178
|
? `${upward} upward edges, none drawn`
|
|
1002
1179
|
: `${upward} upward edges`;
|
|
1003
1180
|
return {
|
|
1004
|
-
text: layerSvg(bands, shown, footer, `Architecture layers${over(relation)}: ${bands.length} bands, ${drew}
|
|
1181
|
+
text: layerSvg(bands, shown, footer, `Architecture layers${over(relation)}: ${bands.length} bands, ${drew}`, o.style),
|
|
1005
1182
|
};
|
|
1006
1183
|
}
|
|
1007
1184
|
if ('text' === o.as) {
|
|
1185
|
+
const paint = painter(o.style);
|
|
1008
1186
|
const w = widest(bands.map((b) => b.name));
|
|
1009
|
-
const rows = bands.map((b) => pad(b.name, w) + ' ' +
|
|
1010
|
-
|
|
1011
|
-
const
|
|
1187
|
+
const rows = bands.map((b) => paint('muted', pad(b.name, w)) + ' ' +
|
|
1188
|
+
b.nodes.map((n) => n.label).join(' '));
|
|
1189
|
+
const inner = widest(bands.map((b) => pad(b.name, w) + ' ' + b.nodes.map((n) => n.label).join(' ')));
|
|
1190
|
+
const rule = paint('rule', '+' + '-'.repeat(inner + 2) + '+');
|
|
1012
1191
|
out.push(rule);
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1192
|
+
rows.forEach((row, i) => {
|
|
1193
|
+
// The row was padded from its UNPAINTED width, which the band
|
|
1194
|
+
// name's escapes do not change; `pad` would count them, so the
|
|
1195
|
+
// padding is computed here and appended.
|
|
1196
|
+
const bare = pad(bands[i].name, w) + ' ' +
|
|
1197
|
+
bands[i].nodes.map((n) => n.label).join(' ');
|
|
1198
|
+
out.push(paint('rule', '|') + ' ' + row +
|
|
1199
|
+
' '.repeat(inner - bare.length) + ' ' + paint('rule', '|'), rule);
|
|
1200
|
+
});
|
|
1201
|
+
// The first footer line counts; the rest name one edge each, and
|
|
1202
|
+
// an upward edge is the violation the bands cannot show.
|
|
1203
|
+
out.push(paint('muted', footer[0]));
|
|
1204
|
+
footer.slice(1).forEach((f, i) => {
|
|
1205
|
+
out.push(paint('upward' === shown[i].way ? 'upward' : 'muted', f));
|
|
1206
|
+
});
|
|
1017
1207
|
}
|
|
1018
1208
|
else {
|
|
1019
1209
|
const esc = (s) => escape(s, MERMAID_ESC);
|
|
@@ -1041,7 +1231,7 @@ function drawLayer(triples, root, o, max, loss) {
|
|
|
1041
1231
|
// dipped below the boxes, since two modules of one band sit on the
|
|
1042
1232
|
// same line and a straight edge between them would cross whatever
|
|
1043
1233
|
// stands between.
|
|
1044
|
-
function layerSvg(bands, shown, footer, about) {
|
|
1234
|
+
function layerSvg(bands, shown, footer, about, style) {
|
|
1045
1235
|
const BH = 44;
|
|
1046
1236
|
const gutter = widest(bands.map((b) => b.name)) * CH + 16;
|
|
1047
1237
|
const box = new Map();
|
|
@@ -1104,7 +1294,7 @@ function layerSvg(bands, shown, footer, about) {
|
|
|
1104
1294
|
footer.forEach((f, i) => {
|
|
1105
1295
|
parts.push(svgText(4, y1 + i * LH + 14, 'av-m', f));
|
|
1106
1296
|
});
|
|
1107
|
-
return svgDoc(width, y1 + footer.length * LH + PAD, about, parts);
|
|
1297
|
+
return svgDoc(width, y1 + footer.length * LH + PAD, about, parts, style);
|
|
1108
1298
|
}
|
|
1109
1299
|
// Elements grouped by their exact membership signature; columns by
|
|
1110
1300
|
// degree descending, then cardinality descending, then signature (the
|
|
@@ -1129,45 +1319,53 @@ function columnsOf(names, members, elements, shown) {
|
|
|
1129
1319
|
return [...groups.values()].sort((a, b) => degree(b) - degree(a) || b.items.length - a.items.length
|
|
1130
1320
|
|| (0, keyorder_1.cmpCodePoint)(sigText(a), sigText(b)));
|
|
1131
1321
|
}
|
|
1132
|
-
function renderPanel(p) {
|
|
1322
|
+
function renderPanel(p, style) {
|
|
1323
|
+
const paint = painter(style);
|
|
1133
1324
|
const w = widest(p.names);
|
|
1134
|
-
const out = [p.header, ''];
|
|
1325
|
+
const out = [paint('muted', p.header), ''];
|
|
1135
1326
|
const most = p.sizes.reduce((m, n) => Math.max(m, n), 0);
|
|
1136
1327
|
p.names.forEach((n, i) => {
|
|
1328
|
+
// The bar is padded to `most` from its own length, so the pad is
|
|
1329
|
+
// written outside the painted run rather than counted inside it.
|
|
1330
|
+
const bar = '#'.repeat(p.sizes[i]);
|
|
1137
1331
|
out.push(pad(n, w) + ' ' +
|
|
1138
|
-
(p.bars ?
|
|
1139
|
-
String(p.sizes[i]));
|
|
1332
|
+
(p.bars ? paint('bar', bar) + ' '.repeat(most - bar.length) + ' ' : '') +
|
|
1333
|
+
paint('muted', String(p.sizes[i])));
|
|
1140
1334
|
});
|
|
1141
1335
|
out.push('');
|
|
1142
1336
|
p.names.forEach((n, i) => {
|
|
1143
|
-
out.push(pad(n, w) + ' | ' +
|
|
1144
|
-
p.cols.map((c) => c.sig[i] ? '*' : '
|
|
1337
|
+
out.push(pad(n, w) + ' ' + paint('rule', '|') + ' ' +
|
|
1338
|
+
p.cols.map((c) => c.sig[i] ? paint('direct', '*') : paint('hole', '.'))
|
|
1339
|
+
.join(' '));
|
|
1145
1340
|
});
|
|
1146
|
-
out.push(pad('', w) + ' +' + '-'.repeat(2 * p.cols.length));
|
|
1341
|
+
out.push(pad('', w) + ' ' + paint('rule', '+' + '-'.repeat(2 * p.cols.length)));
|
|
1147
1342
|
if (p.bars) {
|
|
1148
1343
|
const tallest = p.cols.reduce((m, c) => Math.max(m, c.items.length), 0);
|
|
1149
|
-
// The bars, tallest column first; a line ends at its last bar.
|
|
1344
|
+
// The bars, tallest column first; a line ends at its last bar. The
|
|
1345
|
+
// trailing blanks are trimmed BEFORE painting, so an escape can
|
|
1346
|
+
// never be what the trim leaves behind.
|
|
1150
1347
|
for (let h = tallest; 0 < h; h--) {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1348
|
+
const cells = p.cols.map((c) => h <= c.items.length ? ' #' : ' ')
|
|
1349
|
+
.join('').replace(/ +$/, '');
|
|
1350
|
+
out.push(pad('', w) + ' ' + paint('rule', '|') +
|
|
1351
|
+
cells.replace(/#/g, () => paint('bar', '#')));
|
|
1154
1352
|
}
|
|
1155
1353
|
}
|
|
1156
1354
|
out.push(pad('', w) + ' ' +
|
|
1157
|
-
p.cols.map((c) => String(c.items.length)).join(' '));
|
|
1355
|
+
paint('muted', p.cols.map((c) => String(c.items.length)).join(' ')));
|
|
1158
1356
|
out.push('');
|
|
1159
1357
|
p.cols.forEach((c, i) => {
|
|
1160
1358
|
const shown = 4 < c.items.length && !p.bars
|
|
1161
1359
|
? c.items.slice(0, 3).join(' ') + ' ...' : c.items.join(' ');
|
|
1162
|
-
out.push(` col ${i + 1}${p.bars ? '' : ` (${c.items.length})`}
|
|
1163
|
-
(c.sig.some((b) => b) ? '' : p.none));
|
|
1360
|
+
out.push(paint('muted', ` col ${i + 1}${p.bars ? '' : ` (${c.items.length})`}:`) +
|
|
1361
|
+
` ${shown}` + (c.sig.some((b) => b) ? '' : paint('muted', p.none)));
|
|
1164
1362
|
});
|
|
1165
1363
|
return out.join('\n');
|
|
1166
1364
|
}
|
|
1167
1365
|
// The panel as SVG: the set sizes as bars, the intersections as a dot
|
|
1168
1366
|
// matrix (a filled dot where the set lies in the column), the column
|
|
1169
1367
|
// cardinalities as bars under it, and the columns' elements as text.
|
|
1170
|
-
function panelSvg(p, about) {
|
|
1368
|
+
function panelSvg(p, about, style) {
|
|
1171
1369
|
const w = widest(p.names);
|
|
1172
1370
|
const most = p.sizes.reduce((m, n) => Math.max(m, n), 0);
|
|
1173
1371
|
const parts = [svgText(4, 14, 'av-m', p.header)];
|
|
@@ -1208,7 +1406,7 @@ function panelSvg(p, about) {
|
|
|
1208
1406
|
parts.push(svgText(4, yI + i * LH + 14, 'av-t', l));
|
|
1209
1407
|
});
|
|
1210
1408
|
const width = Math.max(gx + p.cols.length * 20, gx + (p.bars ? most * 10 + 8 : 0) + 3 * CH, 4 + widest(lines) * CH, 4 + p.header.length * CH) + PAD;
|
|
1211
|
-
return svgDoc(width, yI + lines.length * LH + PAD, about, parts);
|
|
1409
|
+
return svgDoc(width, yI + lines.length * LH + PAD, about, parts, style);
|
|
1212
1410
|
}
|
|
1213
1411
|
// Elide the columns beyond `--max-cols`, counted. Zero means no limit,
|
|
1214
1412
|
// in both ports.
|
|
@@ -1307,18 +1505,18 @@ function drawSets(gen, o, max, loss) {
|
|
|
1307
1505
|
return {
|
|
1308
1506
|
text: 'svg' === o.as
|
|
1309
1507
|
? panelSvg(panel, `Set panel over ${o.sets}: ${names.length} sets, ` +
|
|
1310
|
-
`${elements.size} elements, ${cols.length} intersections
|
|
1311
|
-
: renderPanel(panel),
|
|
1508
|
+
`${elements.size} elements, ${cols.length} intersections`, o.style)
|
|
1509
|
+
: renderPanel(panel, o.style),
|
|
1312
1510
|
};
|
|
1313
1511
|
}
|
|
1314
1512
|
// The file a contribution names, as the panel shows it: relative to
|
|
1315
1513
|
// the entry document's directory, the entry itself by its own name.
|
|
1316
1514
|
function docName(file, entry) {
|
|
1317
1515
|
if ('' === file || file === entry) {
|
|
1318
|
-
return undefined === entry ? '-' : (0,
|
|
1516
|
+
return undefined === entry ? '-' : (0, node_path_1.basename)(entry);
|
|
1319
1517
|
}
|
|
1320
|
-
return (0,
|
|
1321
|
-
? (0,
|
|
1518
|
+
return (0, node_path_1.isAbsolute)(file) && undefined !== entry
|
|
1519
|
+
? (0, node_path_1.relative)((0, node_path_1.dirname)((0, node_path_1.resolve)(entry)), file) : file;
|
|
1322
1520
|
}
|
|
1323
1521
|
function drawLayers(prov, root, entry, o, max, loss) {
|
|
1324
1522
|
// Every path something met at AND THE DOCUMENT HAS A VALUE AT,
|
|
@@ -1365,7 +1563,7 @@ function drawLayers(prov, root, entry, o, max, loss) {
|
|
|
1365
1563
|
}
|
|
1366
1564
|
cols = elide(cols, o.maxCols, loss);
|
|
1367
1565
|
const panel = {
|
|
1368
|
-
header: `# layers file=${undefined === entry ? '-' : (0,
|
|
1566
|
+
header: `# layers file=${undefined === entry ? '-' : (0, node_path_1.basename)(entry)}` +
|
|
1369
1567
|
` documents=${names.length} paths=${paths.length}`,
|
|
1370
1568
|
names,
|
|
1371
1569
|
sizes: names.map((n) => members.get(n).size),
|
|
@@ -1376,8 +1574,8 @@ function drawLayers(prov, root, entry, o, max, loss) {
|
|
|
1376
1574
|
return {
|
|
1377
1575
|
text: 'svg' === o.as
|
|
1378
1576
|
? panelSvg(panel, `Document layers: ${names.length} documents, ` +
|
|
1379
|
-
`${paths.length} paths, ${cols.length} intersections
|
|
1380
|
-
: renderPanel(panel),
|
|
1577
|
+
`${paths.length} paths, ${cols.length} intersections`, o.style)
|
|
1578
|
+
: renderPanel(panel, o.style),
|
|
1381
1579
|
};
|
|
1382
1580
|
}
|
|
1383
1581
|
// ---------------------------------------------------------------------
|
|
@@ -1406,7 +1604,7 @@ function drawLadder(src, options, as, max) {
|
|
|
1406
1604
|
if (max < rungs.length) {
|
|
1407
1605
|
return { errors: [rowsFinding(rungs.length, max, 'a narrower --at')] };
|
|
1408
1606
|
}
|
|
1409
|
-
const where = (c) => `${(0,
|
|
1607
|
+
const where = (c) => `${(0, node_path_1.basename)(c.site.file)}:${c.site.row}:${c.site.col}`;
|
|
1410
1608
|
const out = [];
|
|
1411
1609
|
if ('mermaid' === as) {
|
|
1412
1610
|
const esc = (s) => escape(s, MERMAID_ESC);
|
|
@@ -1655,6 +1853,24 @@ function view(src, opts, hooks) {
|
|
|
1655
1853
|
errors: [finding('view_profile_unknown', 'reference', '$', `The ${kind} figure does not render as ${as}.`, `profiles: ${profiles.join(', ')}`)],
|
|
1656
1854
|
});
|
|
1657
1855
|
}
|
|
1856
|
+
// ONE MECHANISM PER PROFILE (VIEWS.0.md, "7. Styling"). `ansi` is
|
|
1857
|
+
// the text profile's and `css` the SVG's; asking for one on a
|
|
1858
|
+
// profile that has no way to carry it is a usage error rather than a
|
|
1859
|
+
// silent no-op, so a script that asks for colour and gets none is
|
|
1860
|
+
// told why. `none` is always available -- it is the absence of a
|
|
1861
|
+
// mechanism.
|
|
1862
|
+
const style = styleOf(options.style, as);
|
|
1863
|
+
const carrier = { ansi: 'text', css: 'svg' };
|
|
1864
|
+
if (undefined !== carrier[style] && carrier[style] !== as) {
|
|
1865
|
+
return done({
|
|
1866
|
+
errors: [finding('view_style_profile', 'reference', '$', `The ${as} profile cannot carry --style ${style}.`, `${style} is the ${carrier[style]} profile's mechanism`)],
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
if (undefined === carrier[style] && 'none' !== style) {
|
|
1870
|
+
return done({
|
|
1871
|
+
errors: [finding('view_style_unknown', 'reference', '$', `${style} is not a style.`, 'styles: auto, none, ansi, css')],
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1658
1874
|
// Zero means the default, in both ports.
|
|
1659
1875
|
const max = options.maxRows || DEFAULT_MAX_ROWS;
|
|
1660
1876
|
if ('poset' === kind) {
|
|
@@ -1662,7 +1878,7 @@ function view(src, opts, hooks) {
|
|
|
1662
1878
|
.map((d, i) => ({
|
|
1663
1879
|
src: d.src, path: d.path,
|
|
1664
1880
|
label: d.name ?? (undefined === d.path
|
|
1665
|
-
? `doc${i + 1}` : (0,
|
|
1881
|
+
? `doc${i + 1}` : (0, node_path_1.basename)(d.path).replace(/\.aon$/, '')),
|
|
1666
1882
|
}));
|
|
1667
1883
|
return done(drawPoset(docs, options, as, max, loss, compare));
|
|
1668
1884
|
}
|
|
@@ -1685,8 +1901,12 @@ function view(src, opts, hooks) {
|
|
|
1685
1901
|
// that a document generating `undefined` is still a value the panel
|
|
1686
1902
|
// has rather than one it must recompute.
|
|
1687
1903
|
function drawLoaded(root, ctx, gen, prov, kind, as, options, max, loss) {
|
|
1904
|
+
const style = styleOf(options.style, as);
|
|
1905
|
+
if ('doc' === kind) {
|
|
1906
|
+
return drawDoc(root, { ...options, as, style }, max, loss);
|
|
1907
|
+
}
|
|
1688
1908
|
if ('layers' === kind) {
|
|
1689
|
-
return drawLayers(prov, root, options.path, { ...options, as }, max, loss);
|
|
1909
|
+
return drawLayers(prov, root, options.path, { ...options, as, style }, max, loss);
|
|
1690
1910
|
}
|
|
1691
1911
|
if ('sets' === kind) {
|
|
1692
1912
|
if (undefined === options.sets || undefined === options.member) {
|
|
@@ -1710,7 +1930,7 @@ function drawLoaded(root, ctx, gen, prov, kind, as, options, max, loss) {
|
|
|
1710
1930
|
}
|
|
1711
1931
|
return drawSets(value, {
|
|
1712
1932
|
sets: options.sets, member: options.member, universe: options.universe,
|
|
1713
|
-
minDegree: options.minDegree, maxCols: options.maxCols, as,
|
|
1933
|
+
minDegree: options.minDegree, maxCols: options.maxCols, as, style,
|
|
1714
1934
|
}, max, loss);
|
|
1715
1935
|
}
|
|
1716
1936
|
const triples = triplesOf((0, graph_1.graphOf)(root), options.at, loss);
|
|
@@ -1721,7 +1941,7 @@ function drawLoaded(root, ctx, gen, prov, kind, as, options, max, loss) {
|
|
|
1721
1941
|
if ('matrix' === kind) {
|
|
1722
1942
|
return drawMatrix(triples, decls, {
|
|
1723
1943
|
relation, order: options.order ?? 'canon', closure: true === options.closure,
|
|
1724
|
-
as,
|
|
1944
|
+
as, style,
|
|
1725
1945
|
}, max, loss);
|
|
1726
1946
|
}
|
|
1727
1947
|
if ('graph' === kind) {
|
|
@@ -1733,10 +1953,10 @@ function drawLoaded(root, ctx, gen, prov, kind, as, options, max, loss) {
|
|
|
1733
1953
|
if ('layer' === kind) {
|
|
1734
1954
|
return drawLayer(triples, root, {
|
|
1735
1955
|
relation, groupBy: options.groupBy, layers: options.layers ?? [],
|
|
1736
|
-
edges: options.edges, as,
|
|
1956
|
+
edges: options.edges, as, style,
|
|
1737
1957
|
}, max, loss);
|
|
1738
1958
|
}
|
|
1739
|
-
return drawTree(collapse(triples, relation), relation, options.roots ?? [], max, as);
|
|
1959
|
+
return drawTree(collapse(triples, relation), relation, options.roots ?? [], max, as, style);
|
|
1740
1960
|
}
|
|
1741
1961
|
// The tree view of one document: `view` with the kind fixed.
|
|
1742
1962
|
function viewTree(src, opts) {
|
|
@@ -1766,7 +1986,7 @@ const DECL_ENUM = {
|
|
|
1766
1986
|
order: ['canon', 'partition'],
|
|
1767
1987
|
edges: ['upward', 'all', 'none'],
|
|
1768
1988
|
};
|
|
1769
|
-
const DECL_COUNT = ['maxRows', 'maxCols', 'minDegree', 'minSize'];
|
|
1989
|
+
const DECL_COUNT = ['maxRows', 'maxCols', 'minDegree', 'minSize', 'depth'];
|
|
1770
1990
|
const DECL_FLAG = ['closure'];
|
|
1771
1991
|
const DECL_LIST = ['roots', 'relations', 'layers'];
|
|
1772
1992
|
const DECL_KEYS = [...DECL_TEXT, ...DECL_COUNT, ...DECL_FLAG, ...DECL_LIST]
|