aontu 0.57.0 → 0.59.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.
Files changed (132) hide show
  1. package/README.md +2 -2
  2. package/dist/agentsmd.js +1 -1
  3. package/dist/alias.d.ts +3 -0
  4. package/dist/alias.js +59 -0
  5. package/dist/alias.js.map +1 -0
  6. package/dist/aontu.d.ts +4 -2
  7. package/dist/aontu.js +12 -3
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +3 -1
  10. package/dist/cli.js +486 -4
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +4 -0
  13. package/dist/ctx.js +3 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/err.js +6 -3
  16. package/dist/err.js.map +1 -1
  17. package/dist/format.js +8 -2
  18. package/dist/format.js.map +1 -1
  19. package/dist/hints.js +40 -9
  20. package/dist/hints.js.map +1 -1
  21. package/dist/lang.js +409 -58
  22. package/dist/lang.js.map +1 -1
  23. package/dist/lower.d.ts +20 -0
  24. package/dist/lower.js +575 -0
  25. package/dist/lower.js.map +1 -0
  26. package/dist/lsp.d.ts +1 -1
  27. package/dist/lsp.js +1 -1
  28. package/dist/lsp.js.map +1 -1
  29. package/dist/mcp-server.js +1 -1
  30. package/dist/mcp.d.ts +1 -0
  31. package/dist/mcp.js +40 -3
  32. package/dist/mcp.js.map +1 -1
  33. package/dist/render.d.ts +53 -0
  34. package/dist/render.js +542 -0
  35. package/dist/render.js.map +1 -0
  36. package/dist/sigdecl.js +1 -1
  37. package/dist/sigdecl.js.map +1 -1
  38. package/dist/std.d.ts +2 -0
  39. package/dist/std.js +498 -2
  40. package/dist/std.js.map +1 -1
  41. package/dist/template.d.ts +5 -0
  42. package/dist/template.js +257 -0
  43. package/dist/template.js.map +1 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/dist/type.d.ts +2 -0
  46. package/dist/type.js.map +1 -1
  47. package/dist/unify.js +43 -0
  48. package/dist/unify.js.map +1 -1
  49. package/dist/val/AggFuncVal.d.ts +1 -1
  50. package/dist/val/AggFuncVal.js +10 -21
  51. package/dist/val/AggFuncVal.js.map +1 -1
  52. package/dist/val/BagVal.js +1 -1
  53. package/dist/val/BagVal.js.map +1 -1
  54. package/dist/val/ConstraintVal.js +1 -1
  55. package/dist/val/DisjunctVal.js +33 -2
  56. package/dist/val/DisjunctVal.js.map +1 -1
  57. package/dist/val/EachFuncVal.d.ts +1 -1
  58. package/dist/val/EachFuncVal.js +8 -13
  59. package/dist/val/EachFuncVal.js.map +1 -1
  60. package/dist/val/EmitFuncVal.d.ts +23 -4
  61. package/dist/val/EmitFuncVal.js +298 -28
  62. package/dist/val/EmitFuncVal.js.map +1 -1
  63. package/dist/val/FilterFuncVal.js +8 -4
  64. package/dist/val/FilterFuncVal.js.map +1 -1
  65. package/dist/val/FormFuncVal.d.ts +14 -0
  66. package/dist/val/FormFuncVal.js +55 -0
  67. package/dist/val/FormFuncVal.js.map +1 -0
  68. package/dist/val/FuncBaseVal.js +36 -13
  69. package/dist/val/FuncBaseVal.js.map +1 -1
  70. package/dist/val/ListVal.js +9 -1
  71. package/dist/val/ListVal.js.map +1 -1
  72. package/dist/val/MapVal.d.ts +2 -1
  73. package/dist/val/MapVal.js +2 -1
  74. package/dist/val/MapVal.js.map +1 -1
  75. package/dist/val/PackFuncVal.d.ts +1 -1
  76. package/dist/val/PackFuncVal.js +22 -18
  77. package/dist/val/PackFuncVal.js.map +1 -1
  78. package/dist/val/PlaceVal.js +7 -2
  79. package/dist/val/PlaceVal.js.map +1 -1
  80. package/dist/val/RefVal.d.ts +3 -0
  81. package/dist/val/RefVal.js +181 -18
  82. package/dist/val/RefVal.js.map +1 -1
  83. package/dist/val/Val.d.ts +8 -1
  84. package/dist/val/Val.js +39 -1
  85. package/dist/val/Val.js.map +1 -1
  86. package/dist/val/members.d.ts +9 -0
  87. package/dist/val/members.js +52 -0
  88. package/dist/val/members.js.map +1 -0
  89. package/grammar/aontu.abnf +1 -1
  90. package/grammar/aontu.gbnf +1 -1
  91. package/grammar/aontu.lark +1 -1
  92. package/grammar/aontu.tmLanguage.json +1 -1
  93. package/package.json +1 -1
  94. package/skill/SKILL.md +4 -4
  95. package/skill/error-codes.md +1 -1
  96. package/skill/examples.md +1 -1
  97. package/skill/grammar-card.md +1 -1
  98. package/src/agentsmd.ts +1 -1
  99. package/src/alias.ts +112 -0
  100. package/src/aontu.ts +20 -2
  101. package/src/cli.ts +528 -4
  102. package/src/ctx.ts +18 -0
  103. package/src/err.ts +6 -3
  104. package/src/format.ts +12 -2
  105. package/src/hints.ts +47 -9
  106. package/src/lang.ts +453 -63
  107. package/src/lower.ts +636 -0
  108. package/src/lsp.ts +1 -1
  109. package/src/mcp-server.ts +1 -1
  110. package/src/mcp.ts +43 -4
  111. package/src/render.ts +727 -0
  112. package/src/sigdecl.ts +1 -1
  113. package/src/std.ts +506 -1
  114. package/src/template.ts +291 -0
  115. package/src/type.ts +10 -1
  116. package/src/unify.ts +47 -0
  117. package/src/val/AggFuncVal.ts +10 -21
  118. package/src/val/BagVal.ts +1 -1
  119. package/src/val/ConstraintVal.ts +1 -1
  120. package/src/val/DisjunctVal.ts +33 -2
  121. package/src/val/EachFuncVal.ts +8 -16
  122. package/src/val/EmitFuncVal.ts +376 -39
  123. package/src/val/FilterFuncVal.ts +11 -4
  124. package/src/val/FormFuncVal.ts +119 -0
  125. package/src/val/FuncBaseVal.ts +36 -13
  126. package/src/val/ListVal.ts +9 -1
  127. package/src/val/MapVal.ts +3 -2
  128. package/src/val/PackFuncVal.ts +23 -19
  129. package/src/val/PlaceVal.ts +7 -2
  130. package/src/val/RefVal.ts +193 -20
  131. package/src/val/Val.ts +75 -4
  132. package/src/val/members.ts +86 -0
package/dist/cli.js CHANGED
@@ -13,6 +13,8 @@ exports.runRelations = runRelations;
13
13
  exports.runReaches = runReaches;
14
14
  exports.runView = runView;
15
15
  exports.runJsonSchema = runJsonSchema;
16
+ exports.runRender = runRender;
17
+ exports.runTemplate = runTemplate;
16
18
  exports.runMod = runMod;
17
19
  exports.runHash = runHash;
18
20
  exports.runGet = runGet;
@@ -39,6 +41,8 @@ const node_path_1 = require("node:path");
39
41
  const node_os_1 = require("node:os");
40
42
  const node_readline_1 = require("node:readline");
41
43
  const aontu_1 = require("./aontu");
44
+ const template_1 = require("./template");
45
+ const mcp_1 = require("./mcp");
42
46
  const report_sarif_1 = require("./report-sarif");
43
47
  const lsp_server_1 = require("./lsp-server");
44
48
  const mcp_server_1 = require("./mcp-server");
@@ -61,6 +65,10 @@ const HELP = `Usage: aontu [options] [file]
61
65
  aontu view <kind> [options] <file>...
62
66
  aontu view --views <path> [--check] [options] <file>
63
67
  aontu jsonschema [--at <path>] [--strict] [options] <file>
68
+ aontu render [--at <path>] [--profile <file>]... [--unit <path>]
69
+ [--stdout | --out <dir> | --check <dir> | --coverage]
70
+ [--coverage-at <path>] [--strict] <file>
71
+ aontu template [--resugar] [--check] [--marker <token>] <file>
64
72
  aontu hash [options] <file>
65
73
  aontu mod tidy|verify|vendor|manifest [options] [dir]
66
74
  aontu get <path> [options] <file>
@@ -71,7 +79,7 @@ const HELP = `Usage: aontu [options] [file]
71
79
  aontu lsp
72
80
  aontu mcp [--root <dir>]
73
81
 
74
- Evaluate an Aontu source file and print the result as JSON.
82
+ Evaluate an aontu source file and print the result as JSON.
75
83
  With no file on an interactive terminal, start a REPL.
76
84
  With no file and piped input, read the source from stdin.
77
85
 
@@ -253,6 +261,51 @@ View exit codes: 0 rendered, 1 --check mismatch or lossy under
253
261
  --strict, 2 usage or --max-rows exceeded, 4 the document does not stand
254
262
  up on its own, or a relation, root or path that names nothing.
255
263
 
264
+ Render options:
265
+ --at <path> Render the value at this path ($.a.b); the root by
266
+ default
267
+ --profile <file> A profile document, profile: {lang, ...}, vetted
268
+ against aontu:profile; repeatable, one per language
269
+ --unit <path> Render only the unit with this path
270
+ --stdout One unit's bytes and nothing else (with --unit when
271
+ the instance has several)
272
+ --out <dir> Write every unit below dir, or nothing; never deletes
273
+ --check <dir> Compare every unit with dir/<path>; drift is listed
274
+ --coverage Report what the render read and what it did not:
275
+ model paths no output consumed, and rendered
276
+ declarations no rule produced. Writes nothing
277
+ --coverage-at <p> Measure coverage under this path only, instead of
278
+ the document root
279
+ --strict Refuse the opaque escapes (a text declaration, a raw
280
+ block)
281
+ --format <f> text (default) or json, the whole report; json
282
+ carries the dispatch trace, one entry per emitted
283
+ piece
284
+
285
+ Render exit codes: 0 rendered, 1 lossy under --strict or drift under
286
+ --check, 2 usage or I/O (a refused unit path included), 4 the document
287
+ does not stand up or the instance is not aontu:code.
288
+
289
+ A render entry file whose extension is not .aon is a TEMPLATE: a
290
+ generator in the target's own syntax, whose marker lines carry aontu
291
+ and whose other lines are output. It is desugared before it is
292
+ evaluated, and --marker names the marker for a language the table does
293
+ not know.
294
+
295
+ Template options:
296
+ --resugar The file is the canonical aontu; print the template
297
+ form instead of reading one
298
+ --check Desugar and resugar, and exit 1 if the file is not
299
+ what the round trip answers
300
+ --marker <t> The marker, when the extension does not name it
301
+ (default //-, and #- --- /*- by extension)
302
+
303
+ The template verb prints the canonical aontu form of a generator
304
+ written in the target's own syntax: a marked line is aontu source, and
305
+ every other line is a line of output.
306
+
307
+ Template exit codes: 0 written, 1 --check drift, 2 usage or I/O.
308
+
256
309
  Set options:
257
310
  --entry <file> The document the change is checked against
258
311
  --overlay <file> The file the change is appended to (created if
@@ -491,7 +544,17 @@ function runFile(file, mode, trust) {
491
544
  return 1;
492
545
  }
493
546
  const path = (0, node_path_1.resolve)(file);
494
- const aontu = new aontu_1.Aontu({ path, ...trustOpts(trust, (0, node_path_1.dirname)(path)) });
547
+ // `fs` IS WHAT MAKES A FRAME EXCERPT THE FILE IT NAMES. Without it,
548
+ // err.ts's resolveSrc falls back to the ENTRY text, so a frame whose
549
+ // arrow says `lib/types.aon:2:6` printed the entry's line 2 under it
550
+ // -- a real file name over another file's line, which
551
+ // docs/reference-api.md forbids in the same words it uses to require
552
+ // the name.
553
+ const aontu = new aontu_1.Aontu({
554
+ path,
555
+ errfs: { existsSync: node_fs_1.existsSync, readFileSync: node_fs_1.readFileSync },
556
+ ...trustOpts(trust, (0, node_path_1.dirname)(path)),
557
+ });
495
558
  const res = evalSource(aontu, src, mode);
496
559
  (res.ok ? process.stdout : process.stderr).write(res.text + '\n');
497
560
  return res.ok ? 0 : 1;
@@ -609,7 +672,7 @@ function runRepl(initialMode, jsonl, trust) {
609
672
  prompt: jsonl ? '' : 'aontu> ',
610
673
  });
611
674
  if (!jsonl) {
612
- process.stdout.write(`Aontu v${version()} REPL — :help for commands, :quit to exit\n`);
675
+ process.stdout.write(`aontu v${version()} REPL — :help for commands, :quit to exit\n`);
613
676
  }
614
677
  rl.prompt();
615
678
  rl.on('line', (line) => {
@@ -2432,6 +2495,403 @@ function runJsonSchema(argv) {
2432
2495
  strict && 'lossy' === report.verdict ? 1 : 0;
2433
2496
  }
2434
2497
  // ---------------------------------------------------------------------
2498
+ // THE RENDER VERB (docs/design/RENDER.0.md D8): evaluate a document,
2499
+ // vet the value at --at against aontu:code, fold code.units into bytes,
2500
+ // and put them where the flag says -- one unit on stdout, every unit
2501
+ // below --out (all or nothing), or compared against --check. Exit codes
2502
+ // mirror jsonschema's: 0 ok; 1 lossy under --strict or drift under
2503
+ // --check; 2 usage or I/O, a refused unit path included; 4 the
2504
+ // document does not stand up or the instance is not aontu:code.
2505
+ const RENDER_HELP = 'aontu render [--at <path>] [--profile <file>]... [--unit <path>] ' +
2506
+ '[--stdout | --out <dir> | --check <dir> | --coverage] ' +
2507
+ '[--coverage-at <path>] [--strict] [--marker <token>] <file> (try --help)';
2508
+ function runRender(argv) {
2509
+ const trusted = takeTrust(argv);
2510
+ if (null == trusted) {
2511
+ return 2;
2512
+ }
2513
+ argv = trusted.argv;
2514
+ const trust = trusted.trust;
2515
+ const files = [];
2516
+ const profileFiles = [];
2517
+ let format = 'text';
2518
+ let at = undefined;
2519
+ let unit = undefined;
2520
+ let out = undefined;
2521
+ let check = undefined;
2522
+ let toStdout = false;
2523
+ let strict = false;
2524
+ let coverage = false;
2525
+ let coverageAt = undefined;
2526
+ let marker = undefined;
2527
+ for (let i = 0; i < argv.length; i++) {
2528
+ const arg = argv[i];
2529
+ if ('-h' === arg || '--help' === arg) {
2530
+ process.stdout.write(HELP);
2531
+ return 0;
2532
+ }
2533
+ if ('--format' === arg) {
2534
+ const f = argv[++i];
2535
+ if ('text' !== f && 'json' !== f) {
2536
+ process.stderr.write('aontu: --format needs text or json\n');
2537
+ return 2;
2538
+ }
2539
+ format = f;
2540
+ }
2541
+ else if ('--at' === arg) {
2542
+ at = argv[++i];
2543
+ if (null == at) {
2544
+ process.stderr.write('aontu: --at needs a path\n');
2545
+ return 2;
2546
+ }
2547
+ }
2548
+ else if ('--unit' === arg) {
2549
+ unit = argv[++i];
2550
+ if (null == unit) {
2551
+ process.stderr.write('aontu: --unit needs a unit path\n');
2552
+ return 2;
2553
+ }
2554
+ }
2555
+ else if ('--profile' === arg) {
2556
+ const pf = argv[++i];
2557
+ if (null == pf) {
2558
+ process.stderr.write('aontu: --profile needs a file\n');
2559
+ return 2;
2560
+ }
2561
+ profileFiles.push(pf);
2562
+ }
2563
+ else if ('--out' === arg) {
2564
+ out = argv[++i];
2565
+ if (null == out) {
2566
+ process.stderr.write('aontu: --out needs a directory\n');
2567
+ return 2;
2568
+ }
2569
+ }
2570
+ else if ('--check' === arg) {
2571
+ check = argv[++i];
2572
+ if (null == check) {
2573
+ process.stderr.write('aontu: --check needs a directory\n');
2574
+ return 2;
2575
+ }
2576
+ }
2577
+ else if ('--stdout' === arg) {
2578
+ toStdout = true;
2579
+ }
2580
+ else if ('--coverage' === arg) {
2581
+ coverage = true;
2582
+ }
2583
+ else if ('--marker' === arg) {
2584
+ marker = argv[++i];
2585
+ if (null == marker) {
2586
+ process.stderr.write('aontu: --marker needs a token\n');
2587
+ return 2;
2588
+ }
2589
+ }
2590
+ else if ('--coverage-at' === arg) {
2591
+ coverageAt = argv[++i];
2592
+ if (null == coverageAt) {
2593
+ process.stderr.write('aontu: --coverage-at needs a path\n');
2594
+ return 2;
2595
+ }
2596
+ }
2597
+ else if ('--strict' === arg) {
2598
+ strict = true;
2599
+ }
2600
+ else if (arg.startsWith('-')) {
2601
+ process.stderr.write(`aontu: unknown render option ${arg} (try --help)\n`);
2602
+ return 2;
2603
+ }
2604
+ else {
2605
+ files.push(arg);
2606
+ }
2607
+ }
2608
+ if (1 !== files.length) {
2609
+ process.stderr.write(`aontu: render needs one file\n${RENDER_HELP}\n`);
2610
+ return 2;
2611
+ }
2612
+ const modes = [toStdout, undefined !== out, undefined !== check, coverage]
2613
+ .filter((on) => on).length;
2614
+ if (1 < modes) {
2615
+ process.stderr.write('aontu: render takes one of --stdout, --out, --check or --coverage\n');
2616
+ return 2;
2617
+ }
2618
+ // A NARROWER MEASURE NEEDS SOMETHING TO NARROW. `--coverage-at`
2619
+ // without `--coverage` asks for a report the run does not compute,
2620
+ // and answering silently would be the wrong half of the request.
2621
+ if (undefined !== coverageAt && !coverage) {
2622
+ process.stderr.write('aontu: --coverage-at needs --coverage\n');
2623
+ return 2;
2624
+ }
2625
+ let src;
2626
+ try {
2627
+ src = (0, node_fs_1.readFileSync)(files[0], 'utf8');
2628
+ }
2629
+ catch (err) {
2630
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
2631
+ return 2;
2632
+ }
2633
+ // THE ENTRY MAY BE A TEMPLATE (TEMPLATE.0.md; P8), and its EXTENSION
2634
+ // decides, as an include's extension decides what the include is
2635
+ // (ADR-012): a generator is a file in the target's own syntax, so it
2636
+ // carries the target's extension and never `.aon`. Desugared here
2637
+ // rather than anywhere deeper, because a template is an entry
2638
+ // spelling and not a value: an include is still aontu.
2639
+ if (!files[0].endsWith('.aon')) {
2640
+ src = (0, template_1.desugarTemplate)(src, marker ?? (0, template_1.markerFor)(files[0]));
2641
+ }
2642
+ // THE PROFILES (D5): each --profile file is a document whose root is
2643
+ // `profile: {lang, ...}`, evaluated under the verb's trust and vetted
2644
+ // against aontu:profile as a settled value before the fold reads it
2645
+ // (renderProfile, which also fills the defaults). Two files claiming
2646
+ // one lang is a usage error: the fold could not choose.
2647
+ const profiles = [];
2648
+ const langs = new Map();
2649
+ for (const pf of profileFiles) {
2650
+ let text;
2651
+ try {
2652
+ text = (0, node_fs_1.readFileSync)(pf, 'utf8');
2653
+ }
2654
+ catch (err) {
2655
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
2656
+ return 2;
2657
+ }
2658
+ const loaded = (0, aontu_1.renderProfile)(text, { path: (0, node_path_1.resolve)(pf), ...verbOpts(trust, entryRootOf(pf)) });
2659
+ if (undefined !== loaded.errors) {
2660
+ process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n');
2661
+ return 4;
2662
+ }
2663
+ const profile = loaded.profile;
2664
+ const prev = langs.get(profile.lang);
2665
+ if (undefined !== prev) {
2666
+ process.stderr.write(`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`);
2667
+ return 2;
2668
+ }
2669
+ langs.set(profile.lang, pf);
2670
+ profiles.push(profile);
2671
+ }
2672
+ const report = (0, aontu_1.render)(src, {
2673
+ at, unit, strict, profiles, path: files[0],
2674
+ coverage, coverageAt,
2675
+ // THE JSON REPORT CARRIES THE TRACE (D9), which is what the shape
2676
+ // there has always said; a text run computes it only when the
2677
+ // coverage report needs it.
2678
+ trace: 'json' === format,
2679
+ ...verbOpts(trust, entryRootOf(files[0])),
2680
+ });
2681
+ if ('json' === format) {
2682
+ process.stdout.write((0, aontu_1.exactJSON)({
2683
+ aontu: { version: version(), verb: 'render' },
2684
+ verdict: report.verdict,
2685
+ units: report.units,
2686
+ lossy: report.lossy,
2687
+ ...(null == report.errors ? {} : { errors: report.errors }),
2688
+ ...(null == report.trace ? {} : { trace: report.trace }),
2689
+ ...(null == report.coverage ? {} : { coverage: report.coverage }),
2690
+ }, 2) + '\n');
2691
+ return renderExit(report, 0);
2692
+ }
2693
+ if ('error' === report.verdict) {
2694
+ process.stderr.write(report.errors.map(renderFinding).join('\n') + '\n');
2695
+ return renderExit(report, 0);
2696
+ }
2697
+ let drift = 0;
2698
+ if (toStdout) {
2699
+ // ONE UNIT'S BYTES AND NOTHING ELSE, so the output can be piped
2700
+ // into a formatter or a file.
2701
+ if (1 !== report.units.length) {
2702
+ process.stderr.write('aontu: --stdout needs exactly one unit, and the instance has ' +
2703
+ `${report.units.length}; --unit names one\n`);
2704
+ return 2;
2705
+ }
2706
+ process.stdout.write(report.units[0].text);
2707
+ }
2708
+ else if (undefined !== out) {
2709
+ // EVERY UNIT BELOW <dir>, OR NOTHING: every unit rendered first
2710
+ // (the report above), and no file touched unless all did. The
2711
+ // directory is realpath-confined; a unit path is already a relative
2712
+ // descent (render_path refuses the rest), and the check here is
2713
+ // against the symlink inside it. render never deletes.
2714
+ for (const u of report.units) {
2715
+ if ((0, mcp_1.outsideRoot)(out, (0, node_path_1.resolve)(out, u.path))) {
2716
+ process.stderr.write(`aontu: ${u.path} escapes ${out}\n`);
2717
+ return 2;
2718
+ }
2719
+ }
2720
+ for (const u of report.units) {
2721
+ const full = (0, node_path_1.resolve)(out, u.path);
2722
+ try {
2723
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(full), { recursive: true });
2724
+ (0, node_fs_1.writeFileSync)(full, u.text, 'utf8');
2725
+ }
2726
+ catch (err) {
2727
+ process.stderr.write(`aontu: cannot write ${u.path}: ${err.message}\n`);
2728
+ return 2;
2729
+ }
2730
+ process.stderr.write(`wrote ${u.path}\n`);
2731
+ }
2732
+ }
2733
+ else if (undefined !== check) {
2734
+ // RENDER AND COMPARE: a unit whose bytes differ from the file at
2735
+ // <dir>/<path>, or whose file is absent, is drift, listed by path.
2736
+ // The CI form.
2737
+ for (const u of report.units) {
2738
+ let have = undefined;
2739
+ try {
2740
+ have = (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(check, u.path), 'utf8');
2741
+ }
2742
+ catch {
2743
+ // Absent is drift, reported below.
2744
+ }
2745
+ if (undefined === have) {
2746
+ drift++;
2747
+ process.stderr.write(`aontu: ${u.path} is missing from ${check}\n`);
2748
+ }
2749
+ else if (have !== u.text) {
2750
+ drift++;
2751
+ process.stderr.write(`aontu: ${u.path} differs from the rendered unit\n`);
2752
+ }
2753
+ }
2754
+ }
2755
+ else if (coverage) {
2756
+ // THE COVERAGE REPORT (P7), one line per finding and a count at
2757
+ // the end: dead model first, then the declarations no rule
2758
+ // produced. A clean report is the count line alone.
2759
+ const cov = report.coverage;
2760
+ for (const d of cov.dead) {
2761
+ process.stdout.write(`dead: ${d}\n`);
2762
+ }
2763
+ for (const u of cov.unruled) {
2764
+ process.stdout.write(`unruled: ${u.unit} ${u.path}\n`);
2765
+ }
2766
+ process.stdout.write(`coverage: ${cov.read.length} path(s) read, ${cov.dead.length} ` +
2767
+ `no output consumed, ${cov.unruled.length} declaration(s) ` +
2768
+ 'no rule produced\n');
2769
+ }
2770
+ else {
2771
+ // THE SUMMARY: one line per unit -- its path, its language and its
2772
+ // size -- since several units have no one text to print.
2773
+ for (const u of report.units) {
2774
+ process.stdout.write(`${u.path}\t${u.lang}\t${u.text.length} bytes\n`);
2775
+ }
2776
+ }
2777
+ for (const l of report.lossy) {
2778
+ process.stderr.write(`lossy: ${l.unit} ${l.path} tier ${l.tier} ${l.construct}: ${l.reason}\n`);
2779
+ }
2780
+ return renderExit(report, drift);
2781
+ }
2782
+ // D8's exit table over a report: a refused unit path is usage (2), a
2783
+ // strict refusal is lossy (1), any other error is the document's (4);
2784
+ // drift under --check is 1.
2785
+ function renderExit(report, drift) {
2786
+ if ('error' === report.verdict) {
2787
+ const errors = report.errors;
2788
+ if (errors.every((f) => 'render_path' === f.code)) {
2789
+ return 2;
2790
+ }
2791
+ if (errors.every((f) => 'render_strict' === f.code)) {
2792
+ return 1;
2793
+ }
2794
+ return 4;
2795
+ }
2796
+ return 0 < drift ? 1 : 0;
2797
+ }
2798
+ // ---------------------------------------------------------------------
2799
+ // THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8): the
2800
+ // two transforms and the round trip between them. `render` reads a
2801
+ // template directly, by its extension; this verb is for seeing the
2802
+ // canonical form, for writing one by hand and sugaring it, and for the
2803
+ // check that keeps a committed template and its meaning in agreement.
2804
+ const TEMPLATE_HELP = 'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)';
2805
+ function runTemplate(argv) {
2806
+ const files = [];
2807
+ let resugar = false;
2808
+ let check = false;
2809
+ let marker = undefined;
2810
+ for (let i = 0; i < argv.length; i++) {
2811
+ const arg = argv[i];
2812
+ if ('-h' === arg || '--help' === arg) {
2813
+ process.stdout.write(HELP);
2814
+ return 0;
2815
+ }
2816
+ else if ('--resugar' === arg) {
2817
+ resugar = true;
2818
+ }
2819
+ else if ('--check' === arg) {
2820
+ check = true;
2821
+ }
2822
+ else if ('--marker' === arg) {
2823
+ marker = argv[++i];
2824
+ if (null == marker) {
2825
+ process.stderr.write('aontu: --marker needs a token\n');
2826
+ return 2;
2827
+ }
2828
+ }
2829
+ else if (arg.startsWith('-')) {
2830
+ process.stderr.write(`aontu: unknown template option ${arg} (try --help)\n`);
2831
+ return 2;
2832
+ }
2833
+ else {
2834
+ files.push(arg);
2835
+ }
2836
+ }
2837
+ if (1 !== files.length) {
2838
+ process.stderr.write(`aontu: template needs one file\n${TEMPLATE_HELP}\n`);
2839
+ return 2;
2840
+ }
2841
+ // THE TWO ARE DIRECTIONS, NOT MODES THAT COMPOSE: `--check` reads a
2842
+ // template and asks whether the round trip answers it back, and
2843
+ // `--resugar` reads the canonical form instead. A run cannot be both
2844
+ // at once, because the file is one thing or the other.
2845
+ if (resugar && check) {
2846
+ process.stderr.write('aontu: template takes one of --resugar or --check\n');
2847
+ return 2;
2848
+ }
2849
+ let src;
2850
+ try {
2851
+ src = (0, node_fs_1.readFileSync)(files[0], 'utf8');
2852
+ }
2853
+ catch (err) {
2854
+ process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`);
2855
+ return 2;
2856
+ }
2857
+ const mark = marker ?? (0, template_1.markerFor)(files[0]);
2858
+ if (check) {
2859
+ // THE ROUND TRIP IS THE CHECK (D6): the file held to the spelling
2860
+ // the two transforms answer. What that names is a marker line the
2861
+ // transform would not have written -- one without its space, or one
2862
+ // whose aontu is indented after the marker rather than before it,
2863
+ // since the marker keeps its own indentation. It does NOT name a
2864
+ // changed body line: a template's whitespace is output, so a
2865
+ // trimmed trailing space is still a valid template and it is
2866
+ // `render --check` against the committed files that catches it.
2867
+ // The first line that differs is the report, since a whole diff of
2868
+ // a generator is the file again.
2869
+ const back = (0, template_1.resugarTemplate)((0, template_1.desugarTemplate)(src, mark), mark);
2870
+ if (back === src) {
2871
+ return 0;
2872
+ }
2873
+ const want = back.split('\n');
2874
+ const have = src.split('\n');
2875
+ let n = 0;
2876
+ while (n < want.length && n < have.length && want[n] === have[n]) {
2877
+ n++;
2878
+ }
2879
+ // THE TWO ARE THE SAME LENGTH, always: each transform maps one
2880
+ // line to one line and applies the same trailing-newline rule, so
2881
+ // `back` has as many lines as `src`. The loop above therefore stops
2882
+ // at a real difference rather than by running out of either -- an
2883
+ // equal prefix all the way to the end IS `back === src`, which
2884
+ // returned above. So both indexes are in range here.
2885
+ process.stderr.write(`aontu: ${files[0]}:${n + 1} is not what the round trip answers\n` +
2886
+ ` have: ${JSON.stringify(have[n])}\n` +
2887
+ ` want: ${JSON.stringify(want[n])}\n`);
2888
+ return 1;
2889
+ }
2890
+ process.stdout.write(resugar ?
2891
+ (0, template_1.resugarTemplate)(src, mark) : (0, template_1.desugarTemplate)(src, mark));
2892
+ return 0;
2893
+ }
2894
+ // ---------------------------------------------------------------------
2435
2895
  // The canon-hash (G6 phase 1): the pin an agent, a lockfile or a
2436
2896
  // registry stores for "this module, this meaning". The hash covers the
2437
2897
  // module evaluated STANDALONE -- its own include closure resolved and
@@ -3022,6 +3482,22 @@ function runFmt(argv) {
3022
3482
  }
3023
3483
  let worst = 0;
3024
3484
  for (const file of files) {
3485
+ // FMT FORMATS AONTU SOURCE, AND THE EXTENSION SAYS WHAT A FILE IS
3486
+ // (ADR-012's rule, and the one `render` reads a template by).
3487
+ // A TEMPLATE FILE IS NOT AONTU (docs/design/TEMPLATE.0.md; P8):
3488
+ // its marker lines are fragments of a document and its other lines
3489
+ // are the target's, so there is nothing here to format that would
3490
+ // not also rewrite the output. Refused rather than attempted, and
3491
+ // refused BY NAME rather than by a parse failure, because a `#-`
3492
+ // template parses: `#` opens a comment, so every marker line
3493
+ // vanishes and what is left is read as a document that was never
3494
+ // written. The verb answered `0` over one, having understood none
3495
+ // of it.
3496
+ if (!/[.](aon|aontu)$/.test(file)) {
3497
+ process.stderr.write(`aontu: ${file} is not aontu source (.aon, .aontu); a generator ` +
3498
+ 'written in the target\'s own syntax is aontu template\'s\n');
3499
+ return 2;
3500
+ }
3025
3501
  let src;
3026
3502
  try {
3027
3503
  src = (0, node_fs_1.readFileSync)(file, 'utf8');
@@ -3199,6 +3675,12 @@ function main(argv, servers = SERVERS) {
3199
3675
  if ('jsonschema' === argv[2]) {
3200
3676
  return finish(runJsonSchema(argv.slice(3)));
3201
3677
  }
3678
+ if ('render' === argv[2]) {
3679
+ return finish(runRender(argv.slice(3)));
3680
+ }
3681
+ if ('template' === argv[2]) {
3682
+ return finish(runTemplate(argv.slice(3)));
3683
+ }
3202
3684
  if ('reaches' === argv[2]) {
3203
3685
  return finish(runReaches(argv.slice(3)));
3204
3686
  }
@@ -3296,5 +3778,5 @@ function main(argv, servers = SERVERS) {
3296
3778
  else {
3297
3779
  runStdin(mode, trust).then((code) => finish(code));
3298
3780
  }
3299
- } /* node:coverage ignore next 16 */
3781
+ } /* node:coverage ignore next 18 */
3300
3782
  //# sourceMappingURL=cli.js.map