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/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 path_1 = require("path");
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 ? '└── ' : '├── ') + text + mark);
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 { text: 'svg' === as ? treeSvg(rows, nodes.length) : out.join('\n') };
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, count) {
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, `Dependency tree: ${count} nodes`, parts);
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) + ' ' + cells.join(' '));
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
- function matrixSvg(labels, idx, grid, footer, about) {
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) + ' ' + b.nodes.map((n) => n.label).join(' '));
1010
- const inner = widest(rows);
1011
- const rule = '+' + '-'.repeat(inner + 2) + '+';
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
- for (const row of rows) {
1014
- out.push('| ' + pad(row, inner) + ' |', rule);
1015
- }
1016
- out.push(...footer);
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 ? pad('#'.repeat(p.sizes[i]), most) + ' ' : '') +
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] ? '*' : '.').join(' '));
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
- out.push((pad('', w) + ' |' +
1152
- p.cols.map((c) => h <= c.items.length ? ' #' : ' ').join(''))
1153
- .replace(/ +$/, ''));
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})`}: ${shown}` +
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, path_1.basename)(entry);
1516
+ return undefined === entry ? '-' : (0, node_path_1.basename)(entry);
1319
1517
  }
1320
- return (0, path_1.isAbsolute)(file) && undefined !== entry
1321
- ? (0, path_1.relative)((0, path_1.dirname)((0, path_1.resolve)(entry)), file) : file;
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, path_1.basename)(entry)}` +
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, path_1.basename)(c.site.file)}:${c.site.row}:${c.site.col}`;
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, path_1.basename)(d.path).replace(/\.aon$/, '')),
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]