@amritk/nish-x86_64-linux 0.23.1 → 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.
- package/bin/nish +0 -0
- package/docs/AI.md +50 -23
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/arrays.d.ts +0 -2
- package/runtime/core/arrays.mjs +0 -2
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/core/numbers.mjs +2 -3
- package/runtime/nish.d.ts +100 -4
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +23 -5
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +45 -4
- package/runtime/runtime-os.c +115 -12
- package/runtime/runtime-term.c +2 -2
- package/runtime/runtime.c +1 -1
- package/scripts/build.sh +13 -3
- package/std/README.md +44 -7
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +290 -0
- package/std/compress/deflate.ts +2790 -0
- package/std/compress/gzip.ts +761 -0
- package/std/compress/zlib.ts +168 -0
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/sha256.ts +2 -3
- package/std/crypto/x25519.ts +3 -4
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http-compress.ts +393 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +101 -1
- package/std/net/http1.ts +9 -9
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
package/std/cli-args.ts
ADDED
|
@@ -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
|
+
}
|