@bcts/dcbor-cli 1.0.0-alpha.13
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/LICENSE +48 -0
- package/README.md +105 -0
- package/dist/cli.cjs +132 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +133 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +16 -0
- package/dist/index.d.cts +202 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +202 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/src-Ce085uR8.mjs +407 -0
- package/dist/src-Ce085uR8.mjs.map +1 -0
- package/dist/src-M5HM-SCU.cjs +490 -0
- package/dist/src-M5HM-SCU.cjs.map +1 -0
- package/package.json +87 -0
- package/src/cli.ts +239 -0
- package/src/cmd/array.ts +41 -0
- package/src/cmd/default.ts +108 -0
- package/src/cmd/index.ts +19 -0
- package/src/cmd/map.ts +41 -0
- package/src/cmd/match.ts +196 -0
- package/src/format.ts +82 -0
- package/src/index.ts +41 -0
- package/src/run.ts +134 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console, no-undef, no-restricted-globals, @typescript-eslint/no-unsafe-argument */
|
|
3
|
+
/**
|
|
4
|
+
* dcbor CLI - Command line parser/validator for deterministic CBOR (dCBOR)
|
|
5
|
+
*
|
|
6
|
+
* A command line tool for composing, parsing and validating Gordian dCBOR.
|
|
7
|
+
* See the main repo README: https://github.com/leonardocustodio/bcts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command, Option } from "commander";
|
|
11
|
+
import { VERSION } from "./index.js";
|
|
12
|
+
import { run, type Command as CmdType } from "./run.js";
|
|
13
|
+
import type { InputFormat, OutputFormat } from "./format.js";
|
|
14
|
+
import type { MatchOutputFormat } from "./cmd/match.js";
|
|
15
|
+
|
|
16
|
+
const program = new Command();
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name("dcbor")
|
|
20
|
+
.description("Command line parser/validator for deterministic CBOR (dCBOR)")
|
|
21
|
+
.version(VERSION);
|
|
22
|
+
|
|
23
|
+
// Default command arguments (when no subcommand is provided)
|
|
24
|
+
program
|
|
25
|
+
.argument("[input]", "Input dCBOR in the format specified by --in")
|
|
26
|
+
.addOption(
|
|
27
|
+
new Option("-i, --in <format>", "The input format")
|
|
28
|
+
.choices(["diag", "hex", "bin"])
|
|
29
|
+
.default("diag"),
|
|
30
|
+
)
|
|
31
|
+
.addOption(
|
|
32
|
+
new Option("-o, --out <format>", "The output format")
|
|
33
|
+
.choices(["diag", "hex", "bin", "none"])
|
|
34
|
+
.default("hex"),
|
|
35
|
+
)
|
|
36
|
+
.option("-a, --annotate", "Output diagnostic notation or hexadecimal with annotations")
|
|
37
|
+
.action(
|
|
38
|
+
async (
|
|
39
|
+
input: string | undefined,
|
|
40
|
+
options: {
|
|
41
|
+
in: InputFormat;
|
|
42
|
+
out: OutputFormat;
|
|
43
|
+
annotate?: boolean;
|
|
44
|
+
},
|
|
45
|
+
) => {
|
|
46
|
+
// Read stdin if needed
|
|
47
|
+
let stdinContent: string | undefined;
|
|
48
|
+
if (input === undefined && options.in !== "bin") {
|
|
49
|
+
stdinContent = await readStdin();
|
|
50
|
+
} else if (options.in === "bin") {
|
|
51
|
+
stdinContent = await readStdin();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const command: CmdType = {
|
|
55
|
+
type: "default",
|
|
56
|
+
input,
|
|
57
|
+
in: options.in,
|
|
58
|
+
out: options.out,
|
|
59
|
+
annotate: options.annotate ?? false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const result = run({ command, stdinContent });
|
|
63
|
+
|
|
64
|
+
if (!result.ok) {
|
|
65
|
+
console.error(`Error: ${result.error.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
writeOutput(result.value.output, result.value.isBinary);
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Array subcommand
|
|
74
|
+
program
|
|
75
|
+
.command("array")
|
|
76
|
+
.description("Compose a dCBOR array from the provided elements")
|
|
77
|
+
.argument("<elements...>", "Each element is parsed as a dCBOR item in diagnostic notation")
|
|
78
|
+
.addOption(
|
|
79
|
+
new Option("-o, --out <format>", "The output format")
|
|
80
|
+
.choices(["diag", "hex", "bin", "none"])
|
|
81
|
+
.default("hex"),
|
|
82
|
+
)
|
|
83
|
+
.option("-a, --annotate", "Output diagnostic notation or hexadecimal with annotations")
|
|
84
|
+
.action(
|
|
85
|
+
(
|
|
86
|
+
elements: string[],
|
|
87
|
+
options: {
|
|
88
|
+
out: OutputFormat;
|
|
89
|
+
annotate?: boolean;
|
|
90
|
+
},
|
|
91
|
+
) => {
|
|
92
|
+
const command: CmdType = {
|
|
93
|
+
type: "array",
|
|
94
|
+
elements,
|
|
95
|
+
out: options.out,
|
|
96
|
+
annotate: options.annotate ?? false,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = run({ command });
|
|
100
|
+
|
|
101
|
+
if (!result.ok) {
|
|
102
|
+
console.error(`Error: ${result.error.message}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
writeOutput(result.value.output, result.value.isBinary);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Map subcommand
|
|
111
|
+
program
|
|
112
|
+
.command("map")
|
|
113
|
+
.description("Compose a dCBOR map from the provided keys and values")
|
|
114
|
+
.argument(
|
|
115
|
+
"<pairs...>",
|
|
116
|
+
"Each alternating key and value is parsed as a dCBOR item in diagnostic notation",
|
|
117
|
+
)
|
|
118
|
+
.addOption(
|
|
119
|
+
new Option("-o, --out <format>", "The output format")
|
|
120
|
+
.choices(["diag", "hex", "bin", "none"])
|
|
121
|
+
.default("hex"),
|
|
122
|
+
)
|
|
123
|
+
.option("-a, --annotate", "Output diagnostic notation or hexadecimal with annotations")
|
|
124
|
+
.action(
|
|
125
|
+
(
|
|
126
|
+
pairs: string[],
|
|
127
|
+
options: {
|
|
128
|
+
out: OutputFormat;
|
|
129
|
+
annotate?: boolean;
|
|
130
|
+
},
|
|
131
|
+
) => {
|
|
132
|
+
const command: CmdType = {
|
|
133
|
+
type: "map",
|
|
134
|
+
kvPairs: pairs,
|
|
135
|
+
out: options.out,
|
|
136
|
+
annotate: options.annotate ?? false,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = run({ command });
|
|
140
|
+
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
console.error(`Error: ${result.error.message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
writeOutput(result.value.output, result.value.isBinary);
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Match subcommand
|
|
151
|
+
program
|
|
152
|
+
.command("match")
|
|
153
|
+
.description("Match dCBOR data against a pattern")
|
|
154
|
+
.argument("<pattern>", "The pattern to match against")
|
|
155
|
+
.argument("[input]", "dCBOR input (hex, diag, or binary). If not provided, reads from stdin")
|
|
156
|
+
.addOption(
|
|
157
|
+
new Option("-i, --in <format>", "Input format").choices(["diag", "hex", "bin"]).default("diag"),
|
|
158
|
+
)
|
|
159
|
+
.addOption(
|
|
160
|
+
new Option("-o, --out <format>", "Output format")
|
|
161
|
+
.choices(["paths", "diag", "hex", "bin"])
|
|
162
|
+
.default("paths"),
|
|
163
|
+
)
|
|
164
|
+
.option("--no-indent", "Disable indentation of path elements")
|
|
165
|
+
.option("--last-only", "Show only the last element of each path")
|
|
166
|
+
.option("--annotate", "Add annotations to output")
|
|
167
|
+
.option("--captures", "Include capture information in output")
|
|
168
|
+
.action(
|
|
169
|
+
async (
|
|
170
|
+
pattern: string,
|
|
171
|
+
input: string | undefined,
|
|
172
|
+
options: {
|
|
173
|
+
in: InputFormat;
|
|
174
|
+
out: MatchOutputFormat;
|
|
175
|
+
indent: boolean;
|
|
176
|
+
lastOnly?: boolean;
|
|
177
|
+
annotate?: boolean;
|
|
178
|
+
captures?: boolean;
|
|
179
|
+
},
|
|
180
|
+
) => {
|
|
181
|
+
// Read stdin if needed
|
|
182
|
+
let stdinContent: string | undefined;
|
|
183
|
+
if (input === undefined) {
|
|
184
|
+
stdinContent = await readStdin();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const command: CmdType = {
|
|
188
|
+
type: "match",
|
|
189
|
+
pattern,
|
|
190
|
+
input,
|
|
191
|
+
in: options.in,
|
|
192
|
+
out: options.out,
|
|
193
|
+
noIndent: !options.indent,
|
|
194
|
+
lastOnly: options.lastOnly ?? false,
|
|
195
|
+
annotate: options.annotate ?? false,
|
|
196
|
+
captures: options.captures ?? false,
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const result = run({ command, stdinContent });
|
|
200
|
+
|
|
201
|
+
if (!result.ok) {
|
|
202
|
+
console.error(`Error: ${result.error.message}`);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
writeOutput(result.value.output, result.value.isBinary);
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Read from stdin
|
|
212
|
+
*/
|
|
213
|
+
async function readStdin(): Promise<string> {
|
|
214
|
+
// Check if stdin is a TTY (interactive terminal)
|
|
215
|
+
if (process.stdin.isTTY) {
|
|
216
|
+
return "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const chunks: Buffer[] = [];
|
|
220
|
+
for await (const chunk of process.stdin) {
|
|
221
|
+
chunks.push(chunk);
|
|
222
|
+
}
|
|
223
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Write output to stdout
|
|
228
|
+
*/
|
|
229
|
+
function writeOutput(output: string, isBinary: boolean): void {
|
|
230
|
+
if (isBinary) {
|
|
231
|
+
// For binary output, decode hex back to bytes
|
|
232
|
+
const bytes = Buffer.from(output, "hex");
|
|
233
|
+
process.stdout.write(bytes);
|
|
234
|
+
} else if (output.length > 0) {
|
|
235
|
+
console.log(output);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
program.parse();
|
package/src/cmd/array.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose a dCBOR array from the provided elements
|
|
3
|
+
* Equivalent to Rust's cmd/array.rs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type Result, errorMsg } from "@bcts/dcbor";
|
|
7
|
+
import { composeDcborArray, composeErrorMessage } from "@bcts/dcbor-parse";
|
|
8
|
+
import type { Exec } from "./index.js";
|
|
9
|
+
import { type OutputFormat, formatOutput } from "../format.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Command arguments for array composition
|
|
13
|
+
*/
|
|
14
|
+
export interface ArrayCommandArgs {
|
|
15
|
+
/** Each element is parsed as a dCBOR item in diagnostic notation */
|
|
16
|
+
elements: string[];
|
|
17
|
+
/** The output format (default: hex) */
|
|
18
|
+
out: OutputFormat;
|
|
19
|
+
/** Output with annotations */
|
|
20
|
+
annotate: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Execute array command
|
|
25
|
+
*/
|
|
26
|
+
export function execArray(args: ArrayCommandArgs): Result<string> {
|
|
27
|
+
const result = composeDcborArray(args.elements);
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
return { ok: false, error: errorMsg(composeErrorMessage(result.error)) };
|
|
30
|
+
}
|
|
31
|
+
return formatOutput(result.value, args.out, args.annotate);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create an Exec implementation for array command
|
|
36
|
+
*/
|
|
37
|
+
export function createArrayCommand(args: ArrayCommandArgs): Exec {
|
|
38
|
+
return {
|
|
39
|
+
exec: () => execArray(args),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default parsing and validation behavior
|
|
3
|
+
* Equivalent to Rust's cmd/default.rs
|
|
4
|
+
*/
|
|
5
|
+
/* eslint-disable @typescript-eslint/restrict-template-expressions, @typescript-eslint/strict-boolean-expressions */
|
|
6
|
+
|
|
7
|
+
import { type Cbor, type Result, decodeCbor, hexToBytes, errorMsg } from "@bcts/dcbor";
|
|
8
|
+
import { parseDcborItem, fullErrorMessage } from "@bcts/dcbor-parse";
|
|
9
|
+
import type { Exec } from "./index.js";
|
|
10
|
+
import { type InputFormat, type OutputFormat, formatOutput } from "../format.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Command arguments for default parsing behavior
|
|
14
|
+
*/
|
|
15
|
+
export interface DefaultCommandArgs {
|
|
16
|
+
/** Input dCBOR in the format specified by `in`. Optional - reads from stdin if not provided */
|
|
17
|
+
input?: string | undefined;
|
|
18
|
+
/** The input format (default: diag) */
|
|
19
|
+
in: InputFormat;
|
|
20
|
+
/** The output format (default: hex) */
|
|
21
|
+
out: OutputFormat;
|
|
22
|
+
/** Output with annotations */
|
|
23
|
+
annotate: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Execute default command with a reader function for stdin
|
|
28
|
+
*/
|
|
29
|
+
export function execDefaultWithReader(
|
|
30
|
+
args: DefaultCommandArgs,
|
|
31
|
+
readString: () => string,
|
|
32
|
+
readData: () => Uint8Array,
|
|
33
|
+
): Result<string> {
|
|
34
|
+
let cbor: Cbor;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
switch (args.in) {
|
|
38
|
+
case "diag": {
|
|
39
|
+
if (args.input !== undefined) {
|
|
40
|
+
const result = parseDcborItem(args.input);
|
|
41
|
+
if (!result.ok) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
error: errorMsg(fullErrorMessage(result.error, args.input)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
cbor = result.value;
|
|
48
|
+
} else {
|
|
49
|
+
const diag = readString();
|
|
50
|
+
const result = parseDcborItem(diag);
|
|
51
|
+
if (!result.ok) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: errorMsg(fullErrorMessage(result.error, diag)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
cbor = result.value;
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "hex": {
|
|
62
|
+
if (args.input !== undefined) {
|
|
63
|
+
cbor = decodeCbor(hexToBytes(args.input));
|
|
64
|
+
} else {
|
|
65
|
+
const hexStr = readString().trim();
|
|
66
|
+
cbor = decodeCbor(hexToBytes(hexStr));
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case "bin": {
|
|
71
|
+
const data = readData();
|
|
72
|
+
cbor = decodeCbor(data);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
default:
|
|
76
|
+
return { ok: false, error: errorMsg(`Unknown input format: ${args.in}`) };
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
return { ok: false, error: errorMsg(e instanceof Error ? e.message : String(e)) };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return formatOutput(cbor, args.out, args.annotate);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute default command (reads from stdin if input not provided)
|
|
87
|
+
*/
|
|
88
|
+
export function execDefault(args: DefaultCommandArgs, stdinContent?: string): Result<string> {
|
|
89
|
+
return execDefaultWithReader(
|
|
90
|
+
args,
|
|
91
|
+
() => stdinContent ?? "",
|
|
92
|
+
() => {
|
|
93
|
+
if (stdinContent) {
|
|
94
|
+
return new TextEncoder().encode(stdinContent);
|
|
95
|
+
}
|
|
96
|
+
return new Uint8Array(0);
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create an Exec implementation for default command
|
|
103
|
+
*/
|
|
104
|
+
export function createDefaultCommand(args: DefaultCommandArgs, stdinContent?: string): Exec {
|
|
105
|
+
return {
|
|
106
|
+
exec: () => execDefault(args, stdinContent),
|
|
107
|
+
};
|
|
108
|
+
}
|
package/src/cmd/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command module organization
|
|
3
|
+
* Equivalent to Rust's cmd/mod.rs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Result } from "@bcts/dcbor";
|
|
7
|
+
|
|
8
|
+
export * from "./array.js";
|
|
9
|
+
export * from "./default.js";
|
|
10
|
+
export * from "./map.js";
|
|
11
|
+
export * from "./match.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Trait for command execution
|
|
15
|
+
* Equivalent to Rust's `pub trait Exec`
|
|
16
|
+
*/
|
|
17
|
+
export interface Exec {
|
|
18
|
+
exec(): Result<string>;
|
|
19
|
+
}
|
package/src/cmd/map.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose a dCBOR map from the provided keys and values
|
|
3
|
+
* Equivalent to Rust's cmd/map.rs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { type Result, errorMsg } from "@bcts/dcbor";
|
|
7
|
+
import { composeDcborMap, composeErrorMessage } from "@bcts/dcbor-parse";
|
|
8
|
+
import type { Exec } from "./index.js";
|
|
9
|
+
import { type OutputFormat, formatOutput } from "../format.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Command arguments for map composition
|
|
13
|
+
*/
|
|
14
|
+
export interface MapCommandArgs {
|
|
15
|
+
/** Alternating keys and values parsed as dCBOR items in diagnostic notation */
|
|
16
|
+
kvPairs: string[];
|
|
17
|
+
/** The output format (default: hex) */
|
|
18
|
+
out: OutputFormat;
|
|
19
|
+
/** Output with annotations */
|
|
20
|
+
annotate: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Execute map command
|
|
25
|
+
*/
|
|
26
|
+
export function execMap(args: MapCommandArgs): Result<string> {
|
|
27
|
+
const result = composeDcborMap(args.kvPairs);
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
return { ok: false, error: errorMsg(composeErrorMessage(result.error)) };
|
|
30
|
+
}
|
|
31
|
+
return formatOutput(result.value, args.out, args.annotate);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create an Exec implementation for map command
|
|
36
|
+
*/
|
|
37
|
+
export function createMapCommand(args: MapCommandArgs): Exec {
|
|
38
|
+
return {
|
|
39
|
+
exec: () => execMap(args),
|
|
40
|
+
};
|
|
41
|
+
}
|
package/src/cmd/match.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match dCBOR data against a pattern
|
|
3
|
+
* Equivalent to Rust's cmd/match.rs
|
|
4
|
+
*/
|
|
5
|
+
/* eslint-disable @typescript-eslint/restrict-template-expressions, @typescript-eslint/switch-exhaustiveness-check */
|
|
6
|
+
|
|
7
|
+
import { type Cbor, type Result, decodeCbor, hexToBytes, errorMsg } from "@bcts/dcbor";
|
|
8
|
+
import { parseDcborItem, fullErrorMessage } from "@bcts/dcbor-parse";
|
|
9
|
+
import {
|
|
10
|
+
parse as parsePattern,
|
|
11
|
+
pathsWithCaptures,
|
|
12
|
+
formatPathsWithCaptures,
|
|
13
|
+
FormatPathsOptsBuilder,
|
|
14
|
+
type Error as PatternParseError,
|
|
15
|
+
} from "@bcts/dcbor-pattern";
|
|
16
|
+
import type { Exec } from "./index.js";
|
|
17
|
+
import { type OutputFormat, formatOutput } from "../format.js";
|
|
18
|
+
import type { InputFormat } from "../format.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Match output format options
|
|
22
|
+
*/
|
|
23
|
+
export type MatchOutputFormat = "paths" | "diag" | "hex" | "bin";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Command arguments for match command
|
|
27
|
+
*/
|
|
28
|
+
export interface MatchCommandArgs {
|
|
29
|
+
/** The pattern to match against */
|
|
30
|
+
pattern: string;
|
|
31
|
+
/** dCBOR input (hex, diag, or binary). If not provided, reads from stdin */
|
|
32
|
+
input?: string | undefined;
|
|
33
|
+
/** Input format (default: diag) */
|
|
34
|
+
in: InputFormat;
|
|
35
|
+
/** Output format (default: paths) */
|
|
36
|
+
out: MatchOutputFormat;
|
|
37
|
+
/** Disable indentation of path elements */
|
|
38
|
+
noIndent: boolean;
|
|
39
|
+
/** Show only the last element of each path */
|
|
40
|
+
lastOnly: boolean;
|
|
41
|
+
/** Add annotations to output */
|
|
42
|
+
annotate: boolean;
|
|
43
|
+
/** Include capture information in output */
|
|
44
|
+
captures: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format a parse error with context
|
|
49
|
+
*/
|
|
50
|
+
function formatPatternError(error: PatternParseError, patternStr: string): string {
|
|
51
|
+
switch (error.type) {
|
|
52
|
+
case "UnrecognizedToken": {
|
|
53
|
+
const start = Math.min(error.span.start, patternStr.length);
|
|
54
|
+
const end = Math.min(error.span.end, patternStr.length);
|
|
55
|
+
const errorText = start < patternStr.length ? patternStr.slice(start, end) : "<end of input>";
|
|
56
|
+
return `Failed to parse pattern at position ${start}..${end}: unrecognized token '${errorText}'\nPattern: ${patternStr}\n ${" ".repeat(start)}^`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
case "ExtraData": {
|
|
60
|
+
const start = Math.min(error.span.start, patternStr.length);
|
|
61
|
+
return `Failed to parse pattern: extra data at position ${start}\nPattern: ${patternStr}\n ${" ".repeat(start)}^`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case "UnexpectedToken": {
|
|
65
|
+
const start = Math.min(error.span.start, patternStr.length);
|
|
66
|
+
return `Failed to parse pattern at position ${start}: unexpected token\nPattern: ${patternStr}\n ${" ".repeat(start)}^`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
default:
|
|
70
|
+
return `Failed to parse pattern: ${error.type}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Execute match command
|
|
76
|
+
*/
|
|
77
|
+
export function execMatch(args: MatchCommandArgs, stdinContent?: string): Result<string> {
|
|
78
|
+
// Read input data
|
|
79
|
+
let inputData: Uint8Array;
|
|
80
|
+
if (args.input !== undefined) {
|
|
81
|
+
inputData = new TextEncoder().encode(args.input);
|
|
82
|
+
} else if (stdinContent !== undefined) {
|
|
83
|
+
inputData = new TextEncoder().encode(stdinContent);
|
|
84
|
+
} else {
|
|
85
|
+
inputData = new Uint8Array(0);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse input based on format
|
|
89
|
+
let cbor: Cbor;
|
|
90
|
+
try {
|
|
91
|
+
switch (args.in) {
|
|
92
|
+
case "diag": {
|
|
93
|
+
const inputStr = new TextDecoder().decode(inputData).trim();
|
|
94
|
+
const result = parseDcborItem(inputStr);
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
error: errorMsg(fullErrorMessage(result.error, inputStr)),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
cbor = result.value;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "hex": {
|
|
105
|
+
const inputStr = new TextDecoder().decode(inputData).trim();
|
|
106
|
+
cbor = decodeCbor(hexToBytes(inputStr));
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "bin": {
|
|
110
|
+
cbor = decodeCbor(inputData);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
default:
|
|
114
|
+
return { ok: false, error: errorMsg(`Unknown input format: ${args.in}`) };
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return { ok: false, error: errorMsg(e instanceof Error ? e.message : String(e)) };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Parse pattern
|
|
121
|
+
const patternResult = parsePattern(args.pattern);
|
|
122
|
+
if (!patternResult.ok) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: errorMsg(formatPatternError(patternResult.error, args.pattern)),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const pattern = patternResult.value;
|
|
129
|
+
|
|
130
|
+
// Execute pattern matching
|
|
131
|
+
const { paths, captures } = pathsWithCaptures(pattern, cbor);
|
|
132
|
+
|
|
133
|
+
// Check for matches
|
|
134
|
+
if (paths.length === 0) {
|
|
135
|
+
return { ok: false, error: errorMsg("No match") };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Format output based on requested format
|
|
139
|
+
switch (args.out) {
|
|
140
|
+
case "paths": {
|
|
141
|
+
// Build format options from command line arguments
|
|
142
|
+
const formatOptions = FormatPathsOptsBuilder.new()
|
|
143
|
+
.indent(!args.noIndent)
|
|
144
|
+
.lastElementOnly(args.lastOnly)
|
|
145
|
+
.build();
|
|
146
|
+
|
|
147
|
+
// Show captures only if explicitly requested
|
|
148
|
+
if (args.captures) {
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
value: formatPathsWithCaptures(paths, captures, formatOptions),
|
|
152
|
+
};
|
|
153
|
+
} else {
|
|
154
|
+
// Show paths without captures
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
value: formatPathsWithCaptures(paths, new Map(), formatOptions),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "diag":
|
|
163
|
+
case "hex":
|
|
164
|
+
case "bin": {
|
|
165
|
+
// For data format outputs, extract the matched elements
|
|
166
|
+
const outputFormat: OutputFormat = args.out;
|
|
167
|
+
|
|
168
|
+
const elementsToOutput = args.lastOnly
|
|
169
|
+
? paths.map((path) => path[path.length - 1]).filter(Boolean)
|
|
170
|
+
: paths.map((path) => path[0]).filter(Boolean);
|
|
171
|
+
|
|
172
|
+
const results: string[] = [];
|
|
173
|
+
for (const element of elementsToOutput) {
|
|
174
|
+
const result = formatOutput(element, outputFormat, args.annotate);
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
results.push(result.value);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { ok: true, value: results.join("\n") };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
default:
|
|
185
|
+
return { ok: false, error: errorMsg(`Unknown output format: ${args.out}`) };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create an Exec implementation for match command
|
|
191
|
+
*/
|
|
192
|
+
export function createMatchCommand(args: MatchCommandArgs, stdinContent?: string): Exec {
|
|
193
|
+
return {
|
|
194
|
+
exec: () => execMatch(args, stdinContent),
|
|
195
|
+
};
|
|
196
|
+
}
|