@optique/core 0.8.0-dev.164 → 0.8.0-dev.166
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/dist/constructs.cjs +108 -184
- package/dist/constructs.d.cts +10 -1
- package/dist/constructs.d.ts +10 -1
- package/dist/constructs.js +109 -186
- package/dist/index.cjs +2 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/modifiers.cjs +41 -58
- package/dist/modifiers.js +41 -58
- package/dist/parser.cjs +15 -9
- package/dist/parser.d.cts +3 -3
- package/dist/parser.d.ts +3 -3
- package/dist/parser.js +16 -12
- package/dist/primitives.cjs +167 -1
- package/dist/primitives.d.cts +78 -1
- package/dist/primitives.d.ts +78 -1
- package/dist/primitives.js +166 -1
- package/dist/suggestion.cjs +50 -0
- package/dist/suggestion.js +50 -1
- package/dist/usage.cjs +7 -1
- package/dist/usage.d.cts +10 -0
- package/dist/usage.d.ts +10 -0
- package/dist/usage.js +7 -1
- package/dist/valueparser.cjs +1 -1
- package/dist/valueparser.js +1 -1
- package/package.json +1 -1
package/dist/primitives.js
CHANGED
|
@@ -723,6 +723,171 @@ function command(name, parser, options = {}) {
|
|
|
723
723
|
}
|
|
724
724
|
};
|
|
725
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* Creates a parser that collects unrecognized options and passes them through.
|
|
728
|
+
* This is useful for building wrapper CLI tools that need to forward unknown
|
|
729
|
+
* options to an underlying tool.
|
|
730
|
+
*
|
|
731
|
+
* **Important**: This parser intentionally weakens Optique's strict parsing
|
|
732
|
+
* philosophy where "all input must be recognized." The benefit is enabling
|
|
733
|
+
* legitimate wrapper/proxy tool patterns, but the trade-off is that typos
|
|
734
|
+
* in pass-through options won't be caught.
|
|
735
|
+
*
|
|
736
|
+
* @param options Configuration for how to capture options.
|
|
737
|
+
* @returns A {@link Parser} that captures unrecognized options as an array
|
|
738
|
+
* of strings.
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* // Default format: only captures --opt=val
|
|
743
|
+
* const parser = object({
|
|
744
|
+
* debug: option("--debug"),
|
|
745
|
+
* extra: passThrough(),
|
|
746
|
+
* });
|
|
747
|
+
*
|
|
748
|
+
* // mycli --debug --foo=bar --baz=qux
|
|
749
|
+
* // → { debug: true, extra: ["--foo=bar", "--baz=qux"] }
|
|
750
|
+
*
|
|
751
|
+
* // nextToken format: captures --opt val pairs
|
|
752
|
+
* const parser = object({
|
|
753
|
+
* debug: option("--debug"),
|
|
754
|
+
* extra: passThrough({ format: "nextToken" }),
|
|
755
|
+
* });
|
|
756
|
+
*
|
|
757
|
+
* // mycli --debug --foo bar
|
|
758
|
+
* // → { debug: true, extra: ["--foo", "bar"] }
|
|
759
|
+
*
|
|
760
|
+
* // greedy format: captures all remaining tokens
|
|
761
|
+
* const parser = command("exec", object({
|
|
762
|
+
* container: argument(string()),
|
|
763
|
+
* args: passThrough({ format: "greedy" }),
|
|
764
|
+
* }));
|
|
765
|
+
*
|
|
766
|
+
* // myproxy exec mycontainer --verbose -it bash
|
|
767
|
+
* // → { container: "mycontainer", args: ["--verbose", "-it", "bash"] }
|
|
768
|
+
* ```
|
|
769
|
+
*
|
|
770
|
+
* @since 0.8.0
|
|
771
|
+
*/
|
|
772
|
+
function passThrough(options = {}) {
|
|
773
|
+
const format = options.format ?? "equalsOnly";
|
|
774
|
+
const optionPattern = /^-[a-z0-9-]|^--[a-z0-9-]+/i;
|
|
775
|
+
const equalsOptionPattern = /^--[a-z0-9-]+=/i;
|
|
776
|
+
return {
|
|
777
|
+
$valueType: [],
|
|
778
|
+
$stateType: [],
|
|
779
|
+
priority: -10,
|
|
780
|
+
usage: [{ type: "passthrough" }],
|
|
781
|
+
initialState: [],
|
|
782
|
+
parse(context) {
|
|
783
|
+
if (context.buffer.length < 1) return {
|
|
784
|
+
success: false,
|
|
785
|
+
consumed: 0,
|
|
786
|
+
error: message`No input to pass through.`
|
|
787
|
+
};
|
|
788
|
+
const token = context.buffer[0];
|
|
789
|
+
if (format === "greedy") {
|
|
790
|
+
const captured = [...context.buffer];
|
|
791
|
+
return {
|
|
792
|
+
success: true,
|
|
793
|
+
next: {
|
|
794
|
+
...context,
|
|
795
|
+
buffer: [],
|
|
796
|
+
state: [...context.state, ...captured]
|
|
797
|
+
},
|
|
798
|
+
consumed: captured
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
if (context.optionsTerminated) return {
|
|
802
|
+
success: false,
|
|
803
|
+
consumed: 0,
|
|
804
|
+
error: message`Options terminated; cannot capture pass-through options.`
|
|
805
|
+
};
|
|
806
|
+
if (format === "equalsOnly") {
|
|
807
|
+
if (!equalsOptionPattern.test(token)) return {
|
|
808
|
+
success: false,
|
|
809
|
+
consumed: 0,
|
|
810
|
+
error: message`Expected --option=value format, but got: ${token}.`
|
|
811
|
+
};
|
|
812
|
+
return {
|
|
813
|
+
success: true,
|
|
814
|
+
next: {
|
|
815
|
+
...context,
|
|
816
|
+
buffer: context.buffer.slice(1),
|
|
817
|
+
state: [...context.state, token]
|
|
818
|
+
},
|
|
819
|
+
consumed: [token]
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
if (format === "nextToken") {
|
|
823
|
+
if (!optionPattern.test(token)) return {
|
|
824
|
+
success: false,
|
|
825
|
+
consumed: 0,
|
|
826
|
+
error: message`Expected option, but got: ${token}.`
|
|
827
|
+
};
|
|
828
|
+
if (token.includes("=")) return {
|
|
829
|
+
success: true,
|
|
830
|
+
next: {
|
|
831
|
+
...context,
|
|
832
|
+
buffer: context.buffer.slice(1),
|
|
833
|
+
state: [...context.state, token]
|
|
834
|
+
},
|
|
835
|
+
consumed: [token]
|
|
836
|
+
};
|
|
837
|
+
const nextToken = context.buffer[1];
|
|
838
|
+
if (nextToken !== void 0 && !optionPattern.test(nextToken)) return {
|
|
839
|
+
success: true,
|
|
840
|
+
next: {
|
|
841
|
+
...context,
|
|
842
|
+
buffer: context.buffer.slice(2),
|
|
843
|
+
state: [
|
|
844
|
+
...context.state,
|
|
845
|
+
token,
|
|
846
|
+
nextToken
|
|
847
|
+
]
|
|
848
|
+
},
|
|
849
|
+
consumed: [token, nextToken]
|
|
850
|
+
};
|
|
851
|
+
return {
|
|
852
|
+
success: true,
|
|
853
|
+
next: {
|
|
854
|
+
...context,
|
|
855
|
+
buffer: context.buffer.slice(1),
|
|
856
|
+
state: [...context.state, token]
|
|
857
|
+
},
|
|
858
|
+
consumed: [token]
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
success: false,
|
|
863
|
+
consumed: 0,
|
|
864
|
+
error: message`Unknown passThrough format: ${format}.`
|
|
865
|
+
};
|
|
866
|
+
},
|
|
867
|
+
complete(state) {
|
|
868
|
+
return {
|
|
869
|
+
success: true,
|
|
870
|
+
value: state
|
|
871
|
+
};
|
|
872
|
+
},
|
|
873
|
+
suggest(_context, _prefix) {
|
|
874
|
+
return [];
|
|
875
|
+
},
|
|
876
|
+
getDocFragments(_state, _defaultValue) {
|
|
877
|
+
return {
|
|
878
|
+
fragments: [{
|
|
879
|
+
type: "entry",
|
|
880
|
+
term: { type: "passthrough" },
|
|
881
|
+
description: options.description
|
|
882
|
+
}],
|
|
883
|
+
description: options.description
|
|
884
|
+
};
|
|
885
|
+
},
|
|
886
|
+
[Symbol.for("Deno.customInspect")]() {
|
|
887
|
+
return `passThrough(${format})`;
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
}
|
|
726
891
|
|
|
727
892
|
//#endregion
|
|
728
|
-
export { argument, command, constant, flag, option };
|
|
893
|
+
export { argument, command, constant, flag, option, passThrough };
|
package/dist/suggestion.cjs
CHANGED
|
@@ -179,8 +179,58 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
|
|
|
179
179
|
...suggestionMsg
|
|
180
180
|
] : baseError;
|
|
181
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates a unique key for a suggestion to enable deduplication.
|
|
184
|
+
*
|
|
185
|
+
* For literal suggestions, the text itself is used as the key.
|
|
186
|
+
* For file suggestions, a composite key is created from the type,
|
|
187
|
+
* extensions, and pattern.
|
|
188
|
+
*
|
|
189
|
+
* @param suggestion The suggestion to create a key for
|
|
190
|
+
* @returns A string key that uniquely identifies this suggestion
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function getSuggestionKey(suggestion) {
|
|
194
|
+
if (suggestion.kind === "literal") return suggestion.text;
|
|
195
|
+
return `__FILE__:${suggestion.type}:${suggestion.extensions?.join(",") ?? ""}:${suggestion.pattern ?? ""}`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Removes duplicate suggestions from an array while preserving order.
|
|
199
|
+
*
|
|
200
|
+
* Suggestions are considered duplicates if they have the same key:
|
|
201
|
+
* - Literal suggestions: same text
|
|
202
|
+
* - File suggestions: same type, extensions, and pattern
|
|
203
|
+
*
|
|
204
|
+
* The first occurrence of each unique suggestion is kept.
|
|
205
|
+
*
|
|
206
|
+
* @param suggestions Array of suggestions that may contain duplicates
|
|
207
|
+
* @returns A new array with duplicates removed, preserving order of first occurrences
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const suggestions = [
|
|
212
|
+
* { kind: "literal", text: "--verbose" },
|
|
213
|
+
* { kind: "literal", text: "--help" },
|
|
214
|
+
* { kind: "literal", text: "--verbose" }, // duplicate
|
|
215
|
+
* ];
|
|
216
|
+
* deduplicateSuggestions(suggestions);
|
|
217
|
+
* // returns [{ kind: "literal", text: "--verbose" }, { kind: "literal", text: "--help" }]
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* @since 0.9.0
|
|
221
|
+
*/
|
|
222
|
+
function deduplicateSuggestions(suggestions) {
|
|
223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
224
|
+
return suggestions.filter((suggestion) => {
|
|
225
|
+
const key = getSuggestionKey(suggestion);
|
|
226
|
+
if (seen.has(key)) return false;
|
|
227
|
+
seen.add(key);
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
182
231
|
|
|
183
232
|
//#endregion
|
|
184
233
|
exports.DEFAULT_FIND_SIMILAR_OPTIONS = DEFAULT_FIND_SIMILAR_OPTIONS;
|
|
185
234
|
exports.createErrorWithSuggestions = createErrorWithSuggestions;
|
|
235
|
+
exports.deduplicateSuggestions = deduplicateSuggestions;
|
|
186
236
|
exports.findSimilar = findSimilar;
|
package/dist/suggestion.js
CHANGED
|
@@ -179,6 +179,55 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
|
|
|
179
179
|
...suggestionMsg
|
|
180
180
|
] : baseError;
|
|
181
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Creates a unique key for a suggestion to enable deduplication.
|
|
184
|
+
*
|
|
185
|
+
* For literal suggestions, the text itself is used as the key.
|
|
186
|
+
* For file suggestions, a composite key is created from the type,
|
|
187
|
+
* extensions, and pattern.
|
|
188
|
+
*
|
|
189
|
+
* @param suggestion The suggestion to create a key for
|
|
190
|
+
* @returns A string key that uniquely identifies this suggestion
|
|
191
|
+
* @internal
|
|
192
|
+
*/
|
|
193
|
+
function getSuggestionKey(suggestion) {
|
|
194
|
+
if (suggestion.kind === "literal") return suggestion.text;
|
|
195
|
+
return `__FILE__:${suggestion.type}:${suggestion.extensions?.join(",") ?? ""}:${suggestion.pattern ?? ""}`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Removes duplicate suggestions from an array while preserving order.
|
|
199
|
+
*
|
|
200
|
+
* Suggestions are considered duplicates if they have the same key:
|
|
201
|
+
* - Literal suggestions: same text
|
|
202
|
+
* - File suggestions: same type, extensions, and pattern
|
|
203
|
+
*
|
|
204
|
+
* The first occurrence of each unique suggestion is kept.
|
|
205
|
+
*
|
|
206
|
+
* @param suggestions Array of suggestions that may contain duplicates
|
|
207
|
+
* @returns A new array with duplicates removed, preserving order of first occurrences
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* const suggestions = [
|
|
212
|
+
* { kind: "literal", text: "--verbose" },
|
|
213
|
+
* { kind: "literal", text: "--help" },
|
|
214
|
+
* { kind: "literal", text: "--verbose" }, // duplicate
|
|
215
|
+
* ];
|
|
216
|
+
* deduplicateSuggestions(suggestions);
|
|
217
|
+
* // returns [{ kind: "literal", text: "--verbose" }, { kind: "literal", text: "--help" }]
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* @since 0.9.0
|
|
221
|
+
*/
|
|
222
|
+
function deduplicateSuggestions(suggestions) {
|
|
223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
224
|
+
return suggestions.filter((suggestion) => {
|
|
225
|
+
const key = getSuggestionKey(suggestion);
|
|
226
|
+
if (seen.has(key)) return false;
|
|
227
|
+
seen.add(key);
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
182
231
|
|
|
183
232
|
//#endregion
|
|
184
|
-
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, findSimilar };
|
|
233
|
+
export { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, deduplicateSuggestions, findSimilar };
|
package/dist/usage.cjs
CHANGED
|
@@ -331,7 +331,13 @@ function* formatUsageTermInternal(term, options) {
|
|
|
331
331
|
text: term.value,
|
|
332
332
|
width: term.value.length
|
|
333
333
|
};
|
|
334
|
-
else
|
|
334
|
+
else if (term.type === "passthrough") {
|
|
335
|
+
const text = "[...]";
|
|
336
|
+
yield {
|
|
337
|
+
text: options?.colors ? `\x1b[2m${text}\x1b[0m` : text,
|
|
338
|
+
width: 5
|
|
339
|
+
};
|
|
340
|
+
} else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
//#endregion
|
package/dist/usage.d.cts
CHANGED
|
@@ -123,6 +123,16 @@ type UsageTerm =
|
|
|
123
123
|
* The literal value that must be provided exactly as written.
|
|
124
124
|
*/
|
|
125
125
|
readonly value: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A pass-through term, which represents unrecognized options that are
|
|
129
|
+
* collected and passed through to an underlying tool or command.
|
|
130
|
+
* @since 0.8.0
|
|
131
|
+
*/ | {
|
|
132
|
+
/**
|
|
133
|
+
* The type of the term, which is always `"passthrough"` for this term.
|
|
134
|
+
*/
|
|
135
|
+
readonly type: "passthrough";
|
|
126
136
|
};
|
|
127
137
|
/**
|
|
128
138
|
* Represents a command-line usage description, which is a sequence of
|
package/dist/usage.d.ts
CHANGED
|
@@ -123,6 +123,16 @@ type UsageTerm =
|
|
|
123
123
|
* The literal value that must be provided exactly as written.
|
|
124
124
|
*/
|
|
125
125
|
readonly value: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* A pass-through term, which represents unrecognized options that are
|
|
129
|
+
* collected and passed through to an underlying tool or command.
|
|
130
|
+
* @since 0.8.0
|
|
131
|
+
*/ | {
|
|
132
|
+
/**
|
|
133
|
+
* The type of the term, which is always `"passthrough"` for this term.
|
|
134
|
+
*/
|
|
135
|
+
readonly type: "passthrough";
|
|
126
136
|
};
|
|
127
137
|
/**
|
|
128
138
|
* Represents a command-line usage description, which is a sequence of
|
package/dist/usage.js
CHANGED
|
@@ -330,7 +330,13 @@ function* formatUsageTermInternal(term, options) {
|
|
|
330
330
|
text: term.value,
|
|
331
331
|
width: term.value.length
|
|
332
332
|
};
|
|
333
|
-
else
|
|
333
|
+
else if (term.type === "passthrough") {
|
|
334
|
+
const text = "[...]";
|
|
335
|
+
yield {
|
|
336
|
+
text: options?.colors ? `\x1b[2m${text}\x1b[0m` : text,
|
|
337
|
+
width: 5
|
|
338
|
+
};
|
|
339
|
+
} else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
|
|
334
340
|
}
|
|
335
341
|
|
|
336
342
|
//#endregion
|
package/dist/valueparser.cjs
CHANGED
|
@@ -153,7 +153,7 @@ function integer(options) {
|
|
|
153
153
|
return {
|
|
154
154
|
metavar: options?.metavar ?? "INTEGER",
|
|
155
155
|
parse(input) {
|
|
156
|
-
if (!input.match(
|
|
156
|
+
if (!input.match(/^-?\d+$/)) return {
|
|
157
157
|
success: false,
|
|
158
158
|
error: options?.errors?.invalidInteger ? typeof options.errors.invalidInteger === "function" ? options.errors.invalidInteger(input) : options.errors.invalidInteger : require_message.message`Expected a valid integer, but got ${input}.`
|
|
159
159
|
};
|
package/dist/valueparser.js
CHANGED
|
@@ -153,7 +153,7 @@ function integer(options) {
|
|
|
153
153
|
return {
|
|
154
154
|
metavar: options?.metavar ?? "INTEGER",
|
|
155
155
|
parse(input) {
|
|
156
|
-
if (!input.match(
|
|
156
|
+
if (!input.match(/^-?\d+$/)) return {
|
|
157
157
|
success: false,
|
|
158
158
|
error: options?.errors?.invalidInteger ? typeof options.errors.invalidInteger === "function" ? options.errors.invalidInteger(input) : options.errors.invalidInteger : message`Expected a valid integer, but got ${input}.`
|
|
159
159
|
};
|