@amritk/nish-x86_64-linux 0.24.0 → 0.25.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 (42) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +24 -4
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/host.d.ts +8 -0
  6. package/runtime/core/host.mjs +50 -2
  7. package/runtime/nish.d.ts +100 -0
  8. package/runtime/nish.h +34 -0
  9. package/runtime/nish.mjs +21 -0
  10. package/runtime/node.mjs +57 -6
  11. package/runtime/runtime-net.c +44 -3
  12. package/runtime/runtime-os.c +115 -12
  13. package/scripts/build.sh +13 -3
  14. package/std/README.md +25 -8
  15. package/std/cli-args.ts +1216 -0
  16. package/std/cli-flags.ts +585 -0
  17. package/std/cli.ts +308 -19
  18. package/std/compress/checksum.ts +132 -20
  19. package/std/compress/deflate.ts +638 -248
  20. package/std/compress/gzip.ts +20 -6
  21. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  22. package/std/crypto/p256.ts +63 -1
  23. package/std/crypto/p384.ts +6044 -0
  24. package/std/crypto/pki-roots.ts +322 -0
  25. package/std/crypto/pki.ts +1207 -0
  26. package/std/crypto/rsa.ts +663 -0
  27. package/std/crypto/x509.ts +1054 -58
  28. package/std/net/dns-config.ts +734 -0
  29. package/std/net/dns-message.ts +517 -0
  30. package/std/net/dns-resolver.ts +1015 -0
  31. package/std/net/dns.ts +478 -0
  32. package/std/net/http1-client.ts +1227 -0
  33. package/std/net/http1-server.ts +7 -2
  34. package/std/net/http1.ts +9 -9
  35. package/std/net/tls/client.ts +1094 -0
  36. package/std/net/tls/codec.ts +585 -3
  37. package/std/net/tls/record-client.ts +1192 -0
  38. package/std/net/tls/verify.ts +221 -0
  39. package/std/net/tls-tcp.ts +642 -11
  40. package/std/net/tls.ts +2 -2
  41. package/std/net/url.ts +559 -4
  42. package/std/yaml-write.ts +415 -0
@@ -0,0 +1,1216 @@
1
+ /**
2
+ * `std/cli-args` — a command tree for a large command-line tool: declared
3
+ * once with a builder, parsed in one walk over the arguments, and answered as
4
+ * the matched command's id and every value with where it came from.
5
+ *
6
+ * import { Cli, ROOT, finish } from "nish/cli-args";
7
+ * import { Style } from "nish/cli";
8
+ *
9
+ * const cli = new Cli("api", "1.4.0", "Talk to the Example API");
10
+ * const raw = cli.globalFlag("-r, --raw-output", "print JSON as it arrives");
11
+ * const users = cli.group(ROOT, "users", "Manage users");
12
+ * const list = cli.command(users, "list", 1, "List users");
13
+ * const limit = cli.flag(list, "-l, --limit <n>", "at most n users");
14
+ * const r = cli.parse(process.argv, 1);
15
+ * finish(r, new Style(false, false), new Style(false, false));
16
+ * if (r.id === 1) console.log(r.value(limit) ?? "all");
17
+ *
18
+ * **A builder, not a table.** Nish has no function values, so a command
19
+ * cannot carry its handler; what a builder gives instead is a handle for
20
+ * every command, flag and argument, so the program reads a value by the
21
+ * handle it declared rather than by a name a typo can miss, and every
22
+ * declaration mistake panics at the call that made it.
23
+ *
24
+ * **What it costs.** Declaring is linear in the declarations and builds no
25
+ * help text. Parsing walks the arguments once, with one map lookup per
26
+ * token, and keeps only what the arguments set; help text, did-you-mean and
27
+ * the error wording are built only on the help and error paths.
28
+ *
29
+ * **What it reaches.** Nothing: `parse` reads only the array it is given,
30
+ * and only `finish` writes (to standard output or standard error) and exits.
31
+ */
32
+
33
+ import { HelpMenu, Style } from "nish/cli"
34
+
35
+ /** The handle of the tree's root, the program itself. */
36
+ export const ROOT: i32 = 0
37
+
38
+ /** `ParseResult.kind`: the arguments name a command to run. */
39
+ export const PARSE_OK: i32 = 0
40
+
41
+ /** `ParseResult.kind`: `-h`, `--help` or `help <path>` asked for help. */
42
+ export const PARSE_HELP: i32 = 1
43
+
44
+ /** `ParseResult.kind`: `-v` or `--version` at the root asked for the version. */
45
+ export const PARSE_VERSION: i32 = 2
46
+
47
+ /** `ParseResult.kind`: the arguments are wrong; `message` says how. */
48
+ export const PARSE_ERROR: i32 = 3
49
+
50
+ /** `ParseResult.source`: the value was given on the command line. */
51
+ export const SOURCE_ARGV: i32 = 0
52
+
53
+ /** `ParseResult.source`: the value is the declared default. */
54
+ export const SOURCE_DEFAULT: i32 = 1
55
+
56
+ /** `ParseResult.source`: the flag was not given and has no default. */
57
+ export const SOURCE_UNSET: i32 = 2
58
+
59
+ /** A flag that takes no value. */
60
+ const VALUE_NONE: i32 = 0
61
+
62
+ /** A flag whose value is required: `--name <value>`. */
63
+ const VALUE_REQUIRED: i32 = 1
64
+
65
+ /** A flag whose value is optional: `--name [value]`. */
66
+ const VALUE_OPTIONAL: i32 = 2
67
+
68
+ /** The owner of a global flag, which no command handle is. */
69
+ const GLOBAL: i32 = -1
70
+
71
+ /** No handle: a group's id, a command without arguments, the last argument's successor. */
72
+ const NONE: i32 = -1
73
+
74
+ /** Where descriptions start at the latest, in a help row. */
75
+ const MAX_COLUMN: i32 = 32
76
+
77
+ /** The ASCII hyphen-minus every flag opens with. */
78
+ const DASH: i32 = 45
79
+
80
+ /** The key a child, alias or flag is found by within its scope. */
81
+ const scopeKey = (scope: i32, name: string): string => `${scope}\u0000${name}`
82
+
83
+ /** Whether `name` is a word a command line can name: not empty, no space, no `=`, no leading dash. */
84
+ const isWord = (name: string): boolean =>
85
+ name.length > 0 && name.indexOf(" ") < 0 && name.indexOf("=") < 0 && toI32(name.charCodeAt(0)) !== DASH
86
+
87
+ /** The smaller of two widths. */
88
+ const minI32 = (a: i32, b: i32): i32 => (a < b ? a : b)
89
+
90
+ /**
91
+ * The edit distance between `a` and `b` in bytes, a swap of two neighbours
92
+ * counting as one edit (`kyes` is one from `keys`), worked in the three rows
93
+ * `rows` holds, each at least one longer than `b`.
94
+ */
95
+ const editDistance = (a: string, b: string, rows: i32[][]): i32 => {
96
+ const n: i32 = toI32(b.length)
97
+ if (toI32(rows.length) !== 3) {
98
+ panic("cli-args: edit distance needs three rows")
99
+ }
100
+ for (let j: i32 = 0; j <= n && j < toI32(rows[0].length); j++) {
101
+ rows[0][j] = j
102
+ }
103
+ for (let i: i32 = 1; i <= toI32(a.length); i++) {
104
+ const into = rows[i % 3]
105
+ const above = rows[(i + 2) % 3]
106
+ const twoUp = rows[(i + 1) % 3]
107
+ if (!(n >= 0 && n < toI32(into.length) && n < toI32(above.length) && n < toI32(twoUp.length))) {
108
+ panic("cli-args: edit distance rows too short")
109
+ }
110
+ into[0] = i
111
+ for (
112
+ let j: i32 = 1;
113
+ j <= n && j < toI32(into.length) && j < toI32(above.length) && j < toI32(twoUp.length);
114
+ j++
115
+ ) {
116
+ const cost: i32 = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1
117
+ let d = minI32(minI32(above[j] + 1, into[j - 1] + 1), above[j - 1] + cost)
118
+ if (
119
+ i > 1 &&
120
+ j > 1 &&
121
+ a.charCodeAt(i - 1) === b.charCodeAt(j - 2) &&
122
+ a.charCodeAt(i - 2) === b.charCodeAt(j - 1)
123
+ ) {
124
+ d = minI32(d, twoUp[j - 2] + 1)
125
+ }
126
+ into[j] = d
127
+ }
128
+ }
129
+ const last = rows[toI32(a.length) % 3]
130
+ return n >= 0 && n < toI32(last.length) ? last[n] : n
131
+ }
132
+
133
+ /**
134
+ * The candidate in `names` closest to `typed`, or empty when none is within
135
+ * `max(2, w / 3)` edits for a `typed` of `w` bytes past its dashes; the first
136
+ * declared wins a tie.
137
+ */
138
+ const closest = (typed: string, names: string[]): string => {
139
+ let longest: i32 = 0
140
+ for (let i: i32 = 0; i < toI32(names.length); i++) {
141
+ longest = toI32(names[i].length) > longest ? toI32(names[i].length) : longest
142
+ }
143
+ const rows: i32[][] = [[], [], []]
144
+ for (let j: i32 = 0; j <= longest; j++) {
145
+ rows[0].push(j)
146
+ rows[1].push(j)
147
+ rows[2].push(j)
148
+ }
149
+ let dashes: i32 = 0
150
+ while (dashes < toI32(typed.length) && toI32(typed.charCodeAt(dashes)) === DASH) {
151
+ dashes = dashes + 1
152
+ }
153
+ const third: i32 = (toI32(typed.length) - dashes) / 3
154
+ let best: i32 = third > 2 ? third : 2
155
+ let at: i32 = -1
156
+ for (let i: i32 = 0; i < toI32(names.length); i++) {
157
+ const d = editDistance(typed, names[i], rows)
158
+ if (d <= best && (at < 0 || d < best)) {
159
+ best = d
160
+ at = i
161
+ }
162
+ }
163
+ return at >= 0 && at < toI32(names.length) ? names[at] : ""
164
+ }
165
+
166
+ /** A flag spec taken apart: `-r, --raw-output`, `--limit <n>`, `--no-color`. */
167
+ class FlagSpec {
168
+ short: string
169
+ long: string
170
+ meta: string
171
+ value: i32
172
+ negated: boolean
173
+
174
+ constructor(call: string, spec: string) {
175
+ let rest = spec
176
+ this.short = ""
177
+ if (rest.startsWith("-") && !rest.startsWith("--")) {
178
+ if (!(rest.length >= 6 && rest.substring(2, 4) === ", ")) {
179
+ panic(`cli-args: ${call}: bad flag spec '${spec}': a short flag is one letter, then ', --name'`)
180
+ }
181
+ this.short = rest.substring(1, 2)
182
+ if (!isWord(this.short)) {
183
+ panic(`cli-args: ${call}: bad flag spec '${spec}': the short flag is not a letter`)
184
+ }
185
+ rest = rest.substring(4)
186
+ }
187
+ if (!rest.startsWith("--")) {
188
+ panic(`cli-args: ${call}: bad flag spec '${spec}': a flag is written '--name'`)
189
+ }
190
+ const space: i32 = toI32(rest.indexOf(" "))
191
+ this.long = space < 0 ? rest.substring(2) : rest.substring(2, space)
192
+ this.meta = space < 0 ? "" : rest.substring(space + 1)
193
+ if (!isWord(this.long)) {
194
+ panic(
195
+ `cli-args: ${call}: bad flag spec '${spec}': the name is empty or has a space, '=' or leading dash`
196
+ )
197
+ }
198
+ this.value = VALUE_NONE
199
+ if (this.meta.length > 0) {
200
+ const inner = this.meta.substring(1, this.meta.length - 1)
201
+ if (this.meta.startsWith("<") && this.meta.endsWith(">") && isWord(inner)) {
202
+ this.value = VALUE_REQUIRED
203
+ } else if (this.meta.startsWith("[") && this.meta.endsWith("]") && isWord(inner)) {
204
+ this.value = VALUE_OPTIONAL
205
+ } else {
206
+ panic(`cli-args: ${call}: bad flag spec '${spec}': a value is written <value> or [value]`)
207
+ }
208
+ }
209
+ this.negated = this.value === VALUE_NONE && this.long.startsWith("no-") && this.long.length > 3
210
+ if (this.negated) {
211
+ this.long = this.long.substring(3)
212
+ }
213
+ }
214
+ }
215
+
216
+ /**
217
+ * A command tree: groups, which only hold commands, and leaf commands, which
218
+ * a program runs, each with its flags and positional arguments. Every
219
+ * declaration answers a handle (an `i32`) that `parse`'s answer is read by.
220
+ */
221
+ export class Cli {
222
+ name: string
223
+ version: string
224
+ description: string
225
+
226
+ cmdName: string[]
227
+ cmdParent: i32[]
228
+ cmdId: i32[]
229
+ cmdDesc: string[]
230
+ cmdGroup: boolean[]
231
+ cmdPath: string[]
232
+ cmdAliases: string[]
233
+ aliasOwner: i32[]
234
+ aliasName: string[]
235
+ cmdEpilog: string[]
236
+ cmdFirstArg: i32[]
237
+ cmdLastArg: i32[]
238
+ cmdVariadic: boolean[]
239
+ cmdOptionalArg: boolean[]
240
+ children: Map<string, i32>
241
+
242
+ flagOwner: i32[]
243
+ flagShort: string[]
244
+ flagLong: string[]
245
+ flagMeta: string[]
246
+ flagValue: i32[]
247
+ flagNegated: boolean[]
248
+ flagNegatable: boolean[]
249
+ flagDesc: string[]
250
+ flagDefault: string[]
251
+ flagHasDefault: boolean[]
252
+ flagRequired: boolean[]
253
+ flagRepeatable: boolean[]
254
+ flags: Map<string, i32>
255
+ negations: Map<string, i32>
256
+ cmdRequired: i32[][]
257
+ globalRequired: i32[]
258
+ ids: Map<string, i32>
259
+
260
+ argOwner: i32[]
261
+ argName: string[]
262
+ argRequired: boolean[]
263
+ argVariadic: boolean[]
264
+ argDesc: string[]
265
+ argIndex: i32[]
266
+ argNext: i32[]
267
+
268
+ /** A tree with only its root, the program `name` at `version`, for `description`. */
269
+ constructor(name: string, version: string, description: string) {
270
+ this.name = name
271
+ this.version = version
272
+ this.description = description
273
+ this.cmdName = [name]
274
+ this.cmdParent = [-1]
275
+ this.cmdId = [-1]
276
+ this.cmdDesc = [description]
277
+ this.cmdGroup = [false]
278
+ this.cmdPath = [name]
279
+ this.cmdAliases = [""]
280
+ this.aliasOwner = []
281
+ this.aliasName = []
282
+ this.cmdEpilog = [""]
283
+ this.cmdFirstArg = [-1]
284
+ this.cmdLastArg = [-1]
285
+ this.cmdVariadic = [false]
286
+ this.cmdOptionalArg = [false]
287
+ this.cmdRequired = [[]]
288
+ this.children = new Map<string, i32>()
289
+ this.flagOwner = []
290
+ this.flagShort = []
291
+ this.flagLong = []
292
+ this.flagMeta = []
293
+ this.flagValue = []
294
+ this.flagNegated = []
295
+ this.flagNegatable = []
296
+ this.flagDesc = []
297
+ this.flagDefault = []
298
+ this.flagHasDefault = []
299
+ this.flagRequired = []
300
+ this.flagRepeatable = []
301
+ this.flags = new Map<string, i32>()
302
+ this.negations = new Map<string, i32>()
303
+ this.globalRequired = []
304
+ this.ids = new Map<string, i32>()
305
+ this.argOwner = []
306
+ this.argName = []
307
+ this.argRequired = []
308
+ this.argVariadic = []
309
+ this.argDesc = []
310
+ this.argIndex = []
311
+ this.argNext = []
312
+ }
313
+
314
+ /** The command `handle`, or a panic naming `call` when there is none. */
315
+ checkCommand(call: string, handle: i32): i32 {
316
+ if (!(handle >= 0 && handle < toI32(this.cmdName.length))) {
317
+ panic(`cli-args: ${call}: ${handle} is not a command handle`)
318
+ }
319
+ return handle
320
+ }
321
+
322
+ /** The flag `handle`, or a panic naming `call` when there is none. */
323
+ checkFlag(call: string, handle: i32): i32 {
324
+ if (!(handle >= 0 && handle < toI32(this.flagLong.length))) {
325
+ panic(`cli-args: ${call}: ${handle} is not a flag handle`)
326
+ }
327
+ return handle
328
+ }
329
+
330
+ /** The argument `handle`, or a panic naming `call` when there is none. */
331
+ checkArgument(call: string, handle: i32): i32 {
332
+ if (!(handle >= 0 && handle < toI32(this.argName.length))) {
333
+ panic(`cli-args: ${call}: ${handle} is not an argument handle`)
334
+ }
335
+ return handle
336
+ }
337
+
338
+ /** The command line that names `handle`: the program, then each command down to it. */
339
+ pathOf(handle: i32): string {
340
+ return this.cmdPath[this.checkCommand("path", handle)]
341
+ }
342
+
343
+ /** Whether `handle` holds commands rather than running: a group, or a root given children. */
344
+ isGroup(handle: i32): boolean {
345
+ const h = this.checkCommand("isGroup", handle)
346
+ return this.cmdGroup[h]
347
+ }
348
+
349
+ /** A new child of `parent`; `call` names the declaration in a panic. */
350
+ addCommand(call: string, parent: i32, name: string, id: i32, description: string, group: boolean): i32 {
351
+ const p = this.checkCommand(call, parent)
352
+ if (!isWord(name)) {
353
+ panic(`cli-args: ${call}: '${name}' is not a command name`)
354
+ }
355
+ if (p !== ROOT && !this.cmdGroup[p]) {
356
+ panic(`cli-args: ${call}: ${this.pathOf(p)} is a command, not a group`)
357
+ }
358
+ if (this.cmdFirstArg[p] >= 0) {
359
+ panic(`cli-args: ${call}: ${this.pathOf(p)} takes arguments, so it cannot hold commands`)
360
+ }
361
+ if (toI32(this.cmdRequired[p].length) > 0) {
362
+ panic(`cli-args: ${call}: ${this.pathOf(p)} has a required flag, so it cannot hold commands`)
363
+ }
364
+ const key = scopeKey(p, name)
365
+ if (this.children.has(key)) {
366
+ panic(`cli-args: ${call}: ${this.pathOf(p)} already has a command '${name}'`)
367
+ }
368
+ const handle: i32 = toI32(this.cmdName.length)
369
+ this.children.set(key, handle)
370
+ this.cmdGroup[p] = true
371
+ this.cmdName.push(name)
372
+ this.cmdParent.push(p)
373
+ this.cmdPath.push(`${this.cmdPath[p]} ${name}`)
374
+ this.cmdId.push(id)
375
+ this.cmdDesc.push(description)
376
+ this.cmdGroup.push(group)
377
+ this.cmdAliases.push("")
378
+ this.cmdEpilog.push("")
379
+ this.cmdFirstArg.push(NONE)
380
+ this.cmdLastArg.push(NONE)
381
+ this.cmdVariadic.push(false)
382
+ this.cmdOptionalArg.push(false)
383
+ this.cmdRequired.push(new Array<i32>(0))
384
+ return handle
385
+ }
386
+
387
+ /** A group under `parent`: it holds commands, and naming it without one is an error. */
388
+ group(parent: i32, name: string, description: string): i32 {
389
+ return this.addCommand(`group(${name})`, parent, name, NONE, description, true)
390
+ }
391
+
392
+ /**
393
+ * A command under `parent` that the program runs, answered by `id` in
394
+ * `ParseResult.id`: not negative, which is a group's, and no other command's.
395
+ */
396
+ command(parent: i32, name: string, id: i32, description: string): i32 {
397
+ const call = `command(${name})`
398
+ if (id < 0) {
399
+ panic(`cli-args: ${call}: the id ${id} is negative, which a group answers`)
400
+ }
401
+ const key = `${id}`
402
+ const other = this.ids.get(key) ?? NONE
403
+ if (other >= 0) {
404
+ panic(`cli-args: ${call}: ${this.pathOf(other)} already has the id ${id}`)
405
+ }
406
+ const handle = this.addCommand(call, parent, name, id, description, false)
407
+ this.ids.set(key, handle)
408
+ return handle
409
+ }
410
+
411
+ /** Another name for the command or group `handle`, in the same scope. */
412
+ alias(handle: i32, name: string): void {
413
+ const call = `alias(${name})`
414
+ const h = this.checkCommand(call, handle)
415
+ if (h === ROOT) {
416
+ panic(`cli-args: ${call}: the root has no alias`)
417
+ }
418
+ if (!isWord(name)) {
419
+ panic(`cli-args: ${call}: '${name}' is not a command name`)
420
+ }
421
+ const key = scopeKey(this.cmdParent[h], name)
422
+ if (this.children.has(key)) {
423
+ panic(`cli-args: ${call}: ${this.pathOf(this.cmdParent[h])} already has a command '${name}'`)
424
+ }
425
+ this.children.set(key, h)
426
+ this.aliasOwner.push(h)
427
+ this.aliasName.push(name)
428
+ this.cmdAliases[h] = this.cmdAliases[h].length > 0 ? `${this.cmdAliases[h]}, ${name}` : name
429
+ }
430
+
431
+ /** Whether a flag of `scope` (a command, or `GLOBAL`) answers to `key`, as itself or as a negation. */
432
+ taken(scope: i32, key: string): boolean {
433
+ const k = scopeKey(scope, key)
434
+ return this.flags.has(k) || this.negations.has(k)
435
+ }
436
+
437
+ /** Whether `scope` already answers to any of `--long`, its negation (for `--no-x`), or `-short`. */
438
+ scopeHas(scope: i32, spec: FlagSpec): boolean {
439
+ if (this.taken(scope, `--${spec.long}`) || (spec.negated && this.taken(scope, `--no-${spec.long}`))) {
440
+ return true
441
+ }
442
+ return spec.short.length > 0 && this.taken(scope, `-${spec.short}`)
443
+ }
444
+
445
+ /** Whether flag `f` answers to `--name`, as itself or, negatable, as its negation. */
446
+ answersTo(f: i32, name: string): boolean {
447
+ return this.flagLong[f] === name || (this.flagNegatable[f] && `no-${this.flagLong[f]}` === name)
448
+ }
449
+
450
+ /** A new flag in `scope` from `spec`; `call` names the declaration in a panic. */
451
+ addFlag(call: string, scope: i32, spec: string, description: string): i32 {
452
+ const s = new FlagSpec(call, spec)
453
+ if (s.long === "help" || s.long === "version" || s.short === "h" || s.short === "v") {
454
+ panic(`cli-args: ${call}: -h, --help, -v and --version are taken`)
455
+ }
456
+ if (this.scopeHas(scope, s)) {
457
+ const where = scope === GLOBAL ? "the global flags" : this.pathOf(scope)
458
+ panic(`cli-args: ${call}: ${where} already has a flag of that name`)
459
+ }
460
+ if (scope === GLOBAL) {
461
+ let clash: i32 = -1
462
+ for (let f: i32 = 0; f < toI32(this.flagLong.length) && clash < 0; f++) {
463
+ if (
464
+ this.answersTo(f, s.long) ||
465
+ (s.negated && this.answersTo(f, `no-${s.long}`)) ||
466
+ (s.short.length > 0 && this.flagShort[f] === s.short)
467
+ ) {
468
+ clash = f
469
+ }
470
+ }
471
+ if (clash >= 0) {
472
+ panic(`cli-args: ${call}: ${this.pathOf(this.flagOwner[clash])} already has a flag of that name`)
473
+ }
474
+ } else if (this.scopeHas(GLOBAL, s)) {
475
+ panic(`cli-args: ${call}: a global flag of that name is already declared`)
476
+ }
477
+ const handle: i32 = toI32(this.flagLong.length)
478
+ this.flags.set(scopeKey(scope, `--${s.long}`), handle)
479
+ if (s.negated) {
480
+ this.negations.set(scopeKey(scope, `--no-${s.long}`), handle)
481
+ }
482
+ if (s.short.length > 0) {
483
+ this.flags.set(scopeKey(scope, `-${s.short}`), handle)
484
+ }
485
+ this.flagOwner.push(scope)
486
+ this.flagShort.push(s.short)
487
+ this.flagLong.push(s.long)
488
+ this.flagMeta.push(s.meta)
489
+ this.flagValue.push(s.value)
490
+ this.flagNegated.push(s.negated)
491
+ this.flagNegatable.push(s.negated)
492
+ this.flagDesc.push(description)
493
+ this.flagDefault.push("")
494
+ this.flagHasDefault.push(false)
495
+ this.flagRequired.push(false)
496
+ this.flagRepeatable.push(false)
497
+ return handle
498
+ }
499
+
500
+ /** A flag every command takes, before, between or after the command names. */
501
+ globalFlag(spec: string, description: string): i32 {
502
+ return this.addFlag(`globalFlag("${spec}")`, GLOBAL, spec, description)
503
+ }
504
+
505
+ /** A flag the command or group `cmd` takes, and no other. */
506
+ flag(cmd: i32, spec: string, description: string): i32 {
507
+ const call = `flag("${spec}")`
508
+ return this.addFlag(call, this.checkCommand(call, cmd), spec, description)
509
+ }
510
+
511
+ /** The value flag `f` answers when it is not given. */
512
+ defaultValue(f: i32, value: string): void {
513
+ const call = `defaultValue(${value})`
514
+ const h = this.checkFlag(call, f)
515
+ if (this.flagValue[h] === VALUE_NONE) {
516
+ panic(`cli-args: ${call}: --${this.flagLong[h]} takes no value`)
517
+ }
518
+ if (this.flagRequired[h]) {
519
+ panic(`cli-args: ${call}: --${this.flagLong[h]} is required, so a default would never be read`)
520
+ }
521
+ this.flagDefault[h] = value
522
+ this.flagHasDefault[h] = true
523
+ }
524
+
525
+ /**
526
+ * Makes `f` an error to leave out, for the command that owns it (every
527
+ * command, for a global); a group's flag cannot be, as no parse ends at one.
528
+ */
529
+ required(f: i32): void {
530
+ const h = this.checkFlag("required", f)
531
+ const owner: i32 = this.flagOwner[h]
532
+ if (owner !== GLOBAL && this.cmdGroup[owner]) {
533
+ panic(
534
+ `cli-args: required: ${this.pathOf(owner)} holds commands, so --${this.flagLong[h]} can never be checked`
535
+ )
536
+ }
537
+ if (this.flagHasDefault[h]) {
538
+ panic(`cli-args: required: --${this.flagLong[h]} has a default, so it can never be missing`)
539
+ }
540
+ if (!this.flagRequired[h]) {
541
+ this.flagRequired[h] = true
542
+ if (owner === GLOBAL) {
543
+ this.globalRequired.push(h)
544
+ } else {
545
+ this.cmdRequired[owner].push(h)
546
+ }
547
+ }
548
+ }
549
+
550
+ /** Makes `f` gather every value it is given, in order, for `ParseResult.values`. */
551
+ repeatable(f: i32): void {
552
+ const h = this.checkFlag("repeatable", f)
553
+ if (this.flagValue[h] === VALUE_NONE) {
554
+ panic(`cli-args: repeatable: --${this.flagLong[h]} takes no value`)
555
+ }
556
+ this.flagRepeatable[h] = true
557
+ }
558
+
559
+ /** Adds `--no-x` to the boolean flag `--x`, which turns it back off. */
560
+ negatable(f: i32): void {
561
+ const h = this.checkFlag("negatable", f)
562
+ const call = `negatable(--${this.flagLong[h]})`
563
+ if (this.flagValue[h] !== VALUE_NONE || this.flagNegated[h]) {
564
+ panic(`cli-args: ${call}: only a boolean flag declared '--name' can be negated`)
565
+ }
566
+ const owner: i32 = this.flagOwner[h]
567
+ const name = `no-${this.flagLong[h]}`
568
+ let clash = this.taken(owner, `--${name}`) || (owner !== GLOBAL && this.taken(GLOBAL, `--${name}`))
569
+ for (let g: i32 = 0; g < toI32(this.flagLong.length) && owner === GLOBAL && !clash; g++) {
570
+ clash = this.answersTo(g, name)
571
+ }
572
+ if (clash) {
573
+ panic(`cli-args: ${call}: --${name} is already declared`)
574
+ }
575
+ this.negations.set(scopeKey(owner, `--${name}`), h)
576
+ this.flagNegatable[h] = true
577
+ }
578
+
579
+ /**
580
+ * A positional argument of the command `cmd`: `<name>` required, `[name]`
581
+ * optional, `<name...>` or `[name...]` taking every one left, last only.
582
+ */
583
+ argument(cmd: i32, spec: string, description: string): i32 {
584
+ const call = `argument("${spec}")`
585
+ const c = this.checkCommand(call, cmd)
586
+ if (this.cmdGroup[c]) {
587
+ panic(`cli-args: ${call}: ${this.pathOf(c)} holds commands, so it takes no arguments`)
588
+ }
589
+ const required = spec.startsWith("<") && spec.endsWith(">")
590
+ if (!required && !(spec.startsWith("[") && spec.endsWith("]"))) {
591
+ panic(`cli-args: ${call}: bad argument spec: an argument is written <name> or [name]`)
592
+ }
593
+ let name = spec.substring(1, spec.length - 1)
594
+ const variadic = name.endsWith("...")
595
+ if (variadic) {
596
+ name = name.substring(0, name.length - 3)
597
+ }
598
+ if (!isWord(name)) {
599
+ panic(`cli-args: ${call}: bad argument spec: the name is empty or has a space, '=' or leading dash`)
600
+ }
601
+ if (this.cmdVariadic[c]) {
602
+ panic(`cli-args: ${call}: no argument may follow a variadic one`)
603
+ }
604
+ if (required && this.cmdOptionalArg[c]) {
605
+ panic(`cli-args: ${call}: a required argument may not follow an optional one`)
606
+ }
607
+ const handle: i32 = toI32(this.argName.length)
608
+ const last: i32 = this.cmdLastArg[c]
609
+ let index: i32 = 0
610
+ if (last >= 0 && last < toI32(this.argNext.length)) {
611
+ this.argNext[last] = handle
612
+ index = this.argIndex[last] + 1
613
+ } else {
614
+ this.cmdFirstArg[c] = handle
615
+ }
616
+ this.cmdLastArg[c] = handle
617
+ this.cmdVariadic[c] = variadic
618
+ this.cmdOptionalArg[c] = !required
619
+ this.argOwner.push(c)
620
+ this.argName.push(name)
621
+ this.argRequired.push(required)
622
+ this.argVariadic.push(variadic)
623
+ this.argDesc.push(description)
624
+ this.argIndex.push(index)
625
+ this.argNext.push(NONE)
626
+ return handle
627
+ }
628
+
629
+ /** Text shown at the end of the help of `handle` (a command or group), as given. */
630
+ epilog(handle: i32, text: string): void {
631
+ const h = this.checkCommand("epilog", handle)
632
+ this.cmdEpilog[h] = text
633
+ }
634
+
635
+ /**
636
+ * The flag `name` (dashes included) names in `table` at `cmd`: its own, then
637
+ * a global; -1 for none. `table` is `flags`, or `negations` for `--no-x`.
638
+ */
639
+ lookup(table: Map<string, i32>, cmd: i32, name: string): i32 {
640
+ const own = table.get(scopeKey(cmd, name)) ?? -1
641
+ return own >= 0 ? own : (table.get(scopeKey(GLOBAL, name)) ?? -1)
642
+ }
643
+
644
+ /** How `f` is spelled on the command line: its long name, or `--no-x` for one declared that way. */
645
+ spelling(f: i32): string {
646
+ const h = this.checkFlag("spelling", f)
647
+ return this.flagNegated[h] ? `--no-${this.flagLong[h]}` : `--${this.flagLong[h]}`
648
+ }
649
+
650
+ /** Every long flag `cmd` answers to, for a did-you-mean. */
651
+ longNames(cmd: i32): string[] {
652
+ const names: string[] = []
653
+ for (let f: i32 = 0; f < toI32(this.flagLong.length); f++) {
654
+ if (this.flagOwner[f] === cmd || this.flagOwner[f] === GLOBAL) {
655
+ names.push(`--${this.flagLong[f]}`)
656
+ if (this.flagNegatable[f]) {
657
+ names.push(`--no-${this.flagLong[f]}`)
658
+ }
659
+ }
660
+ }
661
+ names.push("--help")
662
+ if (cmd === ROOT) {
663
+ names.push("--version")
664
+ }
665
+ return names
666
+ }
667
+
668
+ /** The names `cmd`'s children answer to, in declaration order, then their aliases when `withAliases`. */
669
+ childNames(cmd: i32, withAliases: boolean): string[] {
670
+ const names: string[] = []
671
+ for (let c: i32 = 0; c < toI32(this.cmdName.length); c++) {
672
+ if (this.cmdParent[c] === cmd) {
673
+ names.push(this.cmdName[c])
674
+ }
675
+ }
676
+ for (
677
+ let i: i32 = 0;
678
+ withAliases && i < toI32(this.aliasName.length) && i < toI32(this.aliasOwner.length);
679
+ i++
680
+ ) {
681
+ const owner: i32 = this.aliasOwner[i]
682
+ if (owner >= 0 && owner < toI32(this.cmdParent.length) && this.cmdParent[owner] === cmd) {
683
+ names.push(this.aliasName[i])
684
+ }
685
+ }
686
+ return names
687
+ }
688
+
689
+ /**
690
+ * Parses `argv` from index `start` (1 for `process.argv`, past the
691
+ * program's path) in one walk over it.
692
+ */
693
+ parse(argv: string[], start: i32): ParseResult {
694
+ const r = new ParseResult(this)
695
+ let i: i32 = start < 0 ? 0 : start
696
+ let dashed = false
697
+ while (i >= 0 && i < toI32(argv.length)) {
698
+ const token = argv[i]
699
+ i = i + 1
700
+ if (dashed) {
701
+ r.positionalValues.push(token)
702
+ r.restValues.push(token)
703
+ } else if (token === "--") {
704
+ dashed = true
705
+ } else if (token.length > 1 && toI32(token.charCodeAt(0)) === DASH) {
706
+ i = token.startsWith("--") ? this.longFlag(r, argv, i, token) : this.shortFlags(r, argv, i, token)
707
+ } else if (this.cmdGroup[r.command] && !r.badCommand) {
708
+ const child = this.children.get(scopeKey(r.command, token)) ?? -1
709
+ if (child >= 0) {
710
+ r.command = child
711
+ } else if (r.command === ROOT && token === "help") {
712
+ r.wantHelp = true
713
+ r.helpWord = true
714
+ } else {
715
+ this.unknownCommand(r, token)
716
+ }
717
+ } else {
718
+ r.positionalValues.push(token)
719
+ }
720
+ }
721
+ r.finishWalk()
722
+ return r
723
+ }
724
+
725
+ /** Records `token` as no command `r`'s group holds, with the nearest one that it does. */
726
+ unknownCommand(r: ParseResult, token: string): void {
727
+ const near = closest(token, this.childNames(r.command, true))
728
+ r.badCommand = true
729
+ r.fail(
730
+ r.command,
731
+ near.length > 0 ? `unknown command '${token}' (did you mean '${near}'?)` : `unknown command '${token}'`
732
+ )
733
+ }
734
+
735
+ /** Records `--name`, `--name=value`, `--name value` or `--no-name` from `token`; answers the next index. */
736
+ longFlag(r: ParseResult, argv: string[], next: i32, token: string): i32 {
737
+ const eq: i32 = toI32(token.indexOf("="))
738
+ const name = eq < 0 ? token : token.substring(0, eq)
739
+ if (name === "--help") {
740
+ r.wantHelp = true
741
+ return next
742
+ }
743
+ if (name === "--version" && r.command === ROOT) {
744
+ r.wantVersion = true
745
+ return next
746
+ }
747
+ const f = this.lookup(this.flags, r.command, name)
748
+ if (f >= 0) {
749
+ if (eq >= 0) {
750
+ if (this.flagValue[f] === VALUE_NONE) {
751
+ r.fail(r.command, `${name} takes no value`)
752
+ } else {
753
+ r.set(f, token.substring(eq + 1, token.length))
754
+ }
755
+ return next
756
+ }
757
+ return this.takeValue(r, argv, next, f, "")
758
+ }
759
+ const off = this.lookup(this.negations, r.command, name)
760
+ if (off >= 0) {
761
+ if (eq >= 0) {
762
+ r.fail(r.command, `${name} takes no value`)
763
+ } else {
764
+ r.set(off, "false")
765
+ }
766
+ return next
767
+ }
768
+ const near = closest(name, this.longNames(r.command))
769
+ r.fail(
770
+ r.command,
771
+ near.length > 0 ? `unknown flag ${name} (did you mean ${near}?)` : `unknown flag ${name}`
772
+ )
773
+ return next
774
+ }
775
+
776
+ /** Records the cluster `-abc` (or `-l5`, `-l=5`, `-l 5`) from `token`; answers the next index. */
777
+ shortFlags(r: ParseResult, argv: string[], next: i32, token: string): i32 {
778
+ for (let k: i32 = 1; k < toI32(token.length); k++) {
779
+ const letter = token.substring(k, k + 1)
780
+ if (letter === "h") {
781
+ r.wantHelp = true
782
+ } else if (letter === "v" && r.command === ROOT) {
783
+ r.wantVersion = true
784
+ } else {
785
+ const f = this.lookup(this.flags, r.command, `-${letter}`)
786
+ if (f < 0) {
787
+ r.fail(r.command, `unknown flag -${letter}`)
788
+ return next
789
+ }
790
+ const attached = token.substring(k + 1, token.length)
791
+ if (this.flagValue[f] !== VALUE_NONE) {
792
+ if (attached.startsWith("=")) {
793
+ r.set(f, attached.substring(1, attached.length))
794
+ return next
795
+ }
796
+ return this.takeValue(r, argv, next, f, attached)
797
+ }
798
+ if (attached.startsWith("=")) {
799
+ r.fail(r.command, `-${letter} takes no value`)
800
+ return next
801
+ }
802
+ r.set(f, this.flagNegated[f] ? "false" : "true")
803
+ }
804
+ }
805
+ return next
806
+ }
807
+
808
+ /**
809
+ * Records flag `f` as given, with `attached` (from `-l5`) as its value when
810
+ * not empty, or else the token at `next` when the flag's value takes it;
811
+ * answers the index after whatever it took.
812
+ */
813
+ takeValue(r: ParseResult, argv: string[], next: i32, f: i32, attached: string): i32 {
814
+ const kind: i32 = this.flagValue[f]
815
+ if (kind === VALUE_NONE) {
816
+ r.set(f, "true")
817
+ return next
818
+ }
819
+ if (attached.length > 0) {
820
+ r.set(f, attached)
821
+ return next
822
+ }
823
+ const has = next >= 0 && next < toI32(argv.length)
824
+ if (kind === VALUE_REQUIRED) {
825
+ if (!has) {
826
+ r.fail(r.command, `--${this.flagLong[f]} needs a value`)
827
+ return next
828
+ }
829
+ r.set(f, argv[next])
830
+ return next + 1
831
+ }
832
+ if (has && (argv[next].length === 0 || toI32(argv[next].charCodeAt(0)) !== DASH)) {
833
+ r.set(f, argv[next])
834
+ return next + 1
835
+ }
836
+ r.set(f, "")
837
+ return next
838
+ }
839
+
840
+ /** A help row's flag column for `f`: `-s, --long`, with `--[no-]x` for a negatable one. */
841
+ flagLabel(f: i32): string {
842
+ const long = this.flagNegatable[f] ? `--[no-]${this.flagLong[f]}` : this.spelling(f)
843
+ return this.flagShort[f].length > 0 ? `-${this.flagShort[f]}, ${long}` : long
844
+ }
845
+
846
+ /** A help row's description for `f`, with what it is: repeatable, its default, required. */
847
+ flagText(f: i32): string {
848
+ let text = this.flagDesc[f]
849
+ if (this.flagRepeatable[f]) {
850
+ text = `${text} (repeatable)`
851
+ }
852
+ if (this.flagHasDefault[f]) {
853
+ text = `${text} (default: ${this.flagDefault[f]})`
854
+ }
855
+ if (this.flagRequired[f]) {
856
+ text = `${text} (required)`
857
+ }
858
+ return text
859
+ }
860
+
861
+ /** Adds a `section` row to `rows` for every flag `owner` (a command, or `GLOBAL`) declares. */
862
+ flagRows(rows: HelpRow[], section: string, owner: i32): void {
863
+ for (let f: i32 = 0; f < toI32(this.flagLong.length); f++) {
864
+ if (this.flagOwner[f] === owner) {
865
+ rows.push(new HelpRow(section, this.flagLabel(f), this.flagMeta[f], this.flagText(f)))
866
+ }
867
+ }
868
+ }
869
+
870
+ /** The help of the command or group `handle`, painted with `style`. */
871
+ helpText(handle: i32, style: Style): string {
872
+ const cmd = this.checkCommand("help", handle)
873
+ const rows: HelpRow[] = []
874
+ const shape: string[] = []
875
+ const path = this.pathOf(cmd)
876
+ if (path.length > this.name.length) {
877
+ shape.push(path.substring(this.name.length + 1, path.length))
878
+ }
879
+ if (this.cmdGroup[cmd]) {
880
+ shape.push("<command>")
881
+ for (let c: i32 = 0; c < toI32(this.cmdName.length); c++) {
882
+ if (this.cmdParent[c] === cmd) {
883
+ const aliases = this.cmdAliases[c]
884
+ const label = aliases.length > 0 ? `${this.cmdName[c]}, ${aliases}` : this.cmdName[c]
885
+ rows.push(new HelpRow("Commands", label, "", this.cmdDesc[c]))
886
+ }
887
+ }
888
+ }
889
+ let a: i32 = this.cmdFirstArg[cmd]
890
+ while (a >= 0 && a < toI32(this.argName.length)) {
891
+ const dots = this.argVariadic[a] ? "..." : ""
892
+ const label = this.argRequired[a] ? `<${this.argName[a]}${dots}>` : `[${this.argName[a]}${dots}]`
893
+ shape.push(label)
894
+ rows.push(new HelpRow("Arguments", label, "", this.argDesc[a]))
895
+ a = this.argNext[a]
896
+ }
897
+ shape.push("[flags]")
898
+ this.flagRows(rows, "Flags", cmd)
899
+ rows.push(new HelpRow("Flags", "-h, --help", "", "show help"))
900
+ if (cmd === ROOT) {
901
+ rows.push(new HelpRow("Flags", "-v, --version", "", "show the version"))
902
+ }
903
+ this.flagRows(rows, "Global flags", GLOBAL)
904
+ let widest: i32 = 0
905
+ for (let i: i32 = 0; i < toI32(rows.length); i++) {
906
+ const row = rows[i]
907
+ const width: i32 = 2 + toI32(row.label.length) + (row.meta.length > 0 ? 1 + toI32(row.meta.length) : 0)
908
+ widest = width > widest ? width : widest
909
+ }
910
+ const usage = shape.join(" ")
911
+ const usageColumn: i32 = 8 + toI32(this.name.length) + toI32(usage.length) + 3
912
+ const menu = new HelpMenu(style, this.name, minI32(widest + 2, MAX_COLUMN), usageColumn)
913
+ menu.header(this.version, this.description)
914
+ menu.usage(usage, cmd === ROOT ? "" : this.cmdDesc[cmd])
915
+ let open = ""
916
+ for (let i: i32 = 0; i < toI32(rows.length); i++) {
917
+ const row = rows[i]
918
+ if (row.section !== open) {
919
+ open = row.section
920
+ menu.section(open)
921
+ }
922
+ menu.row(row.label, row.meta, row.text)
923
+ }
924
+ if (this.cmdEpilog[cmd].length > 0) {
925
+ menu.line("")
926
+ menu.line(this.cmdEpilog[cmd])
927
+ }
928
+ return menu.text()
929
+ }
930
+ }
931
+
932
+ /** One row of a help page, kept until the widest row sets the column. */
933
+ class HelpRow {
934
+ section: string
935
+ label: string
936
+ meta: string
937
+ text: string
938
+
939
+ constructor(section: string, label: string, meta: string, text: string) {
940
+ this.section = section
941
+ this.label = label
942
+ this.meta = meta
943
+ this.text = text
944
+ }
945
+ }
946
+
947
+ /**
948
+ * What `Cli.parse` found: the command the arguments name and every value
949
+ * they set, or that they ask for help or the version, or what is wrong with
950
+ * them. Values are read by the handles the tree was declared with.
951
+ */
952
+ export class ParseResult {
953
+ cli: Cli
954
+ /** The command line that names it: `api users keys list`. */
955
+ path: string
956
+ /** The error line for `PARSE_ERROR`, the version line for `PARSE_VERSION`, else empty. */
957
+ message: string
958
+ /** The line after an error that says where the usage is; empty without one. */
959
+ hint: string
960
+ /** The command line of the command an error was found at; empty without one. */
961
+ errorPath: string
962
+ setFlags: i32[]
963
+ setValues: string[]
964
+ positionalValues: string[]
965
+ restValues: string[]
966
+ /** `PARSE_OK`, `PARSE_HELP`, `PARSE_VERSION` or `PARSE_ERROR`. */
967
+ kind: i32
968
+ /** The handle of the deepest command the arguments reached. */
969
+ command: i32
970
+ /** That command's id, or -1 for a group. */
971
+ id: i32
972
+ /** The status to exit with: 2 for an error, 0 otherwise. */
973
+ exitCode: i32
974
+ wantHelp: boolean
975
+ /** Whether help was asked for by the word `help`, which a path follows. */
976
+ helpWord: boolean
977
+ /** Whether a word named no command, which `help <path>` does not forgive. */
978
+ badCommand: boolean
979
+ wantVersion: boolean
980
+
981
+ constructor(cli: Cli) {
982
+ this.cli = cli
983
+ this.kind = PARSE_OK
984
+ this.command = ROOT
985
+ this.id = -1
986
+ this.path = ""
987
+ this.message = ""
988
+ this.hint = ""
989
+ this.exitCode = 0
990
+ this.errorPath = ""
991
+ this.wantHelp = false
992
+ this.helpWord = false
993
+ this.badCommand = false
994
+ this.wantVersion = false
995
+ this.setFlags = []
996
+ this.setValues = []
997
+
998
+ this.positionalValues = []
999
+ this.restValues = []
1000
+ }
1001
+
1002
+ /** Records `text` as the error found at `cmd`, unless one is already recorded: the first wins. */
1003
+ fail(cmd: i32, text: string): void {
1004
+ if (this.message.length === 0) {
1005
+ this.errorPath = this.cli.pathOf(cmd)
1006
+ this.message = `${this.errorPath}: ${text}`
1007
+ this.hint = usageHint(`${this.errorPath} --help`)
1008
+ }
1009
+ }
1010
+
1011
+ /** Records flag `f` as given `value`. */
1012
+ set(f: i32, value: string): void {
1013
+ this.setFlags.push(f)
1014
+ this.setValues.push(value)
1015
+ }
1016
+
1017
+ /**
1018
+ * Settles the kind once the walk is over: help (unless `help <path>` named
1019
+ * no command), then the version, then an error, then the checks only a
1020
+ * whole command line can fail.
1021
+ */
1022
+ finishWalk(): void {
1023
+ const cli = this.cli
1024
+ const cmd = this.command
1025
+ this.id = cli.cmdId[cmd]
1026
+ this.path = cli.pathOf(cmd)
1027
+ if (this.wantHelp && !(this.helpWord && this.badCommand)) {
1028
+ this.kind = PARSE_HELP
1029
+ this.message = ""
1030
+ this.hint = ""
1031
+ return
1032
+ }
1033
+ if (this.wantVersion && !this.wantHelp) {
1034
+ this.kind = PARSE_VERSION
1035
+ this.message = `${cli.name} ${cli.version}`
1036
+ this.hint = ""
1037
+ return
1038
+ }
1039
+ if (cli.cmdGroup[cmd]) {
1040
+ this.fail(cmd, `missing command (one of: ${cli.childNames(cmd, false).join(", ")})`)
1041
+ }
1042
+ let missing: i32 = this.firstMissing(cli.cmdRequired[cmd])
1043
+ if (missing < 0) {
1044
+ missing = this.firstMissing(cli.globalRequired)
1045
+ }
1046
+ if (missing >= 0) {
1047
+ this.fail(cmd, `missing required flag ${cli.spelling(missing)}`)
1048
+ }
1049
+ let a: i32 = cli.cmdFirstArg[cmd]
1050
+ let last: i32 = -1
1051
+ while (a >= 0 && a < toI32(cli.argName.length) && this.message.length === 0) {
1052
+ if (cli.argRequired[a] && cli.argIndex[a] >= toI32(this.positionalValues.length)) {
1053
+ const dots = cli.argVariadic[a] ? "..." : ""
1054
+ this.fail(cmd, `missing argument <${cli.argName[a]}${dots}>`)
1055
+ }
1056
+ last = a
1057
+ a = cli.argNext[a]
1058
+ }
1059
+ let taken: i32 = 0
1060
+ if (last >= 0) {
1061
+ taken = cli.argVariadic[last] ? toI32(this.positionalValues.length) : cli.argIndex[last] + 1
1062
+ }
1063
+ if (this.message.length === 0 && taken < toI32(this.positionalValues.length) && taken >= 0) {
1064
+ this.fail(cmd, `unexpected argument '${this.positionalValues[taken]}'`)
1065
+ }
1066
+ if (this.message.length > 0) {
1067
+ this.kind = PARSE_ERROR
1068
+ this.exitCode = 2
1069
+ }
1070
+ }
1071
+
1072
+ /** The first of the `required` flags the arguments did not set, else -1. */
1073
+ firstMissing(required: i32[]): i32 {
1074
+ for (let i: i32 = 0; i < toI32(required.length); i++) {
1075
+ if (this.lastSet(required[i]) < 0) {
1076
+ return required[i]
1077
+ }
1078
+ }
1079
+ return -1
1080
+ }
1081
+
1082
+ /**
1083
+ * Where the last value of flag `f` is in `setValues`, or -1 when it was not
1084
+ * given: a scan of the flags the arguments set, so bounded by their count.
1085
+ */
1086
+ lastSet(f: i32): i32 {
1087
+ for (let i: i32 = toI32(this.setFlags.length) - 1; i >= 0 && i < toI32(this.setFlags.length); i--) {
1088
+ if (this.setFlags[i] === f) {
1089
+ return i
1090
+ }
1091
+ }
1092
+ return NONE
1093
+ }
1094
+
1095
+ /**
1096
+ * Whether flag `f` is on: a boolean's value (true by default for one
1097
+ * declared `--no-x`), or for a flag with a value, whether it was given.
1098
+ */
1099
+ flag(f: i32): boolean {
1100
+ const h = this.cli.checkFlag("flag", f)
1101
+ const at = this.lastSet(h)
1102
+ if (this.cli.flagValue[h] !== VALUE_NONE) {
1103
+ return at >= 0
1104
+ }
1105
+ if (at >= 0 && at < toI32(this.setValues.length)) {
1106
+ return this.setValues[at] === "true"
1107
+ }
1108
+ return this.cli.flagNegated[h]
1109
+ }
1110
+
1111
+ /**
1112
+ * The value of flag `f`: the last one given (empty for `[value]` given
1113
+ * none), else its default, else null.
1114
+ */
1115
+ value(f: i32): string | null {
1116
+ const h = this.cli.checkFlag("value", f)
1117
+ const at = this.lastSet(h)
1118
+ if (at >= 0 && at < toI32(this.setValues.length)) {
1119
+ return this.setValues[at]
1120
+ }
1121
+ return this.cli.flagHasDefault[h] ? this.cli.flagDefault[h] : null
1122
+ }
1123
+
1124
+ /**
1125
+ * Every value of flag `f` in order for a repeatable one, the last alone
1126
+ * for any other, else its default alone, else none.
1127
+ */
1128
+ values(f: i32): string[] {
1129
+ const h = this.cli.checkFlag("values", f)
1130
+ const out: string[] = []
1131
+ if (this.cli.flagRepeatable[h]) {
1132
+ for (let i: i32 = 0; i < toI32(this.setFlags.length) && i < toI32(this.setValues.length); i++) {
1133
+ if (this.setFlags[i] === h) {
1134
+ out.push(this.setValues[i])
1135
+ }
1136
+ }
1137
+ } else {
1138
+ const at = this.lastSet(h)
1139
+ if (at >= 0 && at < toI32(this.setValues.length)) {
1140
+ out.push(this.setValues[at])
1141
+ }
1142
+ }
1143
+ if (out.length === 0 && this.cli.flagHasDefault[h]) {
1144
+ out.push(this.cli.flagDefault[h])
1145
+ }
1146
+ return out
1147
+ }
1148
+
1149
+ /** Where flag `f`'s value came from: `SOURCE_ARGV`, `SOURCE_DEFAULT` or `SOURCE_UNSET`. */
1150
+ source(f: i32): i32 {
1151
+ const h = this.cli.checkFlag("source", f)
1152
+ if (this.lastSet(h) >= 0) {
1153
+ return SOURCE_ARGV
1154
+ }
1155
+ return this.cli.flagHasDefault[h] || this.cli.flagNegated[h] ? SOURCE_DEFAULT : SOURCE_UNSET
1156
+ }
1157
+
1158
+ /** The argument `a`'s value, or null when it was not given or belongs to another command. */
1159
+ positional(a: i32): string | null {
1160
+ const h = this.cli.checkArgument("positional", a)
1161
+ const k: i32 = this.cli.argIndex[h]
1162
+ if (this.cli.argOwner[h] !== this.command || !(k >= 0 && k < toI32(this.positionalValues.length))) {
1163
+ return null
1164
+ }
1165
+ return this.positionalValues[k]
1166
+ }
1167
+
1168
+ /** Every value from argument `a` on: all a variadic one took, or the one value of any other. */
1169
+ positionals(a: i32): string[] {
1170
+ const h = this.cli.checkArgument("positionals", a)
1171
+ const out: string[] = []
1172
+ if (this.cli.argOwner[h] !== this.command) {
1173
+ return out
1174
+ }
1175
+ const k: i32 = this.cli.argIndex[h]
1176
+ const end: i32 = this.cli.argVariadic[h] ? toI32(this.positionalValues.length) : k + 1
1177
+ for (let i: i32 = k; i < end && i >= 0 && i < toI32(this.positionalValues.length); i++) {
1178
+ out.push(this.positionalValues[i])
1179
+ }
1180
+ return out
1181
+ }
1182
+
1183
+ /** The tokens after `--`, in order. */
1184
+ rest(): string[] {
1185
+ return this.restValues
1186
+ }
1187
+
1188
+ /** The help of the command the arguments reached, painted with `style`. */
1189
+ help(style: Style): string {
1190
+ return this.cli.helpText(this.command, style)
1191
+ }
1192
+ }
1193
+
1194
+ /** The pointer under an error line, naming `command` (`<path> --help`, painted or not). */
1195
+ const usageHint = (command: string): string => `Run '${command}' for usage.`
1196
+
1197
+ /**
1198
+ * Acts on `r` for a program: help (painted with `outStyle`) or the version on
1199
+ * standard output and exit 0; an error line and the hint on standard error
1200
+ * and exit 2, the hint's command painted with `errStyle`, the error never;
1201
+ * answers 0 for `PARSE_OK`, which the program goes on to run.
1202
+ */
1203
+ export const finish = (r: ParseResult, outStyle: Style, errStyle: Style): i32 => {
1204
+ if (r.kind === PARSE_HELP) {
1205
+ console.log(r.help(outStyle))
1206
+ process.exit(0)
1207
+ } else if (r.kind === PARSE_VERSION) {
1208
+ console.log(r.message)
1209
+ process.exit(0)
1210
+ } else if (r.kind === PARSE_ERROR) {
1211
+ console.error(r.message)
1212
+ console.error(usageHint(errStyle.flag(`${r.errorPath} --help`)))
1213
+ process.exit(2)
1214
+ }
1215
+ return 0
1216
+ }