@bcts/envelope-pattern 1.0.0-alpha.12
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 +13 -0
- package/dist/index.cjs +6781 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2628 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +2628 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +6781 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +6545 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
- package/src/error.ts +262 -0
- package/src/format.ts +375 -0
- package/src/index.ts +27 -0
- package/src/parse/index.ts +923 -0
- package/src/parse/token.ts +906 -0
- package/src/parse/utils.ts +339 -0
- package/src/pattern/index.ts +719 -0
- package/src/pattern/leaf/array-pattern.ts +273 -0
- package/src/pattern/leaf/bool-pattern.ts +140 -0
- package/src/pattern/leaf/byte-string-pattern.ts +172 -0
- package/src/pattern/leaf/cbor-pattern.ts +355 -0
- package/src/pattern/leaf/date-pattern.ts +178 -0
- package/src/pattern/leaf/index.ts +280 -0
- package/src/pattern/leaf/known-value-pattern.ts +192 -0
- package/src/pattern/leaf/map-pattern.ts +152 -0
- package/src/pattern/leaf/null-pattern.ts +110 -0
- package/src/pattern/leaf/number-pattern.ts +248 -0
- package/src/pattern/leaf/tagged-pattern.ts +228 -0
- package/src/pattern/leaf/text-pattern.ts +165 -0
- package/src/pattern/matcher.ts +88 -0
- package/src/pattern/meta/and-pattern.ts +109 -0
- package/src/pattern/meta/any-pattern.ts +81 -0
- package/src/pattern/meta/capture-pattern.ts +111 -0
- package/src/pattern/meta/group-pattern.ts +110 -0
- package/src/pattern/meta/index.ts +269 -0
- package/src/pattern/meta/not-pattern.ts +91 -0
- package/src/pattern/meta/or-pattern.ts +146 -0
- package/src/pattern/meta/search-pattern.ts +201 -0
- package/src/pattern/meta/traverse-pattern.ts +146 -0
- package/src/pattern/structure/assertions-pattern.ts +244 -0
- package/src/pattern/structure/digest-pattern.ts +225 -0
- package/src/pattern/structure/index.ts +272 -0
- package/src/pattern/structure/leaf-structure-pattern.ts +85 -0
- package/src/pattern/structure/node-pattern.ts +188 -0
- package/src/pattern/structure/object-pattern.ts +149 -0
- package/src/pattern/structure/obscured-pattern.ts +159 -0
- package/src/pattern/structure/predicate-pattern.ts +151 -0
- package/src/pattern/structure/subject-pattern.ts +152 -0
- package/src/pattern/structure/wrapped-pattern.ts +195 -0
- package/src/pattern/vm.ts +1021 -0
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bcts/envelope-pattern",
|
|
3
|
+
"version": "1.0.0-alpha.12",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pattern matching for Gordian Envelope structures",
|
|
6
|
+
"license": "BSD-2-Clause-Patent",
|
|
7
|
+
"contributors": [
|
|
8
|
+
{
|
|
9
|
+
"name": "Leonardo Custodio",
|
|
10
|
+
"email": "leonardo.custodio@parity.io",
|
|
11
|
+
"url": "https://github.com/leonardocustodio"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "Karim Jedda",
|
|
15
|
+
"email": "karim@parity.io",
|
|
16
|
+
"url": "https://github.com/KarimJedda"
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://bcts.dev",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/leonardocustodio/bcts.git",
|
|
23
|
+
"directory": "packages/envelope-pattern"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/leonardocustodio/bcts/issues"
|
|
27
|
+
},
|
|
28
|
+
"main": "dist/index.cjs",
|
|
29
|
+
"module": "dist/index.mjs",
|
|
30
|
+
"types": "dist/index.d.mts",
|
|
31
|
+
"browser": "dist/index.iife.js",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"import": "./dist/index.mjs",
|
|
36
|
+
"require": "./dist/index.cjs",
|
|
37
|
+
"default": "./dist/index.mjs"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"src",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"dev": "tsdown --watch",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest",
|
|
50
|
+
"lint": "eslint 'src/**/*.ts' 'tests/**/*.ts'",
|
|
51
|
+
"lint:fix": "eslint 'src/**/*.ts' 'tests/**/*.ts' --fix",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"clean": "rm -rf dist",
|
|
54
|
+
"docs": "typedoc",
|
|
55
|
+
"prepublishOnly": "npm run clean && npm run build && npm test"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18.0.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@bcts/eslint": "workspace:*",
|
|
62
|
+
"@bcts/tsconfig": "workspace:*",
|
|
63
|
+
"eslint": "^9.39.2",
|
|
64
|
+
"tsdown": "^0.18.3",
|
|
65
|
+
"typedoc": "^0.28.15",
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"vitest": "^4.0.16"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@bcts/dcbor": "workspace:*",
|
|
71
|
+
"@bcts/dcbor-pattern": "workspace:*",
|
|
72
|
+
"@bcts/envelope": "workspace:*",
|
|
73
|
+
"@bcts/components": "workspace:*",
|
|
74
|
+
"@bcts/tags": "workspace:*",
|
|
75
|
+
"@bcts/known-values": "workspace:*"
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/error.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Error types for envelope pattern parsing
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust error.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/error
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Token } from "./parse/token";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Span represents a range in the source input.
|
|
13
|
+
*/
|
|
14
|
+
export interface Span {
|
|
15
|
+
readonly start: number;
|
|
16
|
+
readonly end: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Error types that can occur during parsing of Envelope patterns.
|
|
21
|
+
*
|
|
22
|
+
* Corresponds to the Rust `Error` enum in error.rs
|
|
23
|
+
*/
|
|
24
|
+
export type EnvelopePatternError =
|
|
25
|
+
| { readonly type: "EmptyInput" }
|
|
26
|
+
| { readonly type: "UnexpectedEndOfInput" }
|
|
27
|
+
| { readonly type: "ExtraData"; readonly span: Span }
|
|
28
|
+
| { readonly type: "UnexpectedToken"; readonly token: Token; readonly span: Span }
|
|
29
|
+
| { readonly type: "UnrecognizedToken"; readonly span: Span }
|
|
30
|
+
| { readonly type: "InvalidRegex"; readonly span: Span }
|
|
31
|
+
| { readonly type: "UnterminatedRegex"; readonly span: Span }
|
|
32
|
+
| { readonly type: "InvalidRange"; readonly span: Span }
|
|
33
|
+
| { readonly type: "InvalidHexString"; readonly span: Span }
|
|
34
|
+
| { readonly type: "InvalidDateFormat"; readonly span: Span }
|
|
35
|
+
| { readonly type: "InvalidNumberFormat"; readonly span: Span }
|
|
36
|
+
| { readonly type: "InvalidUr"; readonly message: string; readonly span: Span }
|
|
37
|
+
| { readonly type: "ExpectedOpenParen"; readonly span: Span }
|
|
38
|
+
| { readonly type: "ExpectedCloseParen"; readonly span: Span }
|
|
39
|
+
| { readonly type: "ExpectedOpenBracket"; readonly span: Span }
|
|
40
|
+
| { readonly type: "ExpectedCloseBracket"; readonly span: Span }
|
|
41
|
+
| { readonly type: "ExpectedPattern"; readonly span: Span }
|
|
42
|
+
| { readonly type: "UnmatchedParentheses"; readonly span: Span }
|
|
43
|
+
| { readonly type: "UnmatchedBraces"; readonly span: Span }
|
|
44
|
+
| { readonly type: "InvalidCaptureGroupName"; readonly name: string; readonly span: Span }
|
|
45
|
+
| { readonly type: "InvalidPattern"; readonly span: Span }
|
|
46
|
+
| { readonly type: "Unknown" }
|
|
47
|
+
| { readonly type: "DCBORPatternError"; readonly error: unknown };
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Result type specialized for envelope pattern parsing.
|
|
51
|
+
*/
|
|
52
|
+
export type Result<T> =
|
|
53
|
+
| { readonly ok: true; readonly value: T }
|
|
54
|
+
| { readonly ok: false; readonly error: EnvelopePatternError };
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a successful result.
|
|
58
|
+
*/
|
|
59
|
+
export function ok<T>(value: T): Result<T> {
|
|
60
|
+
return { ok: true, value };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Creates a failed result.
|
|
65
|
+
*/
|
|
66
|
+
export function err<T>(error: EnvelopePatternError): Result<T> {
|
|
67
|
+
return { ok: false, error };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Type guard for successful results.
|
|
72
|
+
*/
|
|
73
|
+
export function isOk<T>(result: Result<T>): result is { readonly ok: true; readonly value: T } {
|
|
74
|
+
return result.ok;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Type guard for failed results.
|
|
79
|
+
*/
|
|
80
|
+
export function isErr<T>(
|
|
81
|
+
result: Result<T>,
|
|
82
|
+
): result is { readonly ok: false; readonly error: EnvelopePatternError } {
|
|
83
|
+
return !result.ok;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Unwraps a successful result or throws the error.
|
|
88
|
+
*/
|
|
89
|
+
export function unwrap<T>(result: Result<T>): T {
|
|
90
|
+
if (result.ok) {
|
|
91
|
+
return result.value;
|
|
92
|
+
}
|
|
93
|
+
throw new Error(formatError(result.error));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Unwraps a successful result or returns a default value.
|
|
98
|
+
*/
|
|
99
|
+
export function unwrapOr<T>(result: Result<T>, defaultValue: T): T {
|
|
100
|
+
if (result.ok) {
|
|
101
|
+
return result.value;
|
|
102
|
+
}
|
|
103
|
+
return defaultValue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Maps a successful result value.
|
|
108
|
+
*/
|
|
109
|
+
export function map<T, U>(result: Result<T>, fn: (value: T) => U): Result<U> {
|
|
110
|
+
if (result.ok) {
|
|
111
|
+
return ok(fn(result.value));
|
|
112
|
+
}
|
|
113
|
+
return result as Result<U>;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Formats an error for display.
|
|
118
|
+
*/
|
|
119
|
+
export function formatError(error: EnvelopePatternError): string {
|
|
120
|
+
switch (error.type) {
|
|
121
|
+
case "EmptyInput":
|
|
122
|
+
return "Empty input";
|
|
123
|
+
case "UnexpectedEndOfInput":
|
|
124
|
+
return "Unexpected end of input";
|
|
125
|
+
case "ExtraData":
|
|
126
|
+
return `Extra data at end of input at position ${error.span.start}-${error.span.end}`;
|
|
127
|
+
case "UnexpectedToken":
|
|
128
|
+
return `Unexpected token ${JSON.stringify(error.token)} at position ${error.span.start}-${error.span.end}`;
|
|
129
|
+
case "UnrecognizedToken":
|
|
130
|
+
return `Unrecognized token at position ${error.span.start}-${error.span.end}`;
|
|
131
|
+
case "InvalidRegex":
|
|
132
|
+
return `Invalid regex pattern at position ${error.span.start}-${error.span.end}`;
|
|
133
|
+
case "UnterminatedRegex":
|
|
134
|
+
return `Unterminated regex pattern at position ${error.span.start}-${error.span.end}`;
|
|
135
|
+
case "InvalidRange":
|
|
136
|
+
return `Invalid range at position ${error.span.start}-${error.span.end}`;
|
|
137
|
+
case "InvalidHexString":
|
|
138
|
+
return `Invalid hex string at position ${error.span.start}-${error.span.end}`;
|
|
139
|
+
case "InvalidDateFormat":
|
|
140
|
+
return `Invalid date format at position ${error.span.start}-${error.span.end}`;
|
|
141
|
+
case "InvalidNumberFormat":
|
|
142
|
+
return `Invalid number format at position ${error.span.start}-${error.span.end}`;
|
|
143
|
+
case "InvalidUr":
|
|
144
|
+
return `Invalid UR: ${error.message} at position ${error.span.start}-${error.span.end}`;
|
|
145
|
+
case "ExpectedOpenParen":
|
|
146
|
+
return `Expected opening parenthesis at position ${error.span.start}-${error.span.end}`;
|
|
147
|
+
case "ExpectedCloseParen":
|
|
148
|
+
return `Expected closing parenthesis at position ${error.span.start}-${error.span.end}`;
|
|
149
|
+
case "ExpectedOpenBracket":
|
|
150
|
+
return `Expected opening bracket at position ${error.span.start}-${error.span.end}`;
|
|
151
|
+
case "ExpectedCloseBracket":
|
|
152
|
+
return `Expected closing bracket at position ${error.span.start}-${error.span.end}`;
|
|
153
|
+
case "ExpectedPattern":
|
|
154
|
+
return `Expected pattern after operator at position ${error.span.start}-${error.span.end}`;
|
|
155
|
+
case "UnmatchedParentheses":
|
|
156
|
+
return `Unmatched parentheses at position ${error.span.start}-${error.span.end}`;
|
|
157
|
+
case "UnmatchedBraces":
|
|
158
|
+
return `Unmatched braces at position ${error.span.start}-${error.span.end}`;
|
|
159
|
+
case "InvalidCaptureGroupName":
|
|
160
|
+
return `Invalid capture group name '${error.name}' at position ${error.span.start}-${error.span.end}`;
|
|
161
|
+
case "InvalidPattern":
|
|
162
|
+
return `Invalid pattern at position ${error.span.start}-${error.span.end}`;
|
|
163
|
+
case "Unknown":
|
|
164
|
+
return "Unknown error";
|
|
165
|
+
case "DCBORPatternError":
|
|
166
|
+
return `DCBOR pattern error: ${String(error.error)}`;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Error factory functions for convenience
|
|
171
|
+
|
|
172
|
+
export function emptyInput(): EnvelopePatternError {
|
|
173
|
+
return { type: "EmptyInput" };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function unexpectedEndOfInput(): EnvelopePatternError {
|
|
177
|
+
return { type: "UnexpectedEndOfInput" };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function extraData(span: Span): EnvelopePatternError {
|
|
181
|
+
return { type: "ExtraData", span };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function unexpectedToken(token: Token, span: Span): EnvelopePatternError {
|
|
185
|
+
return { type: "UnexpectedToken", token, span };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function unrecognizedToken(span: Span): EnvelopePatternError {
|
|
189
|
+
return { type: "UnrecognizedToken", span };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function invalidRegex(span: Span): EnvelopePatternError {
|
|
193
|
+
return { type: "InvalidRegex", span };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function unterminatedRegex(span: Span): EnvelopePatternError {
|
|
197
|
+
return { type: "UnterminatedRegex", span };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function invalidRange(span: Span): EnvelopePatternError {
|
|
201
|
+
return { type: "InvalidRange", span };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function invalidHexString(span: Span): EnvelopePatternError {
|
|
205
|
+
return { type: "InvalidHexString", span };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function invalidDateFormat(span: Span): EnvelopePatternError {
|
|
209
|
+
return { type: "InvalidDateFormat", span };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function invalidNumberFormat(span: Span): EnvelopePatternError {
|
|
213
|
+
return { type: "InvalidNumberFormat", span };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function invalidUr(message: string, span: Span): EnvelopePatternError {
|
|
217
|
+
return { type: "InvalidUr", message, span };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function expectedOpenParen(span: Span): EnvelopePatternError {
|
|
221
|
+
return { type: "ExpectedOpenParen", span };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function expectedCloseParen(span: Span): EnvelopePatternError {
|
|
225
|
+
return { type: "ExpectedCloseParen", span };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function expectedOpenBracket(span: Span): EnvelopePatternError {
|
|
229
|
+
return { type: "ExpectedOpenBracket", span };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function expectedCloseBracket(span: Span): EnvelopePatternError {
|
|
233
|
+
return { type: "ExpectedCloseBracket", span };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function expectedPattern(span: Span): EnvelopePatternError {
|
|
237
|
+
return { type: "ExpectedPattern", span };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function unmatchedParentheses(span: Span): EnvelopePatternError {
|
|
241
|
+
return { type: "UnmatchedParentheses", span };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function unmatchedBraces(span: Span): EnvelopePatternError {
|
|
245
|
+
return { type: "UnmatchedBraces", span };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function invalidCaptureGroupName(name: string, span: Span): EnvelopePatternError {
|
|
249
|
+
return { type: "InvalidCaptureGroupName", name, span };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function invalidPattern(span: Span): EnvelopePatternError {
|
|
253
|
+
return { type: "InvalidPattern", span };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function unknown(): EnvelopePatternError {
|
|
257
|
+
return { type: "Unknown" };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function dcborPatternError(error: unknown): EnvelopePatternError {
|
|
261
|
+
return { type: "DCBORPatternError", error };
|
|
262
|
+
}
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bcts/envelope-pattern - Path formatting utilities
|
|
3
|
+
*
|
|
4
|
+
* This is a 1:1 TypeScript port of bc-envelope-pattern-rust format.rs
|
|
5
|
+
*
|
|
6
|
+
* @module envelope-pattern/format
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Envelope } from "@bcts/envelope";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A path is a sequence of envelopes from root to a matched element.
|
|
13
|
+
*/
|
|
14
|
+
export type Path = Envelope[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Format options for each path element.
|
|
18
|
+
*
|
|
19
|
+
* Corresponds to the Rust `PathElementFormat` enum in format.rs
|
|
20
|
+
*/
|
|
21
|
+
export type PathElementFormat =
|
|
22
|
+
| { readonly type: "Summary"; readonly maxLength?: number }
|
|
23
|
+
| { readonly type: "EnvelopeUR" }
|
|
24
|
+
| { readonly type: "DigestUR" };
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a Summary format.
|
|
28
|
+
*/
|
|
29
|
+
export function summaryFormat(maxLength?: number): PathElementFormat {
|
|
30
|
+
if (maxLength !== undefined) {
|
|
31
|
+
return { type: "Summary", maxLength };
|
|
32
|
+
}
|
|
33
|
+
return { type: "Summary" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates an EnvelopeUR format.
|
|
38
|
+
*/
|
|
39
|
+
export function envelopeURFormat(): PathElementFormat {
|
|
40
|
+
return { type: "EnvelopeUR" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a DigestUR format.
|
|
45
|
+
*/
|
|
46
|
+
export function digestURFormat(): PathElementFormat {
|
|
47
|
+
return { type: "DigestUR" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Default path element format.
|
|
52
|
+
*/
|
|
53
|
+
export function defaultPathElementFormat(): PathElementFormat {
|
|
54
|
+
return summaryFormat(undefined);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Options for formatting paths.
|
|
59
|
+
*
|
|
60
|
+
* Corresponds to the Rust `FormatPathsOpts` struct in format.rs
|
|
61
|
+
*/
|
|
62
|
+
export interface FormatPathsOpts {
|
|
63
|
+
/**
|
|
64
|
+
* Whether to indent each path element.
|
|
65
|
+
* If true, each element will be indented by 4 spaces per level.
|
|
66
|
+
* Default: true
|
|
67
|
+
*/
|
|
68
|
+
readonly indent: boolean;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Format for each path element.
|
|
72
|
+
* Default: Summary(None)
|
|
73
|
+
*/
|
|
74
|
+
readonly elementFormat: PathElementFormat;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* If true, only the last element of each path will be formatted.
|
|
78
|
+
* This is useful for displaying only the final destination of a path.
|
|
79
|
+
* If false, all elements will be formatted.
|
|
80
|
+
* Default: false
|
|
81
|
+
*/
|
|
82
|
+
readonly lastElementOnly: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates default formatting options.
|
|
87
|
+
*/
|
|
88
|
+
export function defaultFormatPathsOpts(): FormatPathsOpts {
|
|
89
|
+
return {
|
|
90
|
+
indent: true,
|
|
91
|
+
elementFormat: defaultPathElementFormat(),
|
|
92
|
+
lastElementOnly: false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builder for FormatPathsOpts.
|
|
98
|
+
*/
|
|
99
|
+
export class FormatPathsOptsBuilder {
|
|
100
|
+
#indent = true;
|
|
101
|
+
#elementFormat: PathElementFormat = defaultPathElementFormat();
|
|
102
|
+
#lastElementOnly = false;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sets whether to indent each path element.
|
|
106
|
+
*/
|
|
107
|
+
indent(indent: boolean): this {
|
|
108
|
+
this.#indent = indent;
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sets the format for each path element.
|
|
114
|
+
*/
|
|
115
|
+
elementFormat(format: PathElementFormat): this {
|
|
116
|
+
this.#elementFormat = format;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Sets whether to format only the last element of each path.
|
|
122
|
+
*/
|
|
123
|
+
lastElementOnly(lastElementOnly: boolean): this {
|
|
124
|
+
this.#lastElementOnly = lastElementOnly;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Builds the FormatPathsOpts.
|
|
130
|
+
*/
|
|
131
|
+
build(): FormatPathsOpts {
|
|
132
|
+
return {
|
|
133
|
+
indent: this.#indent,
|
|
134
|
+
elementFormat: this.#elementFormat,
|
|
135
|
+
lastElementOnly: this.#lastElementOnly,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Creates a new FormatPathsOptsBuilder.
|
|
142
|
+
*/
|
|
143
|
+
export function formatPathsOpts(): FormatPathsOptsBuilder {
|
|
144
|
+
return new FormatPathsOptsBuilder();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Gets a summary of an envelope for display.
|
|
149
|
+
*
|
|
150
|
+
* @param env - The envelope to summarize
|
|
151
|
+
* @returns A string summary of the envelope
|
|
152
|
+
*/
|
|
153
|
+
export function envelopeSummary(env: Envelope): string {
|
|
154
|
+
const id = env.shortId("short");
|
|
155
|
+
const c = env.case();
|
|
156
|
+
|
|
157
|
+
let summary: string;
|
|
158
|
+
switch (c.type) {
|
|
159
|
+
case "node":
|
|
160
|
+
summary = `NODE ${env.summary(Number.MAX_SAFE_INTEGER)}`;
|
|
161
|
+
break;
|
|
162
|
+
case "leaf":
|
|
163
|
+
summary = `LEAF ${env.summary(Number.MAX_SAFE_INTEGER)}`;
|
|
164
|
+
break;
|
|
165
|
+
case "wrapped":
|
|
166
|
+
summary = `WRAPPED ${env.summary(Number.MAX_SAFE_INTEGER)}`;
|
|
167
|
+
break;
|
|
168
|
+
case "assertion":
|
|
169
|
+
summary = `ASSERTION ${env.summary(Number.MAX_SAFE_INTEGER)}`;
|
|
170
|
+
break;
|
|
171
|
+
case "elided":
|
|
172
|
+
summary = "ELIDED";
|
|
173
|
+
break;
|
|
174
|
+
case "knownValue":
|
|
175
|
+
summary = `KNOWN_VALUE '${c.value.name()}'`;
|
|
176
|
+
break;
|
|
177
|
+
case "encrypted":
|
|
178
|
+
summary = "ENCRYPTED";
|
|
179
|
+
break;
|
|
180
|
+
case "compressed":
|
|
181
|
+
summary = "COMPRESSED";
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
summary = "UNKNOWN";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return `${id} ${summary}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Truncates a string to the specified maximum length, appending an ellipsis if truncated.
|
|
192
|
+
*
|
|
193
|
+
* @param s - The string to truncate
|
|
194
|
+
* @param maxLength - Optional maximum length
|
|
195
|
+
* @returns The truncated string
|
|
196
|
+
*/
|
|
197
|
+
function truncateWithEllipsis(s: string, maxLength?: number): string {
|
|
198
|
+
if (maxLength === undefined) {
|
|
199
|
+
return s;
|
|
200
|
+
}
|
|
201
|
+
if (s.length > maxLength) {
|
|
202
|
+
if (maxLength > 1) {
|
|
203
|
+
return `${s.substring(0, maxLength - 1)}…`;
|
|
204
|
+
}
|
|
205
|
+
return "…";
|
|
206
|
+
}
|
|
207
|
+
return s;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Format a single path element on its own line with custom options.
|
|
212
|
+
*
|
|
213
|
+
* @param path - The path to format
|
|
214
|
+
* @param opts - Formatting options
|
|
215
|
+
* @returns The formatted path string
|
|
216
|
+
*/
|
|
217
|
+
export function formatPathOpt(
|
|
218
|
+
path: Path,
|
|
219
|
+
opts: FormatPathsOpts = defaultFormatPathsOpts(),
|
|
220
|
+
): string {
|
|
221
|
+
if (opts.lastElementOnly) {
|
|
222
|
+
// Only format the last element, no indentation
|
|
223
|
+
const element = path[path.length - 1];
|
|
224
|
+
if (element === undefined) {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
switch (opts.elementFormat.type) {
|
|
229
|
+
case "Summary": {
|
|
230
|
+
const summary = envelopeSummary(element);
|
|
231
|
+
return truncateWithEllipsis(summary, opts.elementFormat.maxLength);
|
|
232
|
+
}
|
|
233
|
+
case "EnvelopeUR":
|
|
234
|
+
// TODO: Implement proper UR string format when available
|
|
235
|
+
return element.digest().toString();
|
|
236
|
+
case "DigestUR":
|
|
237
|
+
return element.digest().toString();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
switch (opts.elementFormat.type) {
|
|
242
|
+
case "Summary": {
|
|
243
|
+
// Multi-line output with indentation for summaries
|
|
244
|
+
const lines: string[] = [];
|
|
245
|
+
for (let index = 0; index < path.length; index++) {
|
|
246
|
+
const element = path[index];
|
|
247
|
+
if (element === undefined) continue;
|
|
248
|
+
|
|
249
|
+
const indent = opts.indent ? " ".repeat(index * 4) : "";
|
|
250
|
+
const summary = envelopeSummary(element);
|
|
251
|
+
const content = truncateWithEllipsis(summary, opts.elementFormat.maxLength);
|
|
252
|
+
lines.push(`${indent}${content}`);
|
|
253
|
+
}
|
|
254
|
+
return lines.join("\n");
|
|
255
|
+
}
|
|
256
|
+
case "EnvelopeUR":
|
|
257
|
+
// TODO: Implement proper UR string format when available
|
|
258
|
+
return path.map((element) => element.digest().toString()).join(" ");
|
|
259
|
+
case "DigestUR":
|
|
260
|
+
// Single-line, space-separated digest strings
|
|
261
|
+
return path.map((element) => element.digest().toString()).join(" ");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Format a single path with default options.
|
|
267
|
+
*
|
|
268
|
+
* @param path - The path to format
|
|
269
|
+
* @returns The formatted path string
|
|
270
|
+
*/
|
|
271
|
+
export function formatPath(path: Path): string {
|
|
272
|
+
return formatPathOpt(path, defaultFormatPathsOpts());
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Format multiple paths with captures and custom options.
|
|
277
|
+
*
|
|
278
|
+
* Captures come first, sorted lexicographically by name, with their name
|
|
279
|
+
* prefixed by '@'. Regular paths follow after all captures.
|
|
280
|
+
*
|
|
281
|
+
* @param paths - The paths to format
|
|
282
|
+
* @param captures - Map of capture name to captured paths
|
|
283
|
+
* @param opts - Formatting options
|
|
284
|
+
* @returns The formatted string
|
|
285
|
+
*/
|
|
286
|
+
export function formatPathsWithCapturesOpt(
|
|
287
|
+
paths: Path[],
|
|
288
|
+
captures: Map<string, Path[]>,
|
|
289
|
+
opts: FormatPathsOpts = defaultFormatPathsOpts(),
|
|
290
|
+
): string {
|
|
291
|
+
const result: string[] = [];
|
|
292
|
+
|
|
293
|
+
// First, format all captures, sorted lexicographically by name
|
|
294
|
+
const captureNames = Array.from(captures.keys()).sort();
|
|
295
|
+
|
|
296
|
+
for (const captureName of captureNames) {
|
|
297
|
+
const capturePaths = captures.get(captureName);
|
|
298
|
+
if (capturePaths === undefined) continue;
|
|
299
|
+
|
|
300
|
+
result.push(`@${captureName}`);
|
|
301
|
+
for (const path of capturePaths) {
|
|
302
|
+
const formattedPath = formatPathOpt(path, opts);
|
|
303
|
+
// Add indentation to each line of the formatted path
|
|
304
|
+
for (const line of formattedPath.split("\n")) {
|
|
305
|
+
if (line.length > 0) {
|
|
306
|
+
result.push(` ${line}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Then, format all regular paths
|
|
313
|
+
switch (opts.elementFormat.type) {
|
|
314
|
+
case "EnvelopeUR":
|
|
315
|
+
case "DigestUR": {
|
|
316
|
+
// For UR formats, join paths with spaces on same line
|
|
317
|
+
if (paths.length > 0) {
|
|
318
|
+
const formattedPaths = paths.map((path) => formatPathOpt(path, opts)).join(" ");
|
|
319
|
+
if (formattedPaths.length > 0) {
|
|
320
|
+
result.push(formattedPaths);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case "Summary": {
|
|
326
|
+
// For summary format, format each path separately
|
|
327
|
+
for (const path of paths) {
|
|
328
|
+
const formattedPath = formatPathOpt(path, opts);
|
|
329
|
+
for (const line of formattedPath.split("\n")) {
|
|
330
|
+
if (line.length > 0) {
|
|
331
|
+
result.push(line);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return result.join("\n");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Format multiple paths with captures using default options.
|
|
344
|
+
*
|
|
345
|
+
* @param paths - The paths to format
|
|
346
|
+
* @param captures - Map of capture name to captured paths
|
|
347
|
+
* @returns The formatted string
|
|
348
|
+
*/
|
|
349
|
+
export function formatPathsWithCaptures(paths: Path[], captures: Map<string, Path[]>): string {
|
|
350
|
+
return formatPathsWithCapturesOpt(paths, captures, defaultFormatPathsOpts());
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Format multiple paths with custom options.
|
|
355
|
+
*
|
|
356
|
+
* @param paths - The paths to format
|
|
357
|
+
* @param opts - Formatting options
|
|
358
|
+
* @returns The formatted string
|
|
359
|
+
*/
|
|
360
|
+
export function formatPathsOpt(
|
|
361
|
+
paths: Path[],
|
|
362
|
+
opts: FormatPathsOpts = defaultFormatPathsOpts(),
|
|
363
|
+
): string {
|
|
364
|
+
return formatPathsWithCapturesOpt(paths, new Map(), opts);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Format multiple paths with default options.
|
|
369
|
+
*
|
|
370
|
+
* @param paths - The paths to format
|
|
371
|
+
* @returns The formatted string
|
|
372
|
+
*/
|
|
373
|
+
export function formatPaths(paths: Path[]): string {
|
|
374
|
+
return formatPathsOpt(paths, defaultFormatPathsOpts());
|
|
375
|
+
}
|