@c9up/ream 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/dist/ConfigLoader.d.ts +46 -5
- package/dist/ConfigLoader.d.ts.map +1 -1
- package/dist/ConfigLoader.js +60 -9
- package/dist/ConfigLoader.js.map +1 -1
- package/dist/HttpKernel.d.ts.map +1 -1
- package/dist/HttpKernel.js +46 -6
- package/dist/HttpKernel.js.map +1 -1
- package/dist/Ignitor.d.ts +115 -7
- package/dist/Ignitor.d.ts.map +1 -1
- package/dist/Ignitor.js +254 -40
- package/dist/Ignitor.js.map +1 -1
- package/dist/console/BaseCommand.d.ts +217 -0
- package/dist/console/BaseCommand.d.ts.map +1 -0
- package/dist/console/BaseCommand.js +340 -0
- package/dist/console/BaseCommand.js.map +1 -0
- package/dist/console/Console.d.ts +86 -0
- package/dist/console/Console.d.ts.map +1 -0
- package/dist/console/Console.js +134 -0
- package/dist/console/Console.js.map +1 -0
- package/dist/console/ExceptionHandler.d.ts +28 -0
- package/dist/console/ExceptionHandler.d.ts.map +1 -0
- package/dist/console/ExceptionHandler.js +76 -0
- package/dist/console/ExceptionHandler.js.map +1 -0
- package/dist/console/HelpCommand.d.ts +16 -0
- package/dist/console/HelpCommand.d.ts.map +1 -0
- package/dist/console/HelpCommand.js +40 -0
- package/dist/console/HelpCommand.js.map +1 -0
- package/dist/console/IndexGenerator.d.ts +24 -0
- package/dist/console/IndexGenerator.d.ts.map +1 -0
- package/dist/console/IndexGenerator.js +65 -0
- package/dist/console/IndexGenerator.js.map +1 -0
- package/dist/console/Kernel.d.ts +298 -0
- package/dist/console/Kernel.d.ts.map +1 -0
- package/dist/console/Kernel.js +1120 -0
- package/dist/console/Kernel.js.map +1 -0
- package/dist/console/ListCommand.d.ts +18 -0
- package/dist/console/ListCommand.d.ts.map +1 -0
- package/dist/console/ListCommand.js +60 -0
- package/dist/console/ListCommand.js.map +1 -0
- package/dist/console/assertions.d.ts +20 -0
- package/dist/console/assertions.d.ts.map +1 -0
- package/dist/console/assertions.js +84 -0
- package/dist/console/assertions.js.map +1 -0
- package/dist/console/cliui.d.ts +248 -0
- package/dist/console/cliui.d.ts.map +1 -0
- package/dist/console/cliui.js +587 -0
- package/dist/console/cliui.js.map +1 -0
- package/dist/console/decorators.d.ts +90 -0
- package/dist/console/decorators.d.ts.map +1 -0
- package/dist/console/decorators.js +142 -0
- package/dist/console/decorators.js.map +1 -0
- package/dist/console/index.d.ts +32 -0
- package/dist/console/index.d.ts.map +1 -0
- package/dist/console/index.js +24 -0
- package/dist/console/index.js.map +1 -0
- package/dist/console/loaders.d.ts +52 -0
- package/dist/console/loaders.d.ts.map +1 -0
- package/dist/console/loaders.js +131 -0
- package/dist/console/loaders.js.map +1 -0
- package/dist/console/parser.d.ts +86 -0
- package/dist/console/parser.d.ts.map +1 -0
- package/dist/console/parser.js +440 -0
- package/dist/console/parser.js.map +1 -0
- package/dist/console/prompts.d.ts +124 -0
- package/dist/console/prompts.d.ts.map +1 -0
- package/dist/console/prompts.js +356 -0
- package/dist/console/prompts.js.map +1 -0
- package/dist/console/tracing.d.ts +17 -0
- package/dist/console/tracing.d.ts.map +1 -0
- package/dist/console/tracing.js +11 -0
- package/dist/console/tracing.js.map +1 -0
- package/dist/console/types.d.ts +255 -0
- package/dist/console/types.d.ts.map +1 -0
- package/dist/console/types.js +25 -0
- package/dist/console/types.js.map +1 -0
- package/dist/console/ui.d.ts +20 -0
- package/dist/console/ui.d.ts.map +1 -0
- package/dist/console/ui.js +31 -0
- package/dist/console/ui.js.map +1 -0
- package/dist/console/utils.d.ts +27 -0
- package/dist/console/utils.d.ts.map +1 -0
- package/dist/console/utils.js +56 -0
- package/dist/console/utils.js.map +1 -0
- package/dist/container/Container.d.ts +23 -3
- package/dist/container/Container.d.ts.map +1 -1
- package/dist/container/Container.js +177 -70
- package/dist/container/Container.js.map +1 -1
- package/dist/container/types.d.ts +14 -2
- package/dist/container/types.d.ts.map +1 -1
- package/dist/decorators/Service.d.ts +7 -0
- package/dist/decorators/Service.d.ts.map +1 -1
- package/dist/decorators/Service.js +23 -3
- package/dist/decorators/Service.js.map +1 -1
- package/dist/env/Env.d.ts +29 -4
- package/dist/env/Env.d.ts.map +1 -1
- package/dist/env/Env.js +27 -4
- package/dist/env/Env.js.map +1 -1
- package/dist/env/Secret.d.ts +11 -0
- package/dist/env/Secret.d.ts.map +1 -0
- package/dist/env/Secret.js +36 -0
- package/dist/env/Secret.js.map +1 -0
- package/dist/env/interpolate.d.ts +26 -0
- package/dist/env/interpolate.d.ts.map +1 -0
- package/dist/env/interpolate.js +48 -0
- package/dist/env/interpolate.js.map +1 -0
- package/dist/env/loadEnvFiles.d.ts.map +1 -1
- package/dist/env/loadEnvFiles.js +14 -4
- package/dist/env/loadEnvFiles.js.map +1 -1
- package/dist/env/schema.d.ts +23 -6
- package/dist/env/schema.d.ts.map +1 -1
- package/dist/env/schema.js +67 -33
- package/dist/env/schema.js.map +1 -1
- package/dist/errors/ReamError.d.ts +25 -0
- package/dist/errors/ReamError.d.ts.map +1 -1
- package/dist/errors/ReamError.js +5 -0
- package/dist/errors/ReamError.js.map +1 -1
- package/dist/errors/prettyPrintError.d.ts +9 -0
- package/dist/errors/prettyPrintError.d.ts.map +1 -0
- package/dist/errors/prettyPrintError.js +22 -0
- package/dist/errors/prettyPrintError.js.map +1 -0
- package/dist/events/Emitter.d.ts +1 -1
- package/dist/events/Emitter.d.ts.map +1 -1
- package/dist/events/Emitter.js +1 -1
- package/dist/events/Emitter.js.map +1 -1
- package/dist/events/EventsProvider.d.ts +2 -2
- package/dist/events/EventsProvider.d.ts.map +1 -1
- package/dist/events/EventsProvider.js +3 -3
- package/dist/events/EventsProvider.js.map +1 -1
- package/dist/graphql/GraphQLEngine.d.ts +1 -1
- package/dist/graphql/GraphQLEngine.d.ts.map +1 -1
- package/dist/graphql/GraphQLEngine.js +2 -2
- package/dist/graphql/GraphQLEngine.js.map +1 -1
- package/dist/graphql/GraphQLProvider.d.ts.map +1 -1
- package/dist/graphql/GraphQLProvider.js +7 -3
- package/dist/graphql/GraphQLProvider.js.map +1 -1
- package/dist/http/Exception.d.ts +63 -1
- package/dist/http/Exception.d.ts.map +1 -1
- package/dist/http/Exception.js +185 -48
- package/dist/http/Exception.js.map +1 -1
- package/dist/http/HttpContext.d.ts +53 -7
- package/dist/http/HttpContext.d.ts.map +1 -1
- package/dist/http/HttpContext.js +56 -15
- package/dist/http/HttpContext.js.map +1 -1
- package/dist/http/RedirectBuilder.d.ts.map +1 -1
- package/dist/http/RedirectBuilder.js +4 -1
- package/dist/http/RedirectBuilder.js.map +1 -1
- package/dist/http/Request.d.ts +141 -10
- package/dist/http/Request.d.ts.map +1 -1
- package/dist/http/Request.js +244 -34
- package/dist/http/Request.js.map +1 -1
- package/dist/http/Response.d.ts +176 -9
- package/dist/http/Response.d.ts.map +1 -1
- package/dist/http/Response.js +404 -16
- package/dist/http/Response.js.map +1 -1
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -5
- package/dist/index.js.map +1 -1
- package/dist/middleware/Pipeline.d.ts +29 -8
- package/dist/middleware/Pipeline.d.ts.map +1 -1
- package/dist/middleware/Pipeline.js +40 -2
- package/dist/middleware/Pipeline.js.map +1 -1
- package/dist/openapi/OpenApiProvider.js +4 -4
- package/dist/openapi/OpenApiProvider.js.map +1 -1
- package/dist/router/Router.d.ts +223 -31
- package/dist/router/Router.d.ts.map +1 -1
- package/dist/router/Router.js +520 -102
- package/dist/router/Router.js.map +1 -1
- package/dist/rpc/RpcProvider.d.ts +1 -0
- package/dist/rpc/RpcProvider.d.ts.map +1 -1
- package/dist/rpc/RpcProvider.js +7 -3
- package/dist/rpc/RpcProvider.js.map +1 -1
- package/dist/rpc/RpcRouter.d.ts +2 -2
- package/dist/rpc/RpcRouter.d.ts.map +1 -1
- package/dist/rpc/RpcRouter.js +17 -5
- package/dist/rpc/RpcRouter.js.map +1 -1
- package/dist/scheduler/ScheduleProvider.d.ts.map +1 -1
- package/dist/scheduler/ScheduleProvider.js +7 -15
- package/dist/scheduler/ScheduleProvider.js.map +1 -1
- package/dist/server/Server.d.ts +17 -4
- package/dist/server/Server.d.ts.map +1 -1
- package/dist/server/Server.js +41 -7
- package/dist/server/Server.js.map +1 -1
- package/dist/services/app.d.ts +1 -1
- package/dist/services/app.js +1 -1
- package/dist/services/console.d.ts +21 -0
- package/dist/services/console.d.ts.map +1 -0
- package/dist/services/console.js +35 -0
- package/dist/services/console.js.map +1 -0
- package/dist/session/SessionMiddleware.js +3 -3
- package/dist/session/SessionMiddleware.js.map +1 -1
- package/dist/storage/StaticProvider.js +1 -1
- package/dist/storage/StaticProvider.js.map +1 -1
- package/dist/testing/ApiResponse.d.ts +218 -0
- package/dist/testing/ApiResponse.d.ts.map +1 -0
- package/dist/testing/ApiResponse.js +576 -0
- package/dist/testing/ApiResponse.js.map +1 -0
- package/dist/testing/RequestBuilder.d.ts +155 -35
- package/dist/testing/RequestBuilder.d.ts.map +1 -1
- package/dist/testing/RequestBuilder.js +455 -109
- package/dist/testing/RequestBuilder.js.map +1 -1
- package/dist/testing/TestClient.d.ts +53 -37
- package/dist/testing/TestClient.d.ts.map +1 -1
- package/dist/testing/TestClient.js +84 -52
- package/dist/testing/TestClient.js.map +1 -1
- package/dist/utils/Macroable.d.ts +30 -0
- package/dist/utils/Macroable.d.ts.map +1 -0
- package/dist/utils/Macroable.js +43 -0
- package/dist/utils/Macroable.js.map +1 -0
- package/dist/utils/inflect.d.ts +17 -0
- package/dist/utils/inflect.d.ts.map +1 -0
- package/dist/utils/inflect.js +32 -0
- package/dist/utils/inflect.js.map +1 -0
- package/dist/utils/objectPath.d.ts +57 -0
- package/dist/utils/objectPath.d.ts.map +1 -0
- package/dist/utils/objectPath.js +141 -0
- package/dist/utils/objectPath.js.map +1 -0
- package/events.darwin-arm64.node +0 -0
- package/events.darwin-x64.node +0 -0
- package/events.linux-arm64-gnu.node +0 -0
- package/events.linux-x64-gnu.node +0 -0
- package/events.win32-x64-msvc.node +0 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +20 -2
- package/scheduler.darwin-arm64.node +0 -0
- package/scheduler.darwin-x64.node +0 -0
- package/scheduler.linux-arm64-gnu.node +0 -0
- package/scheduler.linux-x64-gnu.node +0 -0
- package/scheduler.win32-x64-msvc.node +0 -0
- package/src/ConfigLoader.ts +72 -9
- package/src/HttpKernel.ts +53 -15
- package/src/Ignitor.ts +364 -45
- package/src/console/BaseCommand.ts +467 -0
- package/src/console/Console.ts +174 -0
- package/src/console/ExceptionHandler.ts +91 -0
- package/src/console/HelpCommand.ts +37 -0
- package/src/console/IndexGenerator.ts +79 -0
- package/src/console/Kernel.ts +1382 -0
- package/src/console/ListCommand.ts +55 -0
- package/src/console/assertions.ts +114 -0
- package/src/console/cliui.ts +756 -0
- package/src/console/decorators.ts +199 -0
- package/src/console/index.ts +67 -0
- package/src/console/loaders.ts +145 -0
- package/src/console/parser.ts +598 -0
- package/src/console/prompts.ts +548 -0
- package/src/console/tracing.ts +22 -0
- package/src/console/types.ts +280 -0
- package/src/console/ui.ts +37 -0
- package/src/console/utils.ts +69 -0
- package/src/container/Container.ts +198 -72
- package/src/container/types.ts +15 -2
- package/src/decorators/Service.ts +28 -3
- package/src/env/Env.ts +49 -5
- package/src/env/Secret.ts +41 -0
- package/src/env/interpolate.ts +54 -0
- package/src/env/loadEnvFiles.ts +12 -4
- package/src/env/schema.ts +109 -35
- package/src/errors/ReamError.ts +31 -0
- package/src/errors/prettyPrintError.ts +20 -0
- package/src/events/Emitter.ts +2 -2
- package/src/events/EventsProvider.ts +5 -5
- package/src/graphql/GraphQLEngine.ts +4 -4
- package/src/graphql/GraphQLProvider.ts +7 -3
- package/src/http/Exception.ts +214 -37
- package/src/http/HttpContext.ts +87 -18
- package/src/http/RedirectBuilder.ts +4 -1
- package/src/http/Request.ts +264 -34
- package/src/http/Response.ts +478 -27
- package/src/index.ts +41 -6
- package/src/middleware/Pipeline.ts +77 -6
- package/src/openapi/OpenApiProvider.ts +4 -4
- package/src/router/Router.ts +631 -113
- package/src/rpc/RpcProvider.ts +6 -2
- package/src/rpc/RpcRouter.ts +22 -7
- package/src/scheduler/ScheduleProvider.ts +7 -15
- package/src/server/Server.ts +49 -10
- package/src/services/app.ts +1 -1
- package/src/services/console.ts +42 -0
- package/src/session/SessionMiddleware.ts +4 -4
- package/src/storage/StaticProvider.ts +1 -1
- package/src/testing/ApiResponse.ts +691 -0
- package/src/testing/RequestBuilder.ts +577 -137
- package/src/testing/TestClient.ts +110 -77
- package/src/utils/Macroable.ts +54 -0
- package/src/utils/inflect.ts +29 -0
- package/src/utils/objectPath.ts +142 -0
- package/dist/console/CommandRunner.d.ts +0 -18
- package/dist/console/CommandRunner.d.ts.map +0 -1
- package/dist/console/CommandRunner.js +0 -65
- package/dist/console/CommandRunner.js.map +0 -1
- package/dist/decorators/Lazy.d.ts +0 -19
- package/dist/decorators/Lazy.d.ts.map +0 -1
- package/dist/decorators/Lazy.js +0 -53
- package/dist/decorators/Lazy.js.map +0 -1
- package/src/console/CommandRunner.ts +0 -82
- package/src/decorators/Lazy.ts +0 -57
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* argv parser for console commands.
|
|
3
|
+
*
|
|
4
|
+
* Metadata-driven, unlike the naive splitter it replaces: knowing a flag's
|
|
5
|
+
* declared type is what makes `--email hugo@example.com` work. Without it the
|
|
6
|
+
* parser cannot tell a value-taking flag from a boolean one, so it has to treat
|
|
7
|
+
* every `--flag` as `true` and let the value fall through to the positionals —
|
|
8
|
+
* which is exactly how the previous implementation silently dropped arguments.
|
|
9
|
+
*
|
|
10
|
+
* Supported forms (Console parity):
|
|
11
|
+
* --flag=value --flag value --flag (boolean) --no-flag (boolean false)
|
|
12
|
+
* -f value -f -abc (grouped boolean aliases)
|
|
13
|
+
* -- everything after it is positional, never a flag
|
|
14
|
+
* repeated flags accumulate for `@flags.array()`
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { ReamError } from '../errors/ReamError.js'
|
|
18
|
+
import type { ArgumentMetaData, FlagMetaData, ParsedInput } from './types.js'
|
|
19
|
+
|
|
20
|
+
export interface ParseOptions {
|
|
21
|
+
args?: readonly ArgumentMetaData[]
|
|
22
|
+
flags?: readonly FlagMetaData[]
|
|
23
|
+
allowUnknownFlags?: boolean
|
|
24
|
+
/** Command name, used to make error messages point at the right command. */
|
|
25
|
+
commandName?: string
|
|
26
|
+
/**
|
|
27
|
+
* Check the required inputs while parsing. `false` parses leniently and
|
|
28
|
+
* leaves that to {@link validateParsed}.
|
|
29
|
+
*
|
|
30
|
+
* The kernel parses leniently on purpose: Console validates AFTER the global-flag
|
|
31
|
+
* listeners have run, which is what lets `--help` work on a command whose
|
|
32
|
+
* flags are required.
|
|
33
|
+
*/
|
|
34
|
+
validate?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A flag token resolved against the declared metadata. */
|
|
38
|
+
interface RawFlag {
|
|
39
|
+
meta: FlagMetaData
|
|
40
|
+
/** `undefined` means "no inline value" — a boolean, or a value still to come. */
|
|
41
|
+
value?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The parser as an object (Console `Parser`).
|
|
46
|
+
*
|
|
47
|
+
* Same work as {@link parseArgv}, held in a shape that can be passed around and
|
|
48
|
+
* reused — which is how Console's kernel and its ported code hold it.
|
|
49
|
+
*/
|
|
50
|
+
export class Parser {
|
|
51
|
+
readonly #options: ParseOptions
|
|
52
|
+
|
|
53
|
+
constructor(options: ParseOptions = {}) {
|
|
54
|
+
this.#options = options
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
parse(argv: readonly string[] | string): ParsedInput {
|
|
58
|
+
return parseArgv(
|
|
59
|
+
typeof argv === 'string' ? argv.split(' ').filter(Boolean) : argv,
|
|
60
|
+
this.#options,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function parseArgv(argv: readonly string[], options: ParseOptions = {}): ParsedInput {
|
|
66
|
+
const declaredFlags = options.flags ?? []
|
|
67
|
+
const declaredArgs = options.args ?? []
|
|
68
|
+
const where = options.commandName ? ` for "${options.commandName}"` : ''
|
|
69
|
+
|
|
70
|
+
const byName = new Map<string, FlagMetaData>()
|
|
71
|
+
const byAlias = new Map<string, FlagMetaData>()
|
|
72
|
+
for (const flag of declaredFlags) {
|
|
73
|
+
byName.set(flag.flagName, flag)
|
|
74
|
+
for (const alias of flag.alias) byAlias.set(alias, flag)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const positionals: string[] = []
|
|
78
|
+
/** Collected per property so repeated flags can accumulate into arrays. */
|
|
79
|
+
const collected = new Map<string, Array<string | boolean>>()
|
|
80
|
+
const unknown: Record<string, string | boolean> = {}
|
|
81
|
+
|
|
82
|
+
const push = (meta: FlagMetaData, value: string | boolean): void => {
|
|
83
|
+
const existing = collected.get(meta.propertyName)
|
|
84
|
+
if (existing) existing.push(value)
|
|
85
|
+
else collected.set(meta.propertyName, [value])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Record the flag as seen without attaching a value to it. */
|
|
89
|
+
const mention = (meta: FlagMetaData): void => {
|
|
90
|
+
if (!collected.has(meta.propertyName)) collected.set(meta.propertyName, [])
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let sawTerminator = false
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < argv.length; i++) {
|
|
96
|
+
const token = argv[i] as string
|
|
97
|
+
|
|
98
|
+
if (sawTerminator) {
|
|
99
|
+
positionals.push(token)
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if (token === '--') {
|
|
103
|
+
sawTerminator = true
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// --long, --long=value, --no-long
|
|
108
|
+
if (token.startsWith('--')) {
|
|
109
|
+
const body = token.slice(2)
|
|
110
|
+
const eq = body.indexOf('=')
|
|
111
|
+
const name = eq === -1 ? body : body.slice(0, eq)
|
|
112
|
+
const inline = eq === -1 ? undefined : body.slice(eq + 1)
|
|
113
|
+
|
|
114
|
+
// `--no-x` negates a declared boolean. Checked before the plain lookup so
|
|
115
|
+
// a genuine flag literally named `no-cache` still wins.
|
|
116
|
+
const negated =
|
|
117
|
+
!byName.has(name) && name.startsWith('no-') ? byName.get(name.slice(3)) : undefined
|
|
118
|
+
if (negated && negated.type === 'boolean') {
|
|
119
|
+
if (inline !== undefined) {
|
|
120
|
+
throw invalidNegation(token, where)
|
|
121
|
+
}
|
|
122
|
+
push(negated, false)
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const meta = byName.get(name)
|
|
127
|
+
if (!meta) {
|
|
128
|
+
if (!options.allowUnknownFlags) throw unknownFlag(`--${name}`, where, declaredFlags)
|
|
129
|
+
// An undeclared flag has no type to go on, so `--foo bar` is read the
|
|
130
|
+
// way a shell parser does: the next token is its value unless it looks
|
|
131
|
+
// like another flag. Treating it as a bare boolean would drop `bar`
|
|
132
|
+
// entirely — a proxy command would silently lose its arguments.
|
|
133
|
+
if (inline !== undefined) {
|
|
134
|
+
unknown[name] = inline
|
|
135
|
+
} else {
|
|
136
|
+
const next = argv[i + 1]
|
|
137
|
+
if (next !== undefined && (!next.startsWith('-') || next === '-')) {
|
|
138
|
+
unknown[name] = next
|
|
139
|
+
i++
|
|
140
|
+
} else {
|
|
141
|
+
unknown[name] = true
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
i = consume({ meta, value: inline }, argv, i, { push, mention }, where)
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// -a, -a value, -abc (grouped booleans)
|
|
151
|
+
if (token.startsWith('-') && token.length > 1) {
|
|
152
|
+
const body = token.slice(1)
|
|
153
|
+
const eq = body.indexOf('=')
|
|
154
|
+
const chars = eq === -1 ? body : body.slice(0, eq)
|
|
155
|
+
const inline = eq === -1 ? undefined : body.slice(eq + 1)
|
|
156
|
+
|
|
157
|
+
if (chars.length === 1) {
|
|
158
|
+
const meta = byAlias.get(chars)
|
|
159
|
+
if (!meta) {
|
|
160
|
+
if (!options.allowUnknownFlags) throw unknownFlag(`-${chars}`, where, declaredFlags)
|
|
161
|
+
if (inline !== undefined) {
|
|
162
|
+
unknown[chars] = inline
|
|
163
|
+
} else {
|
|
164
|
+
const next = argv[i + 1]
|
|
165
|
+
if (next !== undefined && (!next.startsWith('-') || next === '-')) {
|
|
166
|
+
unknown[chars] = next
|
|
167
|
+
i++
|
|
168
|
+
} else {
|
|
169
|
+
unknown[chars] = true
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
i = consume({ meta, value: inline }, argv, i, { push, mention }, where)
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// A group like `-rs` can only be booleans: there is no way to attach a
|
|
179
|
+
// value to more than one flag at a time.
|
|
180
|
+
for (const char of chars) {
|
|
181
|
+
const meta = byAlias.get(char)
|
|
182
|
+
if (!meta) {
|
|
183
|
+
if (!options.allowUnknownFlags) throw unknownFlag(`-${char}`, where, declaredFlags)
|
|
184
|
+
unknown[char] = true
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
if (meta.type !== 'boolean') {
|
|
188
|
+
throw new ReamError(
|
|
189
|
+
'E_CONSOLE_INVALID_FLAG_GROUP',
|
|
190
|
+
`Cannot group "-${char}" with other flags${where} — "--${meta.flagName}" expects a value.`,
|
|
191
|
+
{ hint: `Pass it on its own: -${char} <value>` },
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
push(meta, true)
|
|
195
|
+
}
|
|
196
|
+
continue
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
positionals.push(token)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const extraArgs: string[] = []
|
|
203
|
+
const strict = options.validate !== false
|
|
204
|
+
const flags = applyParse(
|
|
205
|
+
declaredFlags,
|
|
206
|
+
materialiseFlags(declaredFlags, collected, unknown, where, strict),
|
|
207
|
+
)
|
|
208
|
+
const args = applyParse(
|
|
209
|
+
declaredArgs,
|
|
210
|
+
materialiseArgs(
|
|
211
|
+
declaredArgs,
|
|
212
|
+
positionals,
|
|
213
|
+
where,
|
|
214
|
+
options.allowUnknownFlags === true,
|
|
215
|
+
extraArgs,
|
|
216
|
+
strict,
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
// Rekeyed into Console's shape only here: defaults and `parse` are applied per
|
|
221
|
+
// DECLARATION, which needs the property name, while what comes out describes
|
|
222
|
+
// what was typed — positionals as a list, flags under their CLI name.
|
|
223
|
+
const byFlagName: Record<string, unknown> = { ...unknown }
|
|
224
|
+
for (const meta of declaredFlags) {
|
|
225
|
+
if (meta.propertyName in flags) byFlagName[meta.flagName] = flags[meta.propertyName]
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
unknownFlags: Object.keys(unknown),
|
|
230
|
+
flags: byFlagName,
|
|
231
|
+
args: declaredArgs.map((meta) => args[meta.propertyName]),
|
|
232
|
+
extraArgs,
|
|
233
|
+
_: extraArgs,
|
|
234
|
+
nodeArgs: [],
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Run each declared input's `parse` callback over the materialised value.
|
|
240
|
+
*
|
|
241
|
+
* Applied last, on the already-typed value, so `parse` receives a number for a
|
|
242
|
+
* number flag rather than the raw token — and only for inputs that were
|
|
243
|
+
* actually provided or defaulted, never for an absent optional one.
|
|
244
|
+
*/
|
|
245
|
+
function applyParse<
|
|
246
|
+
M extends { propertyName: string; parse?: (value: never) => unknown },
|
|
247
|
+
V extends Record<string, unknown>,
|
|
248
|
+
>(declared: readonly M[], values: V): V {
|
|
249
|
+
for (const meta of declared) {
|
|
250
|
+
if (meta.parse === undefined) continue
|
|
251
|
+
const current = values[meta.propertyName]
|
|
252
|
+
if (current === undefined) continue
|
|
253
|
+
Reflect.set(values, meta.propertyName, Reflect.apply(meta.parse, undefined, [current]))
|
|
254
|
+
}
|
|
255
|
+
return values
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Attach a value to a flag, pulling the next argv token when the value was not
|
|
260
|
+
* written inline. Returns the new cursor position.
|
|
261
|
+
*/
|
|
262
|
+
function consume(
|
|
263
|
+
raw: RawFlag,
|
|
264
|
+
argv: readonly string[],
|
|
265
|
+
index: number,
|
|
266
|
+
collect: {
|
|
267
|
+
push: (meta: FlagMetaData, value: string | boolean) => void
|
|
268
|
+
mention: (meta: FlagMetaData) => void
|
|
269
|
+
},
|
|
270
|
+
where: string,
|
|
271
|
+
): number {
|
|
272
|
+
const { push, mention } = collect
|
|
273
|
+
const { meta, value } = raw
|
|
274
|
+
|
|
275
|
+
if (meta.type === 'boolean') {
|
|
276
|
+
if (value === undefined) {
|
|
277
|
+
push(meta, true)
|
|
278
|
+
return index
|
|
279
|
+
}
|
|
280
|
+
// `--force=false` is explicit enough to honour; anything else is a typo.
|
|
281
|
+
if (value === 'true' || value === 'false') {
|
|
282
|
+
push(meta, value === 'true')
|
|
283
|
+
return index
|
|
284
|
+
}
|
|
285
|
+
throw new ReamError(
|
|
286
|
+
'E_CONSOLE_INVALID_FLAG_VALUE',
|
|
287
|
+
`Flag "--${meta.flagName}"${where} is a boolean and cannot take the value "${value}".`,
|
|
288
|
+
{ hint: `Use --${meta.flagName} or --no-${meta.flagName}` },
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (value !== undefined) {
|
|
293
|
+
push(meta, value)
|
|
294
|
+
return index
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Value-taking flag with no inline value: the next token is it. A token that
|
|
298
|
+
// itself looks like a flag is NOT consumed — `--email --name x` is a mistake
|
|
299
|
+
// worth reporting, not an email of "--name".
|
|
300
|
+
const next = argv[index + 1]
|
|
301
|
+
if (next === undefined || (next.startsWith('-') && next !== '-')) {
|
|
302
|
+
// Unless the flag says an empty value is meaningful (Console
|
|
303
|
+
// `allowEmptyValue`), in which case the mention itself is the value.
|
|
304
|
+
if (meta.allowEmptyValue === true) {
|
|
305
|
+
// An array flag with nothing behind it is an EMPTY list, not a list
|
|
306
|
+
// holding one empty string — `mention` records it without a value.
|
|
307
|
+
if (meta.type === 'array') mention(meta)
|
|
308
|
+
else push(meta, '')
|
|
309
|
+
return index
|
|
310
|
+
}
|
|
311
|
+
throw new ReamError(
|
|
312
|
+
'E_CONSOLE_MISSING_FLAG_VALUE',
|
|
313
|
+
`Flag "--${meta.flagName}"${where} expects a value.`,
|
|
314
|
+
{ hint: `Example: --${meta.flagName}=<value>` },
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
push(meta, next)
|
|
318
|
+
return index + 1
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Apply types, defaults and required checks to the collected flag values. */
|
|
322
|
+
function materialiseFlags(
|
|
323
|
+
declared: readonly FlagMetaData[],
|
|
324
|
+
collected: Map<string, Array<string | boolean>>,
|
|
325
|
+
unknown: Record<string, string | boolean>,
|
|
326
|
+
where: string,
|
|
327
|
+
strict: boolean,
|
|
328
|
+
): Record<string, string | string[] | number | boolean> {
|
|
329
|
+
const out: Record<string, string | string[] | number | boolean> = { ...unknown }
|
|
330
|
+
|
|
331
|
+
for (const meta of declared) {
|
|
332
|
+
const values = collected.get(meta.propertyName)
|
|
333
|
+
|
|
334
|
+
if (values === undefined) {
|
|
335
|
+
if (meta.default !== undefined) out[meta.propertyName] = meta.default
|
|
336
|
+
else if (meta.required && strict) {
|
|
337
|
+
throw new ReamError(
|
|
338
|
+
'E_CONSOLE_MISSING_FLAG',
|
|
339
|
+
`Missing required flag "--${meta.flagName}"${where}.`,
|
|
340
|
+
)
|
|
341
|
+
}
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
switch (meta.type) {
|
|
346
|
+
case 'array':
|
|
347
|
+
out[meta.propertyName] = values.map(String)
|
|
348
|
+
break
|
|
349
|
+
case 'number': {
|
|
350
|
+
// Last one wins for scalars, matching how shells are normally used.
|
|
351
|
+
const last = String(values[values.length - 1])
|
|
352
|
+
const parsed = Number(last)
|
|
353
|
+
if (last.trim() === '' || Number.isNaN(parsed)) {
|
|
354
|
+
throw new ReamError(
|
|
355
|
+
'E_CONSOLE_INVALID_FLAG_VALUE',
|
|
356
|
+
`Flag "--${meta.flagName}"${where} expects a number, got "${last}".`,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
out[meta.propertyName] = parsed
|
|
360
|
+
break
|
|
361
|
+
}
|
|
362
|
+
case 'boolean':
|
|
363
|
+
out[meta.propertyName] = Boolean(values[values.length - 1])
|
|
364
|
+
break
|
|
365
|
+
default:
|
|
366
|
+
out[meta.propertyName] = String(values[values.length - 1])
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return out
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Map positionals onto declared arguments, honouring `@args.spread()`. */
|
|
374
|
+
function materialiseArgs(
|
|
375
|
+
declared: readonly ArgumentMetaData[],
|
|
376
|
+
positionals: readonly string[],
|
|
377
|
+
where: string,
|
|
378
|
+
allowExtra: boolean,
|
|
379
|
+
extraArgs: string[],
|
|
380
|
+
strict: boolean,
|
|
381
|
+
): Record<string, string | string[]> {
|
|
382
|
+
const out: Record<string, string | string[]> = {}
|
|
383
|
+
let cursor = 0
|
|
384
|
+
|
|
385
|
+
for (const meta of declared) {
|
|
386
|
+
if (meta.type === 'spread') {
|
|
387
|
+
const rest = positionals.slice(cursor)
|
|
388
|
+
cursor = positionals.length
|
|
389
|
+
if (rest.length === 0) {
|
|
390
|
+
// No default and nothing to collect: the argument is UNDEFINED, not an
|
|
391
|
+
// empty list (Console). A command that distinguishes "not given" from
|
|
392
|
+
// "given empty" must be able to tell them apart.
|
|
393
|
+
if (meta.default !== undefined) out[meta.propertyName] = meta.default
|
|
394
|
+
else if (meta.required && strict) throw missingArgument(meta, where)
|
|
395
|
+
continue
|
|
396
|
+
}
|
|
397
|
+
out[meta.propertyName] = rest
|
|
398
|
+
continue
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const value = positionals[cursor]
|
|
402
|
+
cursor++
|
|
403
|
+
if (value === undefined) {
|
|
404
|
+
if (meta.default !== undefined) out[meta.propertyName] = meta.default
|
|
405
|
+
else if (meta.required && strict) throw missingArgument(meta, where)
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
// An empty positional is nearly always a shell variable that did not
|
|
409
|
+
// expand, so it is reported rather than accepted (Console) — unless the
|
|
410
|
+
// argument declares that an empty value means something.
|
|
411
|
+
if (value === '' && meta.allowEmptyValue !== true && strict) {
|
|
412
|
+
throw new ReamError(
|
|
413
|
+
'E_CONSOLE_MISSING_ARGUMENT_VALUE',
|
|
414
|
+
`Missing value for argument "${meta.argumentName}"${where}.`,
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
out[meta.propertyName] = value
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Anything left over was never asked for. Swallowing it hides a typo:
|
|
421
|
+
// `greet john extra` would run as `greet john`, silently dropping the rest.
|
|
422
|
+
// A `spread` argument consumes the tail, so this only fires without one.
|
|
423
|
+
if (cursor < positionals.length) {
|
|
424
|
+
const extra = positionals.slice(cursor)
|
|
425
|
+
// A proxy command (allowUnknownFlags) keeps them — forwarding them is its
|
|
426
|
+
// whole purpose. Anything else reports them, because swallowing a
|
|
427
|
+
// positional hides a typo.
|
|
428
|
+
if (allowExtra) {
|
|
429
|
+
extraArgs.push(...extra)
|
|
430
|
+
return out
|
|
431
|
+
}
|
|
432
|
+
throw new ReamError(
|
|
433
|
+
'E_CONSOLE_UNEXPECTED_ARGUMENT',
|
|
434
|
+
`Unexpected argument${extra.length > 1 ? 's' : ''} ${extra.map((value) => `"${value}"`).join(', ')}${where}.`,
|
|
435
|
+
{
|
|
436
|
+
hint:
|
|
437
|
+
declared.length === 0
|
|
438
|
+
? 'This command takes no positional arguments.'
|
|
439
|
+
: `It takes ${declared.length}: ${declared.map((arg) => arg.argumentName).join(', ')}.`,
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return out
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Check a parsed input against a command's declarations (Console `validate`).
|
|
449
|
+
*
|
|
450
|
+
* Separate from parsing because the ORDER matters: Console runs the global-flag
|
|
451
|
+
* listeners between the two, which is what lets `--help` work on a command
|
|
452
|
+
* whose flags are required. One implementation, called by the kernel and by
|
|
453
|
+
* `BaseCommand.validate()`.
|
|
454
|
+
*
|
|
455
|
+
* Positionals are accepted as a list (Console's shape) or keyed by property name
|
|
456
|
+
* (what a hand-built Ream input holds).
|
|
457
|
+
*/
|
|
458
|
+
export function validateParsed(
|
|
459
|
+
parsed: {
|
|
460
|
+
args: readonly unknown[] | Record<string, unknown>
|
|
461
|
+
flags?: Record<string, unknown>
|
|
462
|
+
unknownFlags?: readonly string[]
|
|
463
|
+
},
|
|
464
|
+
declarations: {
|
|
465
|
+
args?: readonly ArgumentMetaData[]
|
|
466
|
+
flags?: readonly FlagMetaData[]
|
|
467
|
+
allowUnknownFlags?: boolean
|
|
468
|
+
commandName?: string
|
|
469
|
+
},
|
|
470
|
+
): void {
|
|
471
|
+
const where = declarations.commandName ? ` for "${declarations.commandName}"` : ''
|
|
472
|
+
const byPosition = Array.isArray(parsed.args) ? parsed.args : undefined
|
|
473
|
+
const byName: Record<string, unknown> = byPosition === undefined ? { ...parsed.args } : {}
|
|
474
|
+
|
|
475
|
+
;(declarations.args ?? []).forEach((arg, index) => {
|
|
476
|
+
const value = byPosition === undefined ? byName[arg.propertyName] : byPosition[index]
|
|
477
|
+
|
|
478
|
+
if (arg.required && value === undefined) throw missingArgument(arg, where)
|
|
479
|
+
if (value === undefined || arg.allowEmptyValue === true) return
|
|
480
|
+
if (value === '' || (Array.isArray(value) && value.length === 0)) {
|
|
481
|
+
throw new ReamError(
|
|
482
|
+
'E_CONSOLE_MISSING_ARGUMENT_VALUE',
|
|
483
|
+
`Missing value for argument "${arg.argumentName}"${where}.`,
|
|
484
|
+
)
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
const unknown = parsed.unknownFlags ?? []
|
|
489
|
+
if (declarations.allowUnknownFlags !== true && unknown.length > 0) {
|
|
490
|
+
const flag = unknown[0] ?? ''
|
|
491
|
+
throw new ReamError(
|
|
492
|
+
'E_CONSOLE_UNKNOWN_FLAG',
|
|
493
|
+
`Unknown flag "${flag.length === 1 ? `-${flag}` : `--${flag}`}"${where}.`,
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const flags: Record<string, unknown> = parsed.flags ?? {}
|
|
498
|
+
for (const flag of declarations.flags ?? []) {
|
|
499
|
+
// Keyed by flag name (Console, and what the parser returns) or by property
|
|
500
|
+
// name — a hand-built input must validate whichever shape it was written in.
|
|
501
|
+
const key = Object.hasOwn(flags, flag.flagName) ? flag.flagName : flag.propertyName
|
|
502
|
+
const mentioned = Object.hasOwn(flags, key)
|
|
503
|
+
const value = flags[key]
|
|
504
|
+
|
|
505
|
+
if (flag.required && !mentioned) {
|
|
506
|
+
throw new ReamError(
|
|
507
|
+
'E_CONSOLE_MISSING_FLAG',
|
|
508
|
+
`Missing required flag "--${flag.flagName}"${where}.`,
|
|
509
|
+
)
|
|
510
|
+
}
|
|
511
|
+
if (!mentioned || flag.allowEmptyValue === true) continue
|
|
512
|
+
|
|
513
|
+
if (flag.type === 'number') {
|
|
514
|
+
if (value === undefined || Number.isNaN(value)) {
|
|
515
|
+
throw new ReamError(
|
|
516
|
+
'E_CONSOLE_INVALID_FLAG_VALUE',
|
|
517
|
+
`Flag "--${flag.flagName}"${where} expects a number, got "${String(value)}".`,
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
continue
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// A mentioned string/array flag with nothing behind it: `--name` alone
|
|
524
|
+
// yields '' from a parser, and an empty array for a repeatable flag.
|
|
525
|
+
if (flag.type === 'boolean') continue
|
|
526
|
+
if (value === '' || (Array.isArray(value) && value.length === 0)) {
|
|
527
|
+
throw new ReamError(
|
|
528
|
+
'E_CONSOLE_MISSING_FLAG_VALUE',
|
|
529
|
+
`Missing value for flag "--${flag.flagName}"${where}.`,
|
|
530
|
+
)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Assign the parsed values to the properties they were declared on.
|
|
537
|
+
*
|
|
538
|
+
* The single hydration path: `BaseCommand.hydrate()` calls it, and so does the
|
|
539
|
+
* kernel for a command declared structurally, which has no `hydrate()` of its
|
|
540
|
+
* own. Both input shapes are accepted — positionals as a list (what the parser
|
|
541
|
+
* now returns, and Console's shape) or keyed by property name, flags under their
|
|
542
|
+
* flag name or their property name — because a hand-built input is a documented
|
|
543
|
+
* use case.
|
|
544
|
+
*/
|
|
545
|
+
export function assignParsedValues(
|
|
546
|
+
target: object,
|
|
547
|
+
declaredArgs: readonly ArgumentMetaData[],
|
|
548
|
+
declaredFlags: readonly FlagMetaData[],
|
|
549
|
+
parsed: { args: unknown[] | Record<string, unknown>; flags: Record<string, unknown> },
|
|
550
|
+
): void {
|
|
551
|
+
const byPosition = Array.isArray(parsed.args) ? parsed.args : undefined
|
|
552
|
+
const byArgName: Record<string, unknown> = byPosition === undefined ? { ...parsed.args } : {}
|
|
553
|
+
|
|
554
|
+
declaredArgs.forEach((meta, index) => {
|
|
555
|
+
define(target, meta.propertyName, byPosition?.[index] ?? byArgName[meta.propertyName])
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
for (const meta of declaredFlags) {
|
|
559
|
+
const key = Object.hasOwn(parsed.flags, meta.flagName) ? meta.flagName : meta.propertyName
|
|
560
|
+
define(target, meta.propertyName, parsed.flags[key])
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Writable and enumerable, as Console's `hydrate` defines them. */
|
|
565
|
+
function define(target: object, property: string, value: unknown): void {
|
|
566
|
+
Object.defineProperty(target, property, {
|
|
567
|
+
value,
|
|
568
|
+
enumerable: true,
|
|
569
|
+
writable: true,
|
|
570
|
+
configurable: true,
|
|
571
|
+
})
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Exported so `BaseCommand.validate()` reports a missing argument with the very
|
|
576
|
+
* message the parser uses — two wordings for one rule is how they drift apart.
|
|
577
|
+
*/
|
|
578
|
+
export function missingArgument(meta: ArgumentMetaData, where = ''): ReamError {
|
|
579
|
+
return new ReamError(
|
|
580
|
+
'E_CONSOLE_MISSING_ARGUMENT',
|
|
581
|
+
`Missing required argument "${meta.argumentName}"${where}.`,
|
|
582
|
+
meta.description ? { hint: meta.description } : undefined,
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function unknownFlag(token: string, where: string, declared: readonly FlagMetaData[]): ReamError {
|
|
587
|
+
const known = declared.map((f) => `--${f.flagName}`).join(', ')
|
|
588
|
+
return new ReamError('E_CONSOLE_UNKNOWN_FLAG', `Unknown flag "${token}"${where}.`, {
|
|
589
|
+
hint: known.length > 0 ? `Known flags: ${known}` : 'This command declares no flags.',
|
|
590
|
+
})
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function invalidNegation(token: string, where: string): ReamError {
|
|
594
|
+
return new ReamError(
|
|
595
|
+
'E_CONSOLE_INVALID_FLAG_VALUE',
|
|
596
|
+
`"${token}"${where} negates a boolean flag and cannot carry a value.`,
|
|
597
|
+
)
|
|
598
|
+
}
|