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.
- package/README.md +2 -2
- package/dist/agentsmd.js +1 -1
- package/dist/alias.d.ts +3 -0
- package/dist/alias.js +59 -0
- package/dist/alias.js.map +1 -0
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +12 -3
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +486 -4
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +4 -0
- package/dist/ctx.js +3 -0
- package/dist/ctx.js.map +1 -1
- package/dist/err.js +6 -3
- package/dist/err.js.map +1 -1
- package/dist/format.js +8 -2
- package/dist/format.js.map +1 -1
- package/dist/hints.js +40 -9
- package/dist/hints.js.map +1 -1
- package/dist/lang.js +409 -58
- package/dist/lang.js.map +1 -1
- package/dist/lower.d.ts +20 -0
- package/dist/lower.js +575 -0
- package/dist/lower.js.map +1 -0
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +1 -1
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.js +1 -1
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +40 -3
- package/dist/mcp.js.map +1 -1
- package/dist/render.d.ts +53 -0
- package/dist/render.js +542 -0
- package/dist/render.js.map +1 -0
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/std.d.ts +2 -0
- package/dist/std.js +498 -2
- package/dist/std.js.map +1 -1
- package/dist/template.d.ts +5 -0
- package/dist/template.js +257 -0
- package/dist/template.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +2 -0
- package/dist/type.js.map +1 -1
- package/dist/unify.js +43 -0
- package/dist/unify.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +1 -1
- package/dist/val/AggFuncVal.js +10 -21
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/BagVal.js +1 -1
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/ConstraintVal.js +1 -1
- package/dist/val/DisjunctVal.js +33 -2
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.d.ts +1 -1
- package/dist/val/EachFuncVal.js +8 -13
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +23 -4
- package/dist/val/EmitFuncVal.js +298 -28
- package/dist/val/EmitFuncVal.js.map +1 -1
- package/dist/val/FilterFuncVal.js +8 -4
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FormFuncVal.d.ts +14 -0
- package/dist/val/FormFuncVal.js +55 -0
- package/dist/val/FormFuncVal.js.map +1 -0
- package/dist/val/FuncBaseVal.js +36 -13
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/ListVal.js +9 -1
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/MapVal.d.ts +2 -1
- package/dist/val/MapVal.js +2 -1
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/PackFuncVal.d.ts +1 -1
- package/dist/val/PackFuncVal.js +22 -18
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.js +7 -2
- package/dist/val/PlaceVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +3 -0
- package/dist/val/RefVal.js +181 -18
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/Val.d.ts +8 -1
- package/dist/val/Val.js +39 -1
- package/dist/val/Val.js.map +1 -1
- package/dist/val/members.d.ts +9 -0
- package/dist/val/members.js +52 -0
- package/dist/val/members.js.map +1 -0
- package/grammar/aontu.abnf +1 -1
- package/grammar/aontu.gbnf +1 -1
- package/grammar/aontu.lark +1 -1
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +4 -4
- package/skill/error-codes.md +1 -1
- package/skill/examples.md +1 -1
- package/skill/grammar-card.md +1 -1
- package/src/agentsmd.ts +1 -1
- package/src/alias.ts +112 -0
- package/src/aontu.ts +20 -2
- package/src/cli.ts +528 -4
- package/src/ctx.ts +18 -0
- package/src/err.ts +6 -3
- package/src/format.ts +12 -2
- package/src/hints.ts +47 -9
- package/src/lang.ts +453 -63
- package/src/lower.ts +636 -0
- package/src/lsp.ts +1 -1
- package/src/mcp-server.ts +1 -1
- package/src/mcp.ts +43 -4
- package/src/render.ts +727 -0
- package/src/sigdecl.ts +1 -1
- package/src/std.ts +506 -1
- package/src/template.ts +291 -0
- package/src/type.ts +10 -1
- package/src/unify.ts +47 -0
- package/src/val/AggFuncVal.ts +10 -21
- package/src/val/BagVal.ts +1 -1
- package/src/val/ConstraintVal.ts +1 -1
- package/src/val/DisjunctVal.ts +33 -2
- package/src/val/EachFuncVal.ts +8 -16
- package/src/val/EmitFuncVal.ts +376 -39
- package/src/val/FilterFuncVal.ts +11 -4
- package/src/val/FormFuncVal.ts +119 -0
- package/src/val/FuncBaseVal.ts +36 -13
- package/src/val/ListVal.ts +9 -1
- package/src/val/MapVal.ts +3 -2
- package/src/val/PackFuncVal.ts +23 -19
- package/src/val/PlaceVal.ts +7 -2
- package/src/val/RefVal.ts +193 -20
- package/src/val/Val.ts +75 -4
- package/src/val/members.ts +86 -0
package/src/cli.ts
CHANGED
|
@@ -23,7 +23,12 @@ import {
|
|
|
23
23
|
exactJSON, vet, subsume, trimCheck, relationCheck,
|
|
24
24
|
hcanon, canonHash,
|
|
25
25
|
get, why, patch, agentsMd,
|
|
26
|
+
render,
|
|
27
|
+
renderProfile,
|
|
26
28
|
} from './aontu'
|
|
29
|
+
import type { RenderCoverage, RenderReport } from './render'
|
|
30
|
+
import { desugarTemplate, resugarTemplate, markerFor } from './template'
|
|
31
|
+
import { outsideRoot } from './mcp'
|
|
27
32
|
import { sarifReport } from './report-sarif'
|
|
28
33
|
import { main as lspMain } from './lsp-server'
|
|
29
34
|
import { main as mcpMain } from './mcp-server'
|
|
@@ -68,6 +73,10 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
68
73
|
aontu view <kind> [options] <file>...
|
|
69
74
|
aontu view --views <path> [--check] [options] <file>
|
|
70
75
|
aontu jsonschema [--at <path>] [--strict] [options] <file>
|
|
76
|
+
aontu render [--at <path>] [--profile <file>]... [--unit <path>]
|
|
77
|
+
[--stdout | --out <dir> | --check <dir> | --coverage]
|
|
78
|
+
[--coverage-at <path>] [--strict] <file>
|
|
79
|
+
aontu template [--resugar] [--check] [--marker <token>] <file>
|
|
71
80
|
aontu hash [options] <file>
|
|
72
81
|
aontu mod tidy|verify|vendor|manifest [options] [dir]
|
|
73
82
|
aontu get <path> [options] <file>
|
|
@@ -78,7 +87,7 @@ const HELP = `Usage: aontu [options] [file]
|
|
|
78
87
|
aontu lsp
|
|
79
88
|
aontu mcp [--root <dir>]
|
|
80
89
|
|
|
81
|
-
Evaluate an
|
|
90
|
+
Evaluate an aontu source file and print the result as JSON.
|
|
82
91
|
With no file on an interactive terminal, start a REPL.
|
|
83
92
|
With no file and piped input, read the source from stdin.
|
|
84
93
|
|
|
@@ -260,6 +269,51 @@ View exit codes: 0 rendered, 1 --check mismatch or lossy under
|
|
|
260
269
|
--strict, 2 usage or --max-rows exceeded, 4 the document does not stand
|
|
261
270
|
up on its own, or a relation, root or path that names nothing.
|
|
262
271
|
|
|
272
|
+
Render options:
|
|
273
|
+
--at <path> Render the value at this path ($.a.b); the root by
|
|
274
|
+
default
|
|
275
|
+
--profile <file> A profile document, profile: {lang, ...}, vetted
|
|
276
|
+
against aontu:profile; repeatable, one per language
|
|
277
|
+
--unit <path> Render only the unit with this path
|
|
278
|
+
--stdout One unit's bytes and nothing else (with --unit when
|
|
279
|
+
the instance has several)
|
|
280
|
+
--out <dir> Write every unit below dir, or nothing; never deletes
|
|
281
|
+
--check <dir> Compare every unit with dir/<path>; drift is listed
|
|
282
|
+
--coverage Report what the render read and what it did not:
|
|
283
|
+
model paths no output consumed, and rendered
|
|
284
|
+
declarations no rule produced. Writes nothing
|
|
285
|
+
--coverage-at <p> Measure coverage under this path only, instead of
|
|
286
|
+
the document root
|
|
287
|
+
--strict Refuse the opaque escapes (a text declaration, a raw
|
|
288
|
+
block)
|
|
289
|
+
--format <f> text (default) or json, the whole report; json
|
|
290
|
+
carries the dispatch trace, one entry per emitted
|
|
291
|
+
piece
|
|
292
|
+
|
|
293
|
+
Render exit codes: 0 rendered, 1 lossy under --strict or drift under
|
|
294
|
+
--check, 2 usage or I/O (a refused unit path included), 4 the document
|
|
295
|
+
does not stand up or the instance is not aontu:code.
|
|
296
|
+
|
|
297
|
+
A render entry file whose extension is not .aon is a TEMPLATE: a
|
|
298
|
+
generator in the target's own syntax, whose marker lines carry aontu
|
|
299
|
+
and whose other lines are output. It is desugared before it is
|
|
300
|
+
evaluated, and --marker names the marker for a language the table does
|
|
301
|
+
not know.
|
|
302
|
+
|
|
303
|
+
Template options:
|
|
304
|
+
--resugar The file is the canonical aontu; print the template
|
|
305
|
+
form instead of reading one
|
|
306
|
+
--check Desugar and resugar, and exit 1 if the file is not
|
|
307
|
+
what the round trip answers
|
|
308
|
+
--marker <t> The marker, when the extension does not name it
|
|
309
|
+
(default //-, and #- --- /*- by extension)
|
|
310
|
+
|
|
311
|
+
The template verb prints the canonical aontu form of a generator
|
|
312
|
+
written in the target's own syntax: a marked line is aontu source, and
|
|
313
|
+
every other line is a line of output.
|
|
314
|
+
|
|
315
|
+
Template exit codes: 0 written, 1 --check drift, 2 usage or I/O.
|
|
316
|
+
|
|
263
317
|
Set options:
|
|
264
318
|
--entry <file> The document the change is checked against
|
|
265
319
|
--overlay <file> The file the change is appended to (created if
|
|
@@ -549,7 +603,17 @@ function runFile(file: string, mode: Mode, trust: TrustArg): number {
|
|
|
549
603
|
}
|
|
550
604
|
|
|
551
605
|
const path = resolve(file)
|
|
552
|
-
|
|
606
|
+
// `fs` IS WHAT MAKES A FRAME EXCERPT THE FILE IT NAMES. Without it,
|
|
607
|
+
// err.ts's resolveSrc falls back to the ENTRY text, so a frame whose
|
|
608
|
+
// arrow says `lib/types.aon:2:6` printed the entry's line 2 under it
|
|
609
|
+
// -- a real file name over another file's line, which
|
|
610
|
+
// docs/reference-api.md forbids in the same words it uses to require
|
|
611
|
+
// the name.
|
|
612
|
+
const aontu = new Aontu({
|
|
613
|
+
path,
|
|
614
|
+
errfs: { existsSync, readFileSync },
|
|
615
|
+
...trustOpts(trust, dirname(path)),
|
|
616
|
+
})
|
|
553
617
|
const res = evalSource(aontu, src, mode)
|
|
554
618
|
;(res.ok ? process.stdout : process.stderr).write(res.text + '\n')
|
|
555
619
|
return res.ok ? 0 : 1
|
|
@@ -729,7 +793,7 @@ function runRepl(initialMode: Mode, jsonl: boolean, trust: TrustArg): void {
|
|
|
729
793
|
|
|
730
794
|
if (!jsonl) {
|
|
731
795
|
process.stdout.write(
|
|
732
|
-
`
|
|
796
|
+
`aontu v${version()} REPL — :help for commands, :quit to exit\n`)
|
|
733
797
|
}
|
|
734
798
|
rl.prompt()
|
|
735
799
|
|
|
@@ -2857,6 +2921,439 @@ function runJsonSchema(argv: string[]): number {
|
|
|
2857
2921
|
strict && 'lossy' === report.verdict ? 1 : 0
|
|
2858
2922
|
}
|
|
2859
2923
|
|
|
2924
|
+
// ---------------------------------------------------------------------
|
|
2925
|
+
// THE RENDER VERB (docs/design/RENDER.0.md D8): evaluate a document,
|
|
2926
|
+
// vet the value at --at against aontu:code, fold code.units into bytes,
|
|
2927
|
+
// and put them where the flag says -- one unit on stdout, every unit
|
|
2928
|
+
// below --out (all or nothing), or compared against --check. Exit codes
|
|
2929
|
+
// mirror jsonschema's: 0 ok; 1 lossy under --strict or drift under
|
|
2930
|
+
// --check; 2 usage or I/O, a refused unit path included; 4 the
|
|
2931
|
+
// document does not stand up or the instance is not aontu:code.
|
|
2932
|
+
|
|
2933
|
+
const RENDER_HELP =
|
|
2934
|
+
'aontu render [--at <path>] [--profile <file>]... [--unit <path>] ' +
|
|
2935
|
+
'[--stdout | --out <dir> | --check <dir> | --coverage] ' +
|
|
2936
|
+
'[--coverage-at <path>] [--strict] [--marker <token>] <file> (try --help)'
|
|
2937
|
+
|
|
2938
|
+
function runRender(argv: string[]): number {
|
|
2939
|
+
const trusted = takeTrust(argv)
|
|
2940
|
+
if (null == trusted) {
|
|
2941
|
+
return 2
|
|
2942
|
+
}
|
|
2943
|
+
argv = trusted.argv
|
|
2944
|
+
const trust = trusted.trust
|
|
2945
|
+
const files: string[] = []
|
|
2946
|
+
const profileFiles: string[] = []
|
|
2947
|
+
let format: SubsumeFormat = 'text'
|
|
2948
|
+
let at: string | undefined = undefined
|
|
2949
|
+
let unit: string | undefined = undefined
|
|
2950
|
+
let out: string | undefined = undefined
|
|
2951
|
+
let check: string | undefined = undefined
|
|
2952
|
+
let toStdout = false
|
|
2953
|
+
let strict = false
|
|
2954
|
+
let coverage = false
|
|
2955
|
+
let coverageAt: string | undefined = undefined
|
|
2956
|
+
let marker: string | undefined = undefined
|
|
2957
|
+
|
|
2958
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2959
|
+
const arg = argv[i]
|
|
2960
|
+
if ('-h' === arg || '--help' === arg) {
|
|
2961
|
+
process.stdout.write(HELP)
|
|
2962
|
+
return 0
|
|
2963
|
+
}
|
|
2964
|
+
if ('--format' === arg) {
|
|
2965
|
+
const f = argv[++i]
|
|
2966
|
+
if ('text' !== f && 'json' !== f) {
|
|
2967
|
+
process.stderr.write('aontu: --format needs text or json\n')
|
|
2968
|
+
return 2
|
|
2969
|
+
}
|
|
2970
|
+
format = f
|
|
2971
|
+
}
|
|
2972
|
+
else if ('--at' === arg) {
|
|
2973
|
+
at = argv[++i]
|
|
2974
|
+
if (null == at) {
|
|
2975
|
+
process.stderr.write('aontu: --at needs a path\n')
|
|
2976
|
+
return 2
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
else if ('--unit' === arg) {
|
|
2980
|
+
unit = argv[++i]
|
|
2981
|
+
if (null == unit) {
|
|
2982
|
+
process.stderr.write('aontu: --unit needs a unit path\n')
|
|
2983
|
+
return 2
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
else if ('--profile' === arg) {
|
|
2987
|
+
const pf = argv[++i]
|
|
2988
|
+
if (null == pf) {
|
|
2989
|
+
process.stderr.write('aontu: --profile needs a file\n')
|
|
2990
|
+
return 2
|
|
2991
|
+
}
|
|
2992
|
+
profileFiles.push(pf)
|
|
2993
|
+
}
|
|
2994
|
+
else if ('--out' === arg) {
|
|
2995
|
+
out = argv[++i]
|
|
2996
|
+
if (null == out) {
|
|
2997
|
+
process.stderr.write('aontu: --out needs a directory\n')
|
|
2998
|
+
return 2
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
else if ('--check' === arg) {
|
|
3002
|
+
check = argv[++i]
|
|
3003
|
+
if (null == check) {
|
|
3004
|
+
process.stderr.write('aontu: --check needs a directory\n')
|
|
3005
|
+
return 2
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
else if ('--stdout' === arg) {
|
|
3009
|
+
toStdout = true
|
|
3010
|
+
}
|
|
3011
|
+
else if ('--coverage' === arg) {
|
|
3012
|
+
coverage = true
|
|
3013
|
+
}
|
|
3014
|
+
else if ('--marker' === arg) {
|
|
3015
|
+
marker = argv[++i]
|
|
3016
|
+
if (null == marker) {
|
|
3017
|
+
process.stderr.write('aontu: --marker needs a token\n')
|
|
3018
|
+
return 2
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
else if ('--coverage-at' === arg) {
|
|
3022
|
+
coverageAt = argv[++i]
|
|
3023
|
+
if (null == coverageAt) {
|
|
3024
|
+
process.stderr.write('aontu: --coverage-at needs a path\n')
|
|
3025
|
+
return 2
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
else if ('--strict' === arg) {
|
|
3029
|
+
strict = true
|
|
3030
|
+
}
|
|
3031
|
+
else if (arg.startsWith('-')) {
|
|
3032
|
+
process.stderr.write(
|
|
3033
|
+
`aontu: unknown render option ${arg} (try --help)\n`)
|
|
3034
|
+
return 2
|
|
3035
|
+
}
|
|
3036
|
+
else {
|
|
3037
|
+
files.push(arg)
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
if (1 !== files.length) {
|
|
3042
|
+
process.stderr.write(`aontu: render needs one file\n${RENDER_HELP}\n`)
|
|
3043
|
+
return 2
|
|
3044
|
+
}
|
|
3045
|
+
const modes = [toStdout, undefined !== out, undefined !== check, coverage]
|
|
3046
|
+
.filter((on) => on).length
|
|
3047
|
+
if (1 < modes) {
|
|
3048
|
+
process.stderr.write(
|
|
3049
|
+
'aontu: render takes one of --stdout, --out, --check or --coverage\n')
|
|
3050
|
+
return 2
|
|
3051
|
+
}
|
|
3052
|
+
// A NARROWER MEASURE NEEDS SOMETHING TO NARROW. `--coverage-at`
|
|
3053
|
+
// without `--coverage` asks for a report the run does not compute,
|
|
3054
|
+
// and answering silently would be the wrong half of the request.
|
|
3055
|
+
if (undefined !== coverageAt && !coverage) {
|
|
3056
|
+
process.stderr.write('aontu: --coverage-at needs --coverage\n')
|
|
3057
|
+
return 2
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
let src: string
|
|
3061
|
+
try {
|
|
3062
|
+
src = readFileSync(files[0], 'utf8')
|
|
3063
|
+
}
|
|
3064
|
+
catch (err: any) {
|
|
3065
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3066
|
+
return 2
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
// THE ENTRY MAY BE A TEMPLATE (TEMPLATE.0.md; P8), and its EXTENSION
|
|
3070
|
+
// decides, as an include's extension decides what the include is
|
|
3071
|
+
// (ADR-012): a generator is a file in the target's own syntax, so it
|
|
3072
|
+
// carries the target's extension and never `.aon`. Desugared here
|
|
3073
|
+
// rather than anywhere deeper, because a template is an entry
|
|
3074
|
+
// spelling and not a value: an include is still aontu.
|
|
3075
|
+
if (!files[0].endsWith('.aon')) {
|
|
3076
|
+
src = desugarTemplate(src, marker ?? markerFor(files[0]))
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
// THE PROFILES (D5): each --profile file is a document whose root is
|
|
3080
|
+
// `profile: {lang, ...}`, evaluated under the verb's trust and vetted
|
|
3081
|
+
// against aontu:profile as a settled value before the fold reads it
|
|
3082
|
+
// (renderProfile, which also fills the defaults). Two files claiming
|
|
3083
|
+
// one lang is a usage error: the fold could not choose.
|
|
3084
|
+
const profiles: any[] = []
|
|
3085
|
+
const langs = new Map<string, string>()
|
|
3086
|
+
for (const pf of profileFiles) {
|
|
3087
|
+
let text: string
|
|
3088
|
+
try {
|
|
3089
|
+
text = readFileSync(pf, 'utf8')
|
|
3090
|
+
}
|
|
3091
|
+
catch (err: any) {
|
|
3092
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3093
|
+
return 2
|
|
3094
|
+
}
|
|
3095
|
+
const loaded = renderProfile(text,
|
|
3096
|
+
{ path: resolve(pf), ...verbOpts(trust, entryRootOf(pf)) })
|
|
3097
|
+
if (undefined !== loaded.errors) {
|
|
3098
|
+
process.stderr.write(loaded.errors.map(renderFinding).join('\n') + '\n')
|
|
3099
|
+
return 4
|
|
3100
|
+
}
|
|
3101
|
+
const profile = loaded.profile
|
|
3102
|
+
const prev = langs.get(profile.lang)
|
|
3103
|
+
if (undefined !== prev) {
|
|
3104
|
+
process.stderr.write(
|
|
3105
|
+
`aontu: two profiles claim ${profile.lang}: ${prev} and ${pf}\n`)
|
|
3106
|
+
return 2
|
|
3107
|
+
}
|
|
3108
|
+
langs.set(profile.lang, pf)
|
|
3109
|
+
profiles.push(profile)
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
const report = render(src, {
|
|
3113
|
+
at, unit, strict, profiles, path: files[0],
|
|
3114
|
+
coverage, coverageAt,
|
|
3115
|
+
// THE JSON REPORT CARRIES THE TRACE (D9), which is what the shape
|
|
3116
|
+
// there has always said; a text run computes it only when the
|
|
3117
|
+
// coverage report needs it.
|
|
3118
|
+
trace: 'json' === format,
|
|
3119
|
+
...verbOpts(trust, entryRootOf(files[0])),
|
|
3120
|
+
})
|
|
3121
|
+
|
|
3122
|
+
if ('json' === format) {
|
|
3123
|
+
process.stdout.write(exactJSON({
|
|
3124
|
+
aontu: { version: version(), verb: 'render' },
|
|
3125
|
+
verdict: report.verdict,
|
|
3126
|
+
units: report.units,
|
|
3127
|
+
lossy: report.lossy,
|
|
3128
|
+
...(null == report.errors ? {} : { errors: report.errors }),
|
|
3129
|
+
...(null == report.trace ? {} : { trace: report.trace }),
|
|
3130
|
+
...(null == report.coverage ? {} : { coverage: report.coverage }),
|
|
3131
|
+
}, 2) + '\n')
|
|
3132
|
+
return renderExit(report, 0)
|
|
3133
|
+
}
|
|
3134
|
+
if ('error' === report.verdict) {
|
|
3135
|
+
process.stderr.write(
|
|
3136
|
+
(report.errors as VetFinding[]).map(renderFinding).join('\n') + '\n')
|
|
3137
|
+
return renderExit(report, 0)
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
let drift = 0
|
|
3141
|
+
if (toStdout) {
|
|
3142
|
+
// ONE UNIT'S BYTES AND NOTHING ELSE, so the output can be piped
|
|
3143
|
+
// into a formatter or a file.
|
|
3144
|
+
if (1 !== report.units.length) {
|
|
3145
|
+
process.stderr.write(
|
|
3146
|
+
'aontu: --stdout needs exactly one unit, and the instance has ' +
|
|
3147
|
+
`${report.units.length}; --unit names one\n`)
|
|
3148
|
+
return 2
|
|
3149
|
+
}
|
|
3150
|
+
process.stdout.write(report.units[0].text)
|
|
3151
|
+
}
|
|
3152
|
+
else if (undefined !== out) {
|
|
3153
|
+
// EVERY UNIT BELOW <dir>, OR NOTHING: every unit rendered first
|
|
3154
|
+
// (the report above), and no file touched unless all did. The
|
|
3155
|
+
// directory is realpath-confined; a unit path is already a relative
|
|
3156
|
+
// descent (render_path refuses the rest), and the check here is
|
|
3157
|
+
// against the symlink inside it. render never deletes.
|
|
3158
|
+
for (const u of report.units) {
|
|
3159
|
+
if (outsideRoot(out, resolve(out, u.path))) {
|
|
3160
|
+
process.stderr.write(`aontu: ${u.path} escapes ${out}\n`)
|
|
3161
|
+
return 2
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
for (const u of report.units) {
|
|
3165
|
+
const full = resolve(out, u.path)
|
|
3166
|
+
try {
|
|
3167
|
+
mkdirSync(dirname(full), { recursive: true })
|
|
3168
|
+
writeFileSync(full, u.text, 'utf8')
|
|
3169
|
+
}
|
|
3170
|
+
catch (err: any) {
|
|
3171
|
+
process.stderr.write(`aontu: cannot write ${u.path}: ${err.message}\n`)
|
|
3172
|
+
return 2
|
|
3173
|
+
}
|
|
3174
|
+
process.stderr.write(`wrote ${u.path}\n`)
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
else if (undefined !== check) {
|
|
3178
|
+
// RENDER AND COMPARE: a unit whose bytes differ from the file at
|
|
3179
|
+
// <dir>/<path>, or whose file is absent, is drift, listed by path.
|
|
3180
|
+
// The CI form.
|
|
3181
|
+
for (const u of report.units) {
|
|
3182
|
+
let have: string | undefined = undefined
|
|
3183
|
+
try {
|
|
3184
|
+
have = readFileSync(resolve(check, u.path), 'utf8')
|
|
3185
|
+
}
|
|
3186
|
+
catch {
|
|
3187
|
+
// Absent is drift, reported below.
|
|
3188
|
+
}
|
|
3189
|
+
if (undefined === have) {
|
|
3190
|
+
drift++
|
|
3191
|
+
process.stderr.write(`aontu: ${u.path} is missing from ${check}\n`)
|
|
3192
|
+
}
|
|
3193
|
+
else if (have !== u.text) {
|
|
3194
|
+
drift++
|
|
3195
|
+
process.stderr.write(`aontu: ${u.path} differs from the rendered unit\n`)
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
else if (coverage) {
|
|
3200
|
+
// THE COVERAGE REPORT (P7), one line per finding and a count at
|
|
3201
|
+
// the end: dead model first, then the declarations no rule
|
|
3202
|
+
// produced. A clean report is the count line alone.
|
|
3203
|
+
const cov = report.coverage as RenderCoverage
|
|
3204
|
+
for (const d of cov.dead) {
|
|
3205
|
+
process.stdout.write(`dead: ${d}\n`)
|
|
3206
|
+
}
|
|
3207
|
+
for (const u of cov.unruled) {
|
|
3208
|
+
process.stdout.write(`unruled: ${u.unit} ${u.path}\n`)
|
|
3209
|
+
}
|
|
3210
|
+
process.stdout.write(
|
|
3211
|
+
`coverage: ${cov.read.length} path(s) read, ${cov.dead.length} ` +
|
|
3212
|
+
`no output consumed, ${cov.unruled.length} declaration(s) ` +
|
|
3213
|
+
'no rule produced\n')
|
|
3214
|
+
}
|
|
3215
|
+
else {
|
|
3216
|
+
// THE SUMMARY: one line per unit -- its path, its language and its
|
|
3217
|
+
// size -- since several units have no one text to print.
|
|
3218
|
+
for (const u of report.units) {
|
|
3219
|
+
process.stdout.write(`${u.path}\t${u.lang}\t${u.text.length} bytes\n`)
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
for (const l of report.lossy) {
|
|
3223
|
+
process.stderr.write(
|
|
3224
|
+
`lossy: ${l.unit} ${l.path} tier ${l.tier} ${l.construct}: ${l.reason}\n`)
|
|
3225
|
+
}
|
|
3226
|
+
return renderExit(report, drift)
|
|
3227
|
+
}
|
|
3228
|
+
|
|
3229
|
+
// D8's exit table over a report: a refused unit path is usage (2), a
|
|
3230
|
+
// strict refusal is lossy (1), any other error is the document's (4);
|
|
3231
|
+
// drift under --check is 1.
|
|
3232
|
+
function renderExit(report: RenderReport, drift: number): number {
|
|
3233
|
+
if ('error' === report.verdict) {
|
|
3234
|
+
const errors = report.errors as VetFinding[]
|
|
3235
|
+
if (errors.every((f) => 'render_path' === f.code)) {
|
|
3236
|
+
return 2
|
|
3237
|
+
}
|
|
3238
|
+
if (errors.every((f) => 'render_strict' === f.code)) {
|
|
3239
|
+
return 1
|
|
3240
|
+
}
|
|
3241
|
+
return 4
|
|
3242
|
+
}
|
|
3243
|
+
return 0 < drift ? 1 : 0
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
|
|
3247
|
+
// ---------------------------------------------------------------------
|
|
3248
|
+
// THE TEMPLATE SURFACE (docs/design/TEMPLATE.0.md; RENDER.0.md P8): the
|
|
3249
|
+
// two transforms and the round trip between them. `render` reads a
|
|
3250
|
+
// template directly, by its extension; this verb is for seeing the
|
|
3251
|
+
// canonical form, for writing one by hand and sugaring it, and for the
|
|
3252
|
+
// check that keeps a committed template and its meaning in agreement.
|
|
3253
|
+
|
|
3254
|
+
const TEMPLATE_HELP =
|
|
3255
|
+
'aontu template [--resugar] [--check] [--marker <token>] <file> (try --help)'
|
|
3256
|
+
|
|
3257
|
+
function runTemplate(argv: string[]): number {
|
|
3258
|
+
const files: string[] = []
|
|
3259
|
+
let resugar = false
|
|
3260
|
+
let check = false
|
|
3261
|
+
let marker: string | undefined = undefined
|
|
3262
|
+
|
|
3263
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3264
|
+
const arg = argv[i]
|
|
3265
|
+
if ('-h' === arg || '--help' === arg) {
|
|
3266
|
+
process.stdout.write(HELP)
|
|
3267
|
+
return 0
|
|
3268
|
+
}
|
|
3269
|
+
else if ('--resugar' === arg) {
|
|
3270
|
+
resugar = true
|
|
3271
|
+
}
|
|
3272
|
+
else if ('--check' === arg) {
|
|
3273
|
+
check = true
|
|
3274
|
+
}
|
|
3275
|
+
else if ('--marker' === arg) {
|
|
3276
|
+
marker = argv[++i]
|
|
3277
|
+
if (null == marker) {
|
|
3278
|
+
process.stderr.write('aontu: --marker needs a token\n')
|
|
3279
|
+
return 2
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
else if (arg.startsWith('-')) {
|
|
3283
|
+
process.stderr.write(
|
|
3284
|
+
`aontu: unknown template option ${arg} (try --help)\n`)
|
|
3285
|
+
return 2
|
|
3286
|
+
}
|
|
3287
|
+
else {
|
|
3288
|
+
files.push(arg)
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
if (1 !== files.length) {
|
|
3293
|
+
process.stderr.write(`aontu: template needs one file\n${TEMPLATE_HELP}\n`)
|
|
3294
|
+
return 2
|
|
3295
|
+
}
|
|
3296
|
+
// THE TWO ARE DIRECTIONS, NOT MODES THAT COMPOSE: `--check` reads a
|
|
3297
|
+
// template and asks whether the round trip answers it back, and
|
|
3298
|
+
// `--resugar` reads the canonical form instead. A run cannot be both
|
|
3299
|
+
// at once, because the file is one thing or the other.
|
|
3300
|
+
if (resugar && check) {
|
|
3301
|
+
process.stderr.write(
|
|
3302
|
+
'aontu: template takes one of --resugar or --check\n')
|
|
3303
|
+
return 2
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
let src: string
|
|
3307
|
+
try {
|
|
3308
|
+
src = readFileSync(files[0], 'utf8')
|
|
3309
|
+
}
|
|
3310
|
+
catch (err: any) {
|
|
3311
|
+
process.stderr.write(`aontu: cannot read ${err.path}: ${err.message}\n`)
|
|
3312
|
+
return 2
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3315
|
+
const mark = marker ?? markerFor(files[0])
|
|
3316
|
+
|
|
3317
|
+
if (check) {
|
|
3318
|
+
// THE ROUND TRIP IS THE CHECK (D6): the file held to the spelling
|
|
3319
|
+
// the two transforms answer. What that names is a marker line the
|
|
3320
|
+
// transform would not have written -- one without its space, or one
|
|
3321
|
+
// whose aontu is indented after the marker rather than before it,
|
|
3322
|
+
// since the marker keeps its own indentation. It does NOT name a
|
|
3323
|
+
// changed body line: a template's whitespace is output, so a
|
|
3324
|
+
// trimmed trailing space is still a valid template and it is
|
|
3325
|
+
// `render --check` against the committed files that catches it.
|
|
3326
|
+
// The first line that differs is the report, since a whole diff of
|
|
3327
|
+
// a generator is the file again.
|
|
3328
|
+
const back = resugarTemplate(desugarTemplate(src, mark), mark)
|
|
3329
|
+
if (back === src) {
|
|
3330
|
+
return 0
|
|
3331
|
+
}
|
|
3332
|
+
const want = back.split('\n')
|
|
3333
|
+
const have = src.split('\n')
|
|
3334
|
+
let n = 0
|
|
3335
|
+
while (n < want.length && n < have.length && want[n] === have[n]) {
|
|
3336
|
+
n++
|
|
3337
|
+
}
|
|
3338
|
+
// THE TWO ARE THE SAME LENGTH, always: each transform maps one
|
|
3339
|
+
// line to one line and applies the same trailing-newline rule, so
|
|
3340
|
+
// `back` has as many lines as `src`. The loop above therefore stops
|
|
3341
|
+
// at a real difference rather than by running out of either -- an
|
|
3342
|
+
// equal prefix all the way to the end IS `back === src`, which
|
|
3343
|
+
// returned above. So both indexes are in range here.
|
|
3344
|
+
process.stderr.write(
|
|
3345
|
+
`aontu: ${files[0]}:${n + 1} is not what the round trip answers\n` +
|
|
3346
|
+
` have: ${JSON.stringify(have[n])}\n` +
|
|
3347
|
+
` want: ${JSON.stringify(want[n])}\n`)
|
|
3348
|
+
return 1
|
|
3349
|
+
}
|
|
3350
|
+
|
|
3351
|
+
process.stdout.write(resugar ?
|
|
3352
|
+
resugarTemplate(src, mark) : desugarTemplate(src, mark))
|
|
3353
|
+
return 0
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
|
|
2860
3357
|
// ---------------------------------------------------------------------
|
|
2861
3358
|
// The canon-hash (G6 phase 1): the pin an agent, a lockfile or a
|
|
2862
3359
|
// registry stores for "this module, this meaning". The hash covers the
|
|
@@ -3521,6 +4018,23 @@ function runFmt(argv: string[]): number | Promise<number> {
|
|
|
3521
4018
|
|
|
3522
4019
|
let worst = 0
|
|
3523
4020
|
for (const file of files) {
|
|
4021
|
+
// FMT FORMATS AONTU SOURCE, AND THE EXTENSION SAYS WHAT A FILE IS
|
|
4022
|
+
// (ADR-012's rule, and the one `render` reads a template by).
|
|
4023
|
+
// A TEMPLATE FILE IS NOT AONTU (docs/design/TEMPLATE.0.md; P8):
|
|
4024
|
+
// its marker lines are fragments of a document and its other lines
|
|
4025
|
+
// are the target's, so there is nothing here to format that would
|
|
4026
|
+
// not also rewrite the output. Refused rather than attempted, and
|
|
4027
|
+
// refused BY NAME rather than by a parse failure, because a `#-`
|
|
4028
|
+
// template parses: `#` opens a comment, so every marker line
|
|
4029
|
+
// vanishes and what is left is read as a document that was never
|
|
4030
|
+
// written. The verb answered `0` over one, having understood none
|
|
4031
|
+
// of it.
|
|
4032
|
+
if (!/[.](aon|aontu)$/.test(file)) {
|
|
4033
|
+
process.stderr.write(
|
|
4034
|
+
`aontu: ${file} is not aontu source (.aon, .aontu); a generator ` +
|
|
4035
|
+
'written in the target\'s own syntax is aontu template\'s\n')
|
|
4036
|
+
return 2
|
|
4037
|
+
}
|
|
3524
4038
|
let src: string
|
|
3525
4039
|
try {
|
|
3526
4040
|
src = readFileSync(file, 'utf8')
|
|
@@ -3734,6 +4248,14 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
3734
4248
|
return finish(runJsonSchema(argv.slice(3)))
|
|
3735
4249
|
}
|
|
3736
4250
|
|
|
4251
|
+
if ('render' === argv[2]) {
|
|
4252
|
+
return finish(runRender(argv.slice(3)))
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
if ('template' === argv[2]) {
|
|
4256
|
+
return finish(runTemplate(argv.slice(3)))
|
|
4257
|
+
}
|
|
4258
|
+
|
|
3737
4259
|
if ('reaches' === argv[2]) {
|
|
3738
4260
|
return finish(runReaches(argv.slice(3)))
|
|
3739
4261
|
}
|
|
@@ -3840,7 +4362,7 @@ function main(argv: string[], servers: Servers = SERVERS): void {
|
|
|
3840
4362
|
else {
|
|
3841
4363
|
runStdin(mode, trust).then((code) => finish(code))
|
|
3842
4364
|
}
|
|
3843
|
-
} /* node:coverage ignore next
|
|
4365
|
+
} /* node:coverage ignore next 18 */
|
|
3844
4366
|
|
|
3845
4367
|
|
|
3846
4368
|
// No require.main guard here: bin/aontu.js is the executable entry and
|
|
@@ -3852,6 +4374,8 @@ export {
|
|
|
3852
4374
|
runReaches,
|
|
3853
4375
|
runView,
|
|
3854
4376
|
runJsonSchema,
|
|
4377
|
+
runRender,
|
|
4378
|
+
runTemplate,
|
|
3855
4379
|
runMod,
|
|
3856
4380
|
runHash, runGet,
|
|
3857
4381
|
runWhy, renderWhyText, runSet, runAgentsMd, runFmt,
|
package/src/ctx.ts
CHANGED
|
@@ -26,7 +26,16 @@ type AontuContextConfig = {
|
|
|
26
26
|
// uninstrumented run. Shared by reference down every descend, as the
|
|
27
27
|
// error list is, so one run has one record.
|
|
28
28
|
prov?: any
|
|
29
|
+
|
|
30
|
+
// THE READ SET (RENDER.0.md P7), or absent for an uninstrumented
|
|
31
|
+
// run: every tree path a reference resolved to, in one shared set.
|
|
32
|
+
// It is what `render --coverage` measures the model against -- a
|
|
33
|
+
// path no read reached is model the transform never consumed -- and
|
|
34
|
+
// its presence is also what switches the two render riders on
|
|
35
|
+
// (Val.origin, Val.emitted), so one flag turns the whole record on.
|
|
36
|
+
reads?: Set<string>
|
|
29
37
|
fs?: any
|
|
38
|
+
errfs?: any
|
|
30
39
|
path?: string[]
|
|
31
40
|
root?: Val
|
|
32
41
|
seen?: Record<string, number>
|
|
@@ -69,6 +78,8 @@ class AontuContext {
|
|
|
69
78
|
vars: Record<string, Val> = {}
|
|
70
79
|
src?: string
|
|
71
80
|
fs?: FST
|
|
81
|
+
// The error renderer's own reader; see type.ts's `errfs`.
|
|
82
|
+
errfs?: FST
|
|
72
83
|
|
|
73
84
|
seenI: number
|
|
74
85
|
seen: Record<string, number>
|
|
@@ -94,6 +105,10 @@ class AontuContext {
|
|
|
94
105
|
// context through the prototype chain, so one run has one record.
|
|
95
106
|
prov?: any
|
|
96
107
|
|
|
108
|
+
// The read set (RENDER.0.md P7), or undefined for an uninstrumented
|
|
109
|
+
// run. Inherited exactly as `prov` is.
|
|
110
|
+
reads?: Set<string>
|
|
111
|
+
|
|
97
112
|
// errlist: Omit<NilVal[], "push"> // Nil error log of current unify.
|
|
98
113
|
err: any[]
|
|
99
114
|
explain: any[] | null
|
|
@@ -181,11 +196,13 @@ class AontuContext {
|
|
|
181
196
|
|
|
182
197
|
this.collect = cfg.collect ?? null != cfg.err
|
|
183
198
|
this.prov = cfg.prov
|
|
199
|
+
this.reads = cfg.reads
|
|
184
200
|
|
|
185
201
|
this.err = cfg.err ?? []
|
|
186
202
|
this.explain = Array.isArray(cfg.explain) ? cfg.explain : null
|
|
187
203
|
|
|
188
204
|
this.fs = cfg.fs ?? null
|
|
205
|
+
this.errfs = cfg.errfs ?? null
|
|
189
206
|
|
|
190
207
|
// Multiple unify passes will keep incrementing Val counter.
|
|
191
208
|
this.vc = null == cfg.vc ? 1_000_000_000 : cfg.vc
|
|
@@ -307,6 +324,7 @@ class AontuContext {
|
|
|
307
324
|
this.err = this.opts.err ?? this.err
|
|
308
325
|
this.deps = this.opts.deps ?? this.deps
|
|
309
326
|
this.fs = this.opts.fs ?? this.fs
|
|
327
|
+
this.errfs = (this.opts as any).errfs ?? this.errfs
|
|
310
328
|
this.explain = this.opts.explain ?? this.explain
|
|
311
329
|
|
|
312
330
|
this.src = ('string' === typeof this.opts.src ? this.opts.src : undefined) ?? this.src
|
package/src/err.ts
CHANGED
|
@@ -208,9 +208,12 @@ function resolveSrc(v: Val, errctx: ErrContext | undefined) {
|
|
|
208
208
|
}
|
|
209
209
|
else {
|
|
210
210
|
try {
|
|
211
|
-
|
|
211
|
+
// errfs first: the CLI gives the renderer a reader without
|
|
212
|
+
// giving the resolver one (type.ts, `errfs`).
|
|
213
|
+
const reader = errctx?.errfs ?? errctx?.fs
|
|
214
|
+
const fileExists = reader?.existsSync(url)
|
|
212
215
|
if (fileExists) {
|
|
213
|
-
src =
|
|
216
|
+
src = reader?.readFileSync(url, 'utf8') ?? undefined
|
|
214
217
|
}
|
|
215
218
|
}
|
|
216
219
|
catch (fe: any) {
|
|
@@ -228,7 +231,7 @@ function resolveSrc(v: Val, errctx: ErrContext | undefined) {
|
|
|
228
231
|
}
|
|
229
232
|
else if (errctx) {
|
|
230
233
|
src = 'SOURCE-NOT-FOUND:' + (null != url ? (' ' + url) : '') +
|
|
231
|
-
(null == errctx?.fs ? ' (NO-FS)' : '')
|
|
234
|
+
(null == (errctx?.errfs ?? errctx?.fs) ? ' (NO-FS)' : '')
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
|