@optique/core 0.9.0-dev.186 → 0.9.0-dev.187

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/index.d.cts CHANGED
@@ -2,11 +2,11 @@ import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonemp
2
2
  import { Message, MessageFormatOptions, MessageTerm, commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.cjs";
3
3
  import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.cjs";
4
4
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowDefaultOptions, formatDocPage } from "./doc.cjs";
5
- import { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.cjs";
5
+ import { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.cjs";
6
6
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
7
7
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough } from "./primitives.cjs";
8
8
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.cjs";
9
9
  import { ConditionalErrorOptions, ConditionalOptions, DuplicateOptionError, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
10
10
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.cjs";
11
11
  import { RunError, RunOptions, RunParserError, run, runParser } from "./facade.cjs";
12
- export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, RunError, RunOptions, RunParserError, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, conditional, constant, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isNonEmptyString, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, passThrough, pwsh, run, runParser, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
12
+ export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, RunError, RunOptions, RunParserError, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, conditional, constant, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isNonEmptyString, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, passThrough, pwsh, run, runParser, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
package/dist/index.d.ts CHANGED
@@ -2,11 +2,11 @@ import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonemp
2
2
  import { Message, MessageFormatOptions, MessageTerm, commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.js";
3
3
  import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
4
4
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowDefaultOptions, formatDocPage } from "./doc.js";
5
- import { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.js";
5
+ import { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.js";
6
6
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
7
7
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough } from "./primitives.js";
8
8
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.js";
9
9
  import { ConditionalErrorOptions, ConditionalOptions, DuplicateOptionError, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
10
10
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.js";
11
11
  import { RunError, RunOptions, RunParserError, run, runParser } from "./facade.js";
12
- export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, RunError, RunOptions, RunParserError, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, conditional, constant, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isNonEmptyString, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, passThrough, pwsh, run, runParser, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
12
+ export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DuplicateOptionError, FlagErrorOptions, FlagOptions, FloatOptions, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, Message, MessageFormatOptions, MessageTerm, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, PassThroughFormat, PassThroughOptions, Result, RunError, RunOptions, RunParserError, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, conditional, constant, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, isNonEmptyString, isValueParser, locale, longestMatch, map, merge, message, metavar, multiple, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, passThrough, pwsh, run, runParser, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
@@ -11,49 +11,68 @@ function isValueParser(object) {
11
11
  return typeof object === "object" && object != null && "metavar" in object && typeof object.metavar === "string" && "parse" in object && typeof object.parse === "function" && "format" in object && typeof object.format === "function";
12
12
  }
13
13
  /**
14
- * Creates a {@link ValueParser} that accepts one of multiple
15
- * string values, so-called enumerated values.
16
- *
17
- * This parser validates that the input string matches one of
18
- * the specified values. If the input does not match any of the values,
19
- * it returns an error message indicating the valid options.
20
- * @param choices An array of valid string values that this parser can accept.
21
- * @param options Configuration options for the choice parser.
22
- * @returns A {@link ValueParser} that checks if the input matches one of the
23
- * specified values.
14
+ * Implementation of the choice parser for both string and number types.
24
15
  */
25
16
  function choice(choices, options = {}) {
26
17
  const metavar = options.metavar ?? "TYPE";
27
18
  require_nonempty.ensureNonEmptyString(metavar);
28
- const normalizedValues = options.caseInsensitive ? choices.map((v) => v.toLowerCase()) : choices;
19
+ const isNumberChoice = choices.length > 0 && typeof choices[0] === "number";
20
+ if (isNumberChoice) {
21
+ const numberChoices = choices;
22
+ const numberOptions = options;
23
+ return {
24
+ metavar,
25
+ parse(input) {
26
+ const parsed = Number(input);
27
+ if (Number.isNaN(parsed)) return {
28
+ success: false,
29
+ error: formatNumberChoiceError(input, numberChoices, numberOptions)
30
+ };
31
+ const index = numberChoices.indexOf(parsed);
32
+ if (index < 0) return {
33
+ success: false,
34
+ error: formatNumberChoiceError(input, numberChoices, numberOptions)
35
+ };
36
+ return {
37
+ success: true,
38
+ value: numberChoices[index]
39
+ };
40
+ },
41
+ format(value) {
42
+ return String(value);
43
+ },
44
+ suggest(prefix) {
45
+ return numberChoices.map((value) => String(value)).filter((valueStr) => valueStr.startsWith(prefix)).map((valueStr) => ({
46
+ kind: "literal",
47
+ text: valueStr
48
+ }));
49
+ }
50
+ };
51
+ }
52
+ const stringChoices = choices;
53
+ const stringOptions = options;
54
+ const normalizedValues = stringOptions.caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
29
55
  return {
30
56
  metavar,
31
57
  parse(input) {
32
- const normalizedInput = options.caseInsensitive ? input.toLowerCase() : input;
58
+ const normalizedInput = stringOptions.caseInsensitive ? input.toLowerCase() : input;
33
59
  const index = normalizedValues.indexOf(normalizedInput);
34
- if (index < 0) {
35
- let choicesList = [];
36
- for (let i = 0; i < choices.length; i++) {
37
- if (i > 0) choicesList = [...choicesList, ...require_message.message`, `];
38
- choicesList = [...choicesList, ...require_message.message`${choices[i]}`];
39
- }
40
- return {
41
- success: false,
42
- error: options.errors?.invalidChoice ? typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice : require_message.message`Expected one of ${choicesList}, but got ${input}.`
43
- };
44
- }
60
+ if (index < 0) return {
61
+ success: false,
62
+ error: formatStringChoiceError(input, stringChoices, stringOptions)
63
+ };
45
64
  return {
46
65
  success: true,
47
- value: choices[index]
66
+ value: stringChoices[index]
48
67
  };
49
68
  },
50
69
  format(value) {
51
- return value;
70
+ return String(value);
52
71
  },
53
72
  suggest(prefix) {
54
- const normalizedPrefix = options.caseInsensitive ? prefix.toLowerCase() : prefix;
55
- return choices.filter((value) => {
56
- const normalizedValue = options.caseInsensitive ? value.toLowerCase() : value;
73
+ const normalizedPrefix = stringOptions.caseInsensitive ? prefix.toLowerCase() : prefix;
74
+ return stringChoices.filter((value) => {
75
+ const normalizedValue = stringOptions.caseInsensitive ? value.toLowerCase() : value;
57
76
  return normalizedValue.startsWith(normalizedPrefix);
58
77
  }).map((value) => ({
59
78
  kind: "literal",
@@ -63,6 +82,31 @@ function choice(choices, options = {}) {
63
82
  };
64
83
  }
65
84
  /**
85
+ * Formats error message for string choice parser.
86
+ */
87
+ function formatStringChoiceError(input, choices, options) {
88
+ if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice;
89
+ return formatDefaultChoiceError(input, choices);
90
+ }
91
+ /**
92
+ * Formats error message for number choice parser.
93
+ */
94
+ function formatNumberChoiceError(input, choices, options) {
95
+ if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice;
96
+ return formatDefaultChoiceError(input, choices);
97
+ }
98
+ /**
99
+ * Formats default error message for choice parser.
100
+ */
101
+ function formatDefaultChoiceError(input, choices) {
102
+ let choicesList = [];
103
+ for (let i = 0; i < choices.length; i++) {
104
+ if (i > 0) choicesList = [...choicesList, ...require_message.message`, `];
105
+ choicesList = [...choicesList, ...require_message.message`${String(choices[i])}`];
106
+ }
107
+ return require_message.message`Expected one of ${choicesList}, but got ${input}.`;
108
+ }
109
+ /**
66
110
  * Creates a {@link ValueParser} for strings.
67
111
  *
68
112
  * This parser validates that the input is a string and optionally checks
@@ -103,9 +103,10 @@ interface StringOptions {
103
103
  };
104
104
  }
105
105
  /**
106
- * Options for creating a {@link choice} parser.
106
+ * Base options for creating a {@link choice} parser.
107
+ * @since 0.9.0
107
108
  */
108
- interface ChoiceOptions {
109
+ interface ChoiceOptionsBase {
109
110
  /**
110
111
  * The metavariable name for this parser. This is used in help messages to
111
112
  * indicate what kind of value this parser expects. Usually a single
@@ -113,6 +114,12 @@ interface ChoiceOptions {
113
114
  * @default `"TYPE"`
114
115
  */
115
116
  readonly metavar?: NonEmptyString;
117
+ }
118
+ /**
119
+ * Options for creating a {@link choice} parser with string values.
120
+ * @since 0.9.0
121
+ */
122
+ interface ChoiceOptionsString extends ChoiceOptionsBase {
116
123
  /**
117
124
  * If `true`, the parser will perform case-insensitive matching
118
125
  * against the enumerated values. This means that input like "value",
@@ -134,6 +141,31 @@ interface ChoiceOptions {
134
141
  invalidChoice?: Message | ((input: string, choices: readonly string[]) => Message);
135
142
  };
136
143
  }
144
+ /**
145
+ * Options for creating a {@link choice} parser with number values.
146
+ * Note: `caseInsensitive` is not available for number choices.
147
+ * @since 0.9.0
148
+ */
149
+ interface ChoiceOptionsNumber extends ChoiceOptionsBase {
150
+ /**
151
+ * Custom error messages for choice parsing failures.
152
+ * @since 0.9.0
153
+ */
154
+ readonly errors?: {
155
+ /**
156
+ * Custom error message when input doesn't match any of the valid choices.
157
+ * Can be a static message or a function that receives the input and valid choices.
158
+ * @since 0.9.0
159
+ */
160
+ invalidChoice?: Message | ((input: string, choices: readonly number[]) => Message);
161
+ };
162
+ }
163
+ /**
164
+ * Options for creating a {@link choice} parser.
165
+ * @deprecated Use {@link ChoiceOptionsString} for string choices or
166
+ * {@link ChoiceOptionsNumber} for number choices.
167
+ */
168
+ type ChoiceOptions = ChoiceOptionsString;
137
169
  /**
138
170
  * A predicate function that checks if an object is a {@link ValueParser}.
139
171
  * @param object The object to check.
@@ -152,7 +184,21 @@ declare function isValueParser<T>(object: unknown): object is ValueParser<T>;
152
184
  * @returns A {@link ValueParser} that checks if the input matches one of the
153
185
  * specified values.
154
186
  */
155
- declare function choice<const T extends string>(choices: readonly T[], options?: ChoiceOptions): ValueParser<T>;
187
+ declare function choice<const T extends string>(choices: readonly T[], options?: ChoiceOptionsString): ValueParser<T>;
188
+ /**
189
+ * Creates a {@link ValueParser} that accepts one of multiple
190
+ * number values.
191
+ *
192
+ * This parser validates that the input can be parsed as a number and matches
193
+ * one of the specified values. If the input does not match any of the values,
194
+ * it returns an error message indicating the valid options.
195
+ * @param choices An array of valid number values that this parser can accept.
196
+ * @param options Configuration options for the choice parser.
197
+ * @returns A {@link ValueParser} that checks if the input matches one of the
198
+ * specified values.
199
+ * @since 0.9.0
200
+ */
201
+ declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<T>;
156
202
  /**
157
203
  * Creates a {@link ValueParser} for strings.
158
204
  *
@@ -497,4 +543,4 @@ interface UuidOptions {
497
543
  */
498
544
  declare function uuid(options?: UuidOptions): ValueParser<Uuid>;
499
545
  //#endregion
500
- export { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, type NonEmptyString, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, ensureNonEmptyString, float, integer, isNonEmptyString, isValueParser, locale, string, url, uuid };
546
+ export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, type NonEmptyString, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, ensureNonEmptyString, float, integer, isNonEmptyString, isValueParser, locale, string, url, uuid };
@@ -103,9 +103,10 @@ interface StringOptions {
103
103
  };
104
104
  }
105
105
  /**
106
- * Options for creating a {@link choice} parser.
106
+ * Base options for creating a {@link choice} parser.
107
+ * @since 0.9.0
107
108
  */
108
- interface ChoiceOptions {
109
+ interface ChoiceOptionsBase {
109
110
  /**
110
111
  * The metavariable name for this parser. This is used in help messages to
111
112
  * indicate what kind of value this parser expects. Usually a single
@@ -113,6 +114,12 @@ interface ChoiceOptions {
113
114
  * @default `"TYPE"`
114
115
  */
115
116
  readonly metavar?: NonEmptyString;
117
+ }
118
+ /**
119
+ * Options for creating a {@link choice} parser with string values.
120
+ * @since 0.9.0
121
+ */
122
+ interface ChoiceOptionsString extends ChoiceOptionsBase {
116
123
  /**
117
124
  * If `true`, the parser will perform case-insensitive matching
118
125
  * against the enumerated values. This means that input like "value",
@@ -134,6 +141,31 @@ interface ChoiceOptions {
134
141
  invalidChoice?: Message | ((input: string, choices: readonly string[]) => Message);
135
142
  };
136
143
  }
144
+ /**
145
+ * Options for creating a {@link choice} parser with number values.
146
+ * Note: `caseInsensitive` is not available for number choices.
147
+ * @since 0.9.0
148
+ */
149
+ interface ChoiceOptionsNumber extends ChoiceOptionsBase {
150
+ /**
151
+ * Custom error messages for choice parsing failures.
152
+ * @since 0.9.0
153
+ */
154
+ readonly errors?: {
155
+ /**
156
+ * Custom error message when input doesn't match any of the valid choices.
157
+ * Can be a static message or a function that receives the input and valid choices.
158
+ * @since 0.9.0
159
+ */
160
+ invalidChoice?: Message | ((input: string, choices: readonly number[]) => Message);
161
+ };
162
+ }
163
+ /**
164
+ * Options for creating a {@link choice} parser.
165
+ * @deprecated Use {@link ChoiceOptionsString} for string choices or
166
+ * {@link ChoiceOptionsNumber} for number choices.
167
+ */
168
+ type ChoiceOptions = ChoiceOptionsString;
137
169
  /**
138
170
  * A predicate function that checks if an object is a {@link ValueParser}.
139
171
  * @param object The object to check.
@@ -152,7 +184,21 @@ declare function isValueParser<T>(object: unknown): object is ValueParser<T>;
152
184
  * @returns A {@link ValueParser} that checks if the input matches one of the
153
185
  * specified values.
154
186
  */
155
- declare function choice<const T extends string>(choices: readonly T[], options?: ChoiceOptions): ValueParser<T>;
187
+ declare function choice<const T extends string>(choices: readonly T[], options?: ChoiceOptionsString): ValueParser<T>;
188
+ /**
189
+ * Creates a {@link ValueParser} that accepts one of multiple
190
+ * number values.
191
+ *
192
+ * This parser validates that the input can be parsed as a number and matches
193
+ * one of the specified values. If the input does not match any of the values,
194
+ * it returns an error message indicating the valid options.
195
+ * @param choices An array of valid number values that this parser can accept.
196
+ * @param options Configuration options for the choice parser.
197
+ * @returns A {@link ValueParser} that checks if the input matches one of the
198
+ * specified values.
199
+ * @since 0.9.0
200
+ */
201
+ declare function choice<const T extends number>(choices: readonly T[], options?: ChoiceOptionsNumber): ValueParser<T>;
156
202
  /**
157
203
  * Creates a {@link ValueParser} for strings.
158
204
  *
@@ -497,4 +543,4 @@ interface UuidOptions {
497
543
  */
498
544
  declare function uuid(options?: UuidOptions): ValueParser<Uuid>;
499
545
  //#endregion
500
- export { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, type NonEmptyString, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, ensureNonEmptyString, float, integer, isNonEmptyString, isValueParser, locale, string, url, uuid };
546
+ export { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, type NonEmptyString, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, ensureNonEmptyString, float, integer, isNonEmptyString, isValueParser, locale, string, url, uuid };
@@ -11,49 +11,68 @@ function isValueParser(object) {
11
11
  return typeof object === "object" && object != null && "metavar" in object && typeof object.metavar === "string" && "parse" in object && typeof object.parse === "function" && "format" in object && typeof object.format === "function";
12
12
  }
13
13
  /**
14
- * Creates a {@link ValueParser} that accepts one of multiple
15
- * string values, so-called enumerated values.
16
- *
17
- * This parser validates that the input string matches one of
18
- * the specified values. If the input does not match any of the values,
19
- * it returns an error message indicating the valid options.
20
- * @param choices An array of valid string values that this parser can accept.
21
- * @param options Configuration options for the choice parser.
22
- * @returns A {@link ValueParser} that checks if the input matches one of the
23
- * specified values.
14
+ * Implementation of the choice parser for both string and number types.
24
15
  */
25
16
  function choice(choices, options = {}) {
26
17
  const metavar = options.metavar ?? "TYPE";
27
18
  ensureNonEmptyString(metavar);
28
- const normalizedValues = options.caseInsensitive ? choices.map((v) => v.toLowerCase()) : choices;
19
+ const isNumberChoice = choices.length > 0 && typeof choices[0] === "number";
20
+ if (isNumberChoice) {
21
+ const numberChoices = choices;
22
+ const numberOptions = options;
23
+ return {
24
+ metavar,
25
+ parse(input) {
26
+ const parsed = Number(input);
27
+ if (Number.isNaN(parsed)) return {
28
+ success: false,
29
+ error: formatNumberChoiceError(input, numberChoices, numberOptions)
30
+ };
31
+ const index = numberChoices.indexOf(parsed);
32
+ if (index < 0) return {
33
+ success: false,
34
+ error: formatNumberChoiceError(input, numberChoices, numberOptions)
35
+ };
36
+ return {
37
+ success: true,
38
+ value: numberChoices[index]
39
+ };
40
+ },
41
+ format(value) {
42
+ return String(value);
43
+ },
44
+ suggest(prefix) {
45
+ return numberChoices.map((value) => String(value)).filter((valueStr) => valueStr.startsWith(prefix)).map((valueStr) => ({
46
+ kind: "literal",
47
+ text: valueStr
48
+ }));
49
+ }
50
+ };
51
+ }
52
+ const stringChoices = choices;
53
+ const stringOptions = options;
54
+ const normalizedValues = stringOptions.caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
29
55
  return {
30
56
  metavar,
31
57
  parse(input) {
32
- const normalizedInput = options.caseInsensitive ? input.toLowerCase() : input;
58
+ const normalizedInput = stringOptions.caseInsensitive ? input.toLowerCase() : input;
33
59
  const index = normalizedValues.indexOf(normalizedInput);
34
- if (index < 0) {
35
- let choicesList = [];
36
- for (let i = 0; i < choices.length; i++) {
37
- if (i > 0) choicesList = [...choicesList, ...message`, `];
38
- choicesList = [...choicesList, ...message`${choices[i]}`];
39
- }
40
- return {
41
- success: false,
42
- error: options.errors?.invalidChoice ? typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice : message`Expected one of ${choicesList}, but got ${input}.`
43
- };
44
- }
60
+ if (index < 0) return {
61
+ success: false,
62
+ error: formatStringChoiceError(input, stringChoices, stringOptions)
63
+ };
45
64
  return {
46
65
  success: true,
47
- value: choices[index]
66
+ value: stringChoices[index]
48
67
  };
49
68
  },
50
69
  format(value) {
51
- return value;
70
+ return String(value);
52
71
  },
53
72
  suggest(prefix) {
54
- const normalizedPrefix = options.caseInsensitive ? prefix.toLowerCase() : prefix;
55
- return choices.filter((value) => {
56
- const normalizedValue = options.caseInsensitive ? value.toLowerCase() : value;
73
+ const normalizedPrefix = stringOptions.caseInsensitive ? prefix.toLowerCase() : prefix;
74
+ return stringChoices.filter((value) => {
75
+ const normalizedValue = stringOptions.caseInsensitive ? value.toLowerCase() : value;
57
76
  return normalizedValue.startsWith(normalizedPrefix);
58
77
  }).map((value) => ({
59
78
  kind: "literal",
@@ -63,6 +82,31 @@ function choice(choices, options = {}) {
63
82
  };
64
83
  }
65
84
  /**
85
+ * Formats error message for string choice parser.
86
+ */
87
+ function formatStringChoiceError(input, choices, options) {
88
+ if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice;
89
+ return formatDefaultChoiceError(input, choices);
90
+ }
91
+ /**
92
+ * Formats error message for number choice parser.
93
+ */
94
+ function formatNumberChoiceError(input, choices, options) {
95
+ if (options.errors?.invalidChoice) return typeof options.errors.invalidChoice === "function" ? options.errors.invalidChoice(input, choices) : options.errors.invalidChoice;
96
+ return formatDefaultChoiceError(input, choices);
97
+ }
98
+ /**
99
+ * Formats default error message for choice parser.
100
+ */
101
+ function formatDefaultChoiceError(input, choices) {
102
+ let choicesList = [];
103
+ for (let i = 0; i < choices.length; i++) {
104
+ if (i > 0) choicesList = [...choicesList, ...message`, `];
105
+ choicesList = [...choicesList, ...message`${String(choices[i])}`];
106
+ }
107
+ return message`Expected one of ${choicesList}, but got ${input}.`;
108
+ }
109
+ /**
66
110
  * Creates a {@link ValueParser} for strings.
67
111
  *
68
112
  * This parser validates that the input is a string and optionally checks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "0.9.0-dev.186+976c1c1f",
3
+ "version": "0.9.0-dev.187+992128e2",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",