@optique/core 0.9.0-dev.185 → 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.cjs CHANGED
@@ -4,6 +4,7 @@ const require_constructs = require('./constructs.cjs');
4
4
  const require_doc = require('./doc.cjs');
5
5
  const require_completion = require('./completion.cjs');
6
6
  const require_modifiers = require('./modifiers.cjs');
7
+ const require_nonempty = require('./nonempty.cjs');
7
8
  const require_valueparser = require('./valueparser.cjs');
8
9
  const require_primitives = require('./primitives.cjs');
9
10
  const require_parser = require('./parser.cjs');
@@ -21,6 +22,7 @@ exports.commandLine = require_message.commandLine;
21
22
  exports.concat = require_constructs.concat;
22
23
  exports.conditional = require_constructs.conditional;
23
24
  exports.constant = require_primitives.constant;
25
+ exports.ensureNonEmptyString = require_nonempty.ensureNonEmptyString;
24
26
  exports.envVar = require_message.envVar;
25
27
  exports.extractArgumentMetavars = require_usage.extractArgumentMetavars;
26
28
  exports.extractCommandNames = require_usage.extractCommandNames;
@@ -35,6 +37,7 @@ exports.formatUsageTerm = require_usage.formatUsageTerm;
35
37
  exports.getDocPage = require_parser.getDocPage;
36
38
  exports.group = require_constructs.group;
37
39
  exports.integer = require_valueparser.integer;
40
+ exports.isNonEmptyString = require_nonempty.isNonEmptyString;
38
41
  exports.isValueParser = require_valueparser.isValueParser;
39
42
  exports.locale = require_valueparser.locale;
40
43
  exports.longestMatch = require_constructs.longestMatch;
package/dist/index.d.cts CHANGED
@@ -1,11 +1,12 @@
1
+ import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonempty.cjs";
1
2
  import { Message, MessageFormatOptions, MessageTerm, commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.cjs";
2
3
  import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.cjs";
3
4
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowDefaultOptions, formatDocPage } from "./doc.cjs";
4
- 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";
5
6
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
6
7
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough } from "./primitives.cjs";
7
8
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.cjs";
8
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";
9
10
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.cjs";
10
11
  import { RunError, RunOptions, RunParserError, run, runParser } from "./facade.cjs";
11
- 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, 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, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, 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
@@ -1,11 +1,12 @@
1
+ import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
1
2
  import { Message, MessageFormatOptions, MessageTerm, commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.js";
2
3
  import { OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
3
4
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowDefaultOptions, formatDocPage } from "./doc.js";
4
- 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";
5
6
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
6
7
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, PassThroughFormat, PassThroughOptions, argument, command, constant, flag, option, passThrough } from "./primitives.js";
7
8
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.js";
8
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";
9
10
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.js";
10
11
  import { RunError, RunOptions, RunParserError, run, runParser } from "./facade.js";
11
- 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, 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, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, 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.js CHANGED
@@ -4,9 +4,10 @@ import { DuplicateOptionError, concat, conditional, group, longestMatch, merge,
4
4
  import { formatDocPage } from "./doc.js";
5
5
  import { bash, fish, nu, pwsh, zsh } from "./completion.js";
6
6
  import { WithDefaultError, map, multiple, optional, withDefault } from "./modifiers.js";
7
+ import { ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
7
8
  import { choice, float, integer, isValueParser, locale, string, url, uuid } from "./valueparser.js";
8
9
  import { argument, command, constant, flag, option, passThrough } from "./primitives.js";
9
10
  import { getDocPage, parse, suggest } from "./parser.js";
10
11
  import { RunError, RunParserError, run, runParser } from "./facade.js";
11
12
 
12
- export { DuplicateOptionError, RunError, RunParserError, WithDefaultError, argument, bash, choice, command, commandLine, concat, conditional, constant, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fish, flag, float, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getDocPage, group, integer, 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 };
13
+ export { DuplicateOptionError, RunError, RunParserError, WithDefaultError, 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 };
@@ -1,4 +1,7 @@
1
+ import { NonEmptyString } from "./nonempty.cjs";
2
+
1
3
  //#region src/message.d.ts
4
+
2
5
  /**
3
6
  * Represents a single term in a message, which can be a text, an option
4
7
  * name, a list of option names, a metavariable, a value, or a list of
@@ -60,7 +63,7 @@ type MessageTerm =
60
63
  * The metavariable name, which is a string that represents
61
64
  * a variable in the message. For example, `"VALUE"` or `"ARG"`.
62
65
  */
63
- readonly metavar: string;
66
+ readonly metavar: NonEmptyString;
64
67
  }
65
68
  /**
66
69
  * A value term in the message, which can be a single value.
@@ -171,7 +174,7 @@ declare function optionNames(names: readonly string[]): MessageTerm;
171
174
  * `"ARG"`.
172
175
  * @returns A {@link MessageTerm} representing the metavariable.
173
176
  */
174
- declare function metavar(metavar: string): MessageTerm;
177
+ declare function metavar(metavar: NonEmptyString): MessageTerm;
175
178
  /**
176
179
  * Creates a {@link MessageTerm} for a single value. However, you usually
177
180
  * don't need to use this function directly, as {@link message} string template
package/dist/message.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import { NonEmptyString } from "./nonempty.js";
2
+
1
3
  //#region src/message.d.ts
4
+
2
5
  /**
3
6
  * Represents a single term in a message, which can be a text, an option
4
7
  * name, a list of option names, a metavariable, a value, or a list of
@@ -60,7 +63,7 @@ type MessageTerm =
60
63
  * The metavariable name, which is a string that represents
61
64
  * a variable in the message. For example, `"VALUE"` or `"ARG"`.
62
65
  */
63
- readonly metavar: string;
66
+ readonly metavar: NonEmptyString;
64
67
  }
65
68
  /**
66
69
  * A value term in the message, which can be a single value.
@@ -171,7 +174,7 @@ declare function optionNames(names: readonly string[]): MessageTerm;
171
174
  * `"ARG"`.
172
175
  * @returns A {@link MessageTerm} representing the metavariable.
173
176
  */
174
- declare function metavar(metavar: string): MessageTerm;
177
+ declare function metavar(metavar: NonEmptyString): MessageTerm;
175
178
  /**
176
179
  * Creates a {@link MessageTerm} for a single value. However, you usually
177
180
  * don't need to use this function directly, as {@link message} string template
@@ -0,0 +1,26 @@
1
+
2
+ //#region src/nonempty.ts
3
+ /**
4
+ * Checks if a string is non-empty.
5
+ * Can be used as a type guard for type narrowing.
6
+ * @param value The string to check.
7
+ * @returns `true` if the string is non-empty, `false` otherwise.
8
+ * @since 0.9.0
9
+ */
10
+ function isNonEmptyString(value) {
11
+ return value !== "";
12
+ }
13
+ /**
14
+ * Asserts that a string is non-empty.
15
+ * Throws a `TypeError` if the string is empty.
16
+ * @param value The string to validate.
17
+ * @throws {TypeError} If the string is empty.
18
+ * @since 0.9.0
19
+ */
20
+ function ensureNonEmptyString(value) {
21
+ if (value === "") throw new TypeError("Expected a non-empty string.");
22
+ }
23
+
24
+ //#endregion
25
+ exports.ensureNonEmptyString = ensureNonEmptyString;
26
+ exports.isNonEmptyString = isNonEmptyString;
@@ -0,0 +1,29 @@
1
+ //#region src/nonempty.d.ts
2
+ /**
3
+ * A string type that guarantees at least one character.
4
+ * Used for `metavar` properties to ensure they are never empty.
5
+ *
6
+ * This type uses a template literal pattern that requires at least one
7
+ * character, providing compile-time rejection of empty string literals.
8
+ *
9
+ * @since 0.9.0
10
+ */
11
+ type NonEmptyString = `${any}${string}`;
12
+ /**
13
+ * Checks if a string is non-empty.
14
+ * Can be used as a type guard for type narrowing.
15
+ * @param value The string to check.
16
+ * @returns `true` if the string is non-empty, `false` otherwise.
17
+ * @since 0.9.0
18
+ */
19
+ declare function isNonEmptyString(value: string): value is NonEmptyString;
20
+ /**
21
+ * Asserts that a string is non-empty.
22
+ * Throws a `TypeError` if the string is empty.
23
+ * @param value The string to validate.
24
+ * @throws {TypeError} If the string is empty.
25
+ * @since 0.9.0
26
+ */
27
+ declare function ensureNonEmptyString(value: string): asserts value is NonEmptyString;
28
+ //#endregion
29
+ export { NonEmptyString, ensureNonEmptyString, isNonEmptyString };
@@ -0,0 +1,29 @@
1
+ //#region src/nonempty.d.ts
2
+ /**
3
+ * A string type that guarantees at least one character.
4
+ * Used for `metavar` properties to ensure they are never empty.
5
+ *
6
+ * This type uses a template literal pattern that requires at least one
7
+ * character, providing compile-time rejection of empty string literals.
8
+ *
9
+ * @since 0.9.0
10
+ */
11
+ type NonEmptyString = `${any}${string}`;
12
+ /**
13
+ * Checks if a string is non-empty.
14
+ * Can be used as a type guard for type narrowing.
15
+ * @param value The string to check.
16
+ * @returns `true` if the string is non-empty, `false` otherwise.
17
+ * @since 0.9.0
18
+ */
19
+ declare function isNonEmptyString(value: string): value is NonEmptyString;
20
+ /**
21
+ * Asserts that a string is non-empty.
22
+ * Throws a `TypeError` if the string is empty.
23
+ * @param value The string to validate.
24
+ * @throws {TypeError} If the string is empty.
25
+ * @since 0.9.0
26
+ */
27
+ declare function ensureNonEmptyString(value: string): asserts value is NonEmptyString;
28
+ //#endregion
29
+ export { NonEmptyString, ensureNonEmptyString, isNonEmptyString };
@@ -0,0 +1,24 @@
1
+ //#region src/nonempty.ts
2
+ /**
3
+ * Checks if a string is non-empty.
4
+ * Can be used as a type guard for type narrowing.
5
+ * @param value The string to check.
6
+ * @returns `true` if the string is non-empty, `false` otherwise.
7
+ * @since 0.9.0
8
+ */
9
+ function isNonEmptyString(value) {
10
+ return value !== "";
11
+ }
12
+ /**
13
+ * Asserts that a string is non-empty.
14
+ * Throws a `TypeError` if the string is empty.
15
+ * @param value The string to validate.
16
+ * @throws {TypeError} If the string is empty.
17
+ * @since 0.9.0
18
+ */
19
+ function ensureNonEmptyString(value) {
20
+ if (value === "") throw new TypeError("Expected a non-empty string.");
21
+ }
22
+
23
+ //#endregion
24
+ export { ensureNonEmptyString, isNonEmptyString };
package/dist/usage.d.cts CHANGED
@@ -1,4 +1,7 @@
1
+ import { NonEmptyString } from "./nonempty.cjs";
2
+
1
3
  //#region src/usage.d.ts
4
+
2
5
  /**
3
6
  * Represents the name of a command-line option. There are four types of
4
7
  * option syntax:
@@ -26,7 +29,7 @@ type UsageTerm =
26
29
  * The name of the argument, which is used to identify it in
27
30
  * the command-line usage.
28
31
  */
29
- readonly metavar: string;
32
+ readonly metavar: NonEmptyString;
30
33
  /**
31
34
  * When `true`, hides the argument from help text, shell completion
32
35
  * suggestions, and error suggestions.
@@ -51,7 +54,7 @@ type UsageTerm =
51
54
  * An optional metavariable name for the option, which is used
52
55
  * to indicate what value the option expects.
53
56
  */
54
- readonly metavar?: string;
57
+ readonly metavar?: NonEmptyString;
55
58
  /**
56
59
  * When `true`, hides the option from help text, shell completion
57
60
  * suggestions, and "Did you mean?" error suggestions.
package/dist/usage.d.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import { NonEmptyString } from "./nonempty.js";
2
+
1
3
  //#region src/usage.d.ts
4
+
2
5
  /**
3
6
  * Represents the name of a command-line option. There are four types of
4
7
  * option syntax:
@@ -26,7 +29,7 @@ type UsageTerm =
26
29
  * The name of the argument, which is used to identify it in
27
30
  * the command-line usage.
28
31
  */
29
- readonly metavar: string;
32
+ readonly metavar: NonEmptyString;
30
33
  /**
31
34
  * When `true`, hides the argument from help text, shell completion
32
35
  * suggestions, and error suggestions.
@@ -51,7 +54,7 @@ type UsageTerm =
51
54
  * An optional metavariable name for the option, which is used
52
55
  * to indicate what value the option expects.
53
56
  */
54
- readonly metavar?: string;
57
+ readonly metavar?: NonEmptyString;
55
58
  /**
56
59
  * When `true`, hides the option from help text, shell completion
57
60
  * suggestions, and "Did you mean?" error suggestions.
@@ -1,4 +1,5 @@
1
1
  const require_message = require('./message.cjs');
2
+ const require_nonempty = require('./nonempty.cjs');
2
3
 
3
4
  //#region src/valueparser.ts
4
5
  /**
@@ -10,47 +11,68 @@ function isValueParser(object) {
10
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";
11
12
  }
12
13
  /**
13
- * Creates a {@link ValueParser} that accepts one of multiple
14
- * string values, so-called enumerated values.
15
- *
16
- * This parser validates that the input string matches one of
17
- * the specified values. If the input does not match any of the values,
18
- * it returns an error message indicating the valid options.
19
- * @param choices An array of valid string values that this parser can accept.
20
- * @param options Configuration options for the choice parser.
21
- * @returns A {@link ValueParser} that checks if the input matches one of the
22
- * specified values.
14
+ * Implementation of the choice parser for both string and number types.
23
15
  */
24
16
  function choice(choices, options = {}) {
25
- const normalizedValues = options.caseInsensitive ? choices.map((v) => v.toLowerCase()) : choices;
26
- return {
27
- metavar: options.metavar ?? "TYPE",
28
- parse(input) {
29
- const normalizedInput = options.caseInsensitive ? input.toLowerCase() : input;
30
- const index = normalizedValues.indexOf(normalizedInput);
31
- if (index < 0) {
32
- let choicesList = [];
33
- for (let i = 0; i < choices.length; i++) {
34
- if (i > 0) choicesList = [...choicesList, ...require_message.message`, `];
35
- choicesList = [...choicesList, ...require_message.message`${choices[i]}`];
36
- }
37
- return {
17
+ const metavar = options.metavar ?? "TYPE";
18
+ require_nonempty.ensureNonEmptyString(metavar);
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 {
38
33
  success: false,
39
- 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}.`
34
+ error: formatNumberChoiceError(input, numberChoices, numberOptions)
35
+ };
36
+ return {
37
+ success: true,
38
+ value: numberChoices[index]
40
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
+ }));
41
49
  }
50
+ };
51
+ }
52
+ const stringChoices = choices;
53
+ const stringOptions = options;
54
+ const normalizedValues = stringOptions.caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
55
+ return {
56
+ metavar,
57
+ parse(input) {
58
+ const normalizedInput = stringOptions.caseInsensitive ? input.toLowerCase() : input;
59
+ const index = normalizedValues.indexOf(normalizedInput);
60
+ if (index < 0) return {
61
+ success: false,
62
+ error: formatStringChoiceError(input, stringChoices, stringOptions)
63
+ };
42
64
  return {
43
65
  success: true,
44
- value: choices[index]
66
+ value: stringChoices[index]
45
67
  };
46
68
  },
47
69
  format(value) {
48
- return value;
70
+ return String(value);
49
71
  },
50
72
  suggest(prefix) {
51
- const normalizedPrefix = options.caseInsensitive ? prefix.toLowerCase() : prefix;
52
- return choices.filter((value) => {
53
- 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;
54
76
  return normalizedValue.startsWith(normalizedPrefix);
55
77
  }).map((value) => ({
56
78
  kind: "literal",
@@ -60,6 +82,31 @@ function choice(choices, options = {}) {
60
82
  };
61
83
  }
62
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
+ /**
63
110
  * Creates a {@link ValueParser} for strings.
64
111
  *
65
112
  * This parser validates that the input is a string and optionally checks
@@ -74,8 +121,10 @@ function choice(choices, options = {}) {
74
121
  * specified options.
75
122
  */
76
123
  function string(options = {}) {
124
+ const metavar = options.metavar ?? "STRING";
125
+ require_nonempty.ensureNonEmptyString(metavar);
77
126
  return {
78
- metavar: options.metavar ?? "STRING",
127
+ metavar,
79
128
  parse(input) {
80
129
  if (options.pattern != null && !options.pattern.test(input)) return {
81
130
  success: false,
@@ -125,38 +174,44 @@ function string(options = {}) {
125
174
  * integer type.
126
175
  */
127
176
  function integer(options) {
128
- if (options?.type === "bigint") return {
129
- metavar: options.metavar ?? "INTEGER",
130
- parse(input) {
131
- let value;
132
- try {
133
- value = BigInt(input);
134
- } catch (e) {
135
- if (e instanceof SyntaxError) return {
177
+ if (options?.type === "bigint") {
178
+ const metavar$1 = options.metavar ?? "INTEGER";
179
+ require_nonempty.ensureNonEmptyString(metavar$1);
180
+ return {
181
+ metavar: metavar$1,
182
+ parse(input) {
183
+ let value;
184
+ try {
185
+ value = BigInt(input);
186
+ } catch (e) {
187
+ if (e instanceof SyntaxError) return {
188
+ success: false,
189
+ 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}.`
190
+ };
191
+ throw e;
192
+ }
193
+ if (options.min != null && value < options.min) return {
136
194
  success: false,
137
- 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}.`
195
+ error: options.errors?.belowMinimum ? typeof options.errors.belowMinimum === "function" ? options.errors.belowMinimum(value, options.min) : options.errors.belowMinimum : require_message.message`Expected a value greater than or equal to ${require_message.text(options.min.toLocaleString("en"))}, but got ${input}.`
138
196
  };
139
- throw e;
197
+ else if (options.max != null && value > options.max) return {
198
+ success: false,
199
+ error: options.errors?.aboveMaximum ? typeof options.errors.aboveMaximum === "function" ? options.errors.aboveMaximum(value, options.max) : options.errors.aboveMaximum : require_message.message`Expected a value less than or equal to ${require_message.text(options.max.toLocaleString("en"))}, but got ${input}.`
200
+ };
201
+ return {
202
+ success: true,
203
+ value
204
+ };
205
+ },
206
+ format(value) {
207
+ return value.toString();
140
208
  }
141
- if (options.min != null && value < options.min) return {
142
- success: false,
143
- error: options.errors?.belowMinimum ? typeof options.errors.belowMinimum === "function" ? options.errors.belowMinimum(value, options.min) : options.errors.belowMinimum : require_message.message`Expected a value greater than or equal to ${require_message.text(options.min.toLocaleString("en"))}, but got ${input}.`
144
- };
145
- else if (options.max != null && value > options.max) return {
146
- success: false,
147
- error: options.errors?.aboveMaximum ? typeof options.errors.aboveMaximum === "function" ? options.errors.aboveMaximum(value, options.max) : options.errors.aboveMaximum : require_message.message`Expected a value less than or equal to ${require_message.text(options.max.toLocaleString("en"))}, but got ${input}.`
148
- };
149
- return {
150
- success: true,
151
- value
152
- };
153
- },
154
- format(value) {
155
- return value.toString();
156
- }
157
- };
209
+ };
210
+ }
211
+ const metavar = options?.metavar ?? "INTEGER";
212
+ require_nonempty.ensureNonEmptyString(metavar);
158
213
  return {
159
- metavar: options?.metavar ?? "INTEGER",
214
+ metavar,
160
215
  parse(input) {
161
216
  if (!input.match(/^-?\d+$/)) return {
162
217
  success: false,
@@ -192,8 +247,10 @@ function integer(options) {
192
247
  */
193
248
  function float(options = {}) {
194
249
  const floatRegex = /^[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.\d+))(?:[eE][+-]?\d+)?$/;
250
+ const metavar = options.metavar ?? "NUMBER";
251
+ require_nonempty.ensureNonEmptyString(metavar);
195
252
  return {
196
- metavar: options.metavar ?? "NUMBER",
253
+ metavar,
197
254
  parse(input) {
198
255
  let value;
199
256
  const lowerInput = input.toLowerCase();
@@ -239,8 +296,10 @@ function float(options = {}) {
239
296
  */
240
297
  function url(options = {}) {
241
298
  const allowedProtocols = options.allowedProtocols?.map((p) => p.toLowerCase());
299
+ const metavar = options.metavar ?? "URL";
300
+ require_nonempty.ensureNonEmptyString(metavar);
242
301
  return {
243
- metavar: options.metavar ?? "URL",
302
+ metavar,
244
303
  parse(input) {
245
304
  if (!URL.canParse(input)) return {
246
305
  success: false,
@@ -281,8 +340,10 @@ function url(options = {}) {
281
340
  * objects.
282
341
  */
283
342
  function locale(options = {}) {
343
+ const metavar = options.metavar ?? "LOCALE";
344
+ require_nonempty.ensureNonEmptyString(metavar);
284
345
  return {
285
- metavar: options.metavar ?? "LOCALE",
346
+ metavar,
286
347
  parse(input) {
287
348
  let locale$1;
288
349
  try {
@@ -548,8 +609,10 @@ function locale(options = {}) {
548
609
  */
549
610
  function uuid(options = {}) {
550
611
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
612
+ const metavar = options.metavar ?? "UUID";
613
+ require_nonempty.ensureNonEmptyString(metavar);
551
614
  return {
552
- metavar: options.metavar ?? "UUID",
615
+ metavar,
553
616
  parse(input) {
554
617
  if (!uuidRegex.test(input)) return {
555
618
  success: false,
@@ -584,8 +647,10 @@ function uuid(options = {}) {
584
647
 
585
648
  //#endregion
586
649
  exports.choice = choice;
650
+ exports.ensureNonEmptyString = require_nonempty.ensureNonEmptyString;
587
651
  exports.float = float;
588
652
  exports.integer = integer;
653
+ exports.isNonEmptyString = require_nonempty.isNonEmptyString;
589
654
  exports.isValueParser = isValueParser;
590
655
  exports.locale = locale;
591
656
  exports.string = string;
@@ -1,3 +1,4 @@
1
+ import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonempty.cjs";
1
2
  import { Message } from "./message.cjs";
2
3
  import { Suggestion } from "./parser.cjs";
3
4
 
@@ -17,7 +18,7 @@ interface ValueParser<T> {
17
18
  * to indicate what kind of value this parser expects. Usually
18
19
  * a single word in uppercase, like `PORT` or `FILE`.
19
20
  */
20
- readonly metavar: string;
21
+ readonly metavar: NonEmptyString;
21
22
  /**
22
23
  * Parses a string input into a value of type {@link T}.
23
24
  *
@@ -75,7 +76,7 @@ interface StringOptions {
75
76
  * word in uppercase, like `HOST` or `NAME`.
76
77
  * @default `"STRING"`
77
78
  */
78
- readonly metavar?: string;
79
+ readonly metavar?: NonEmptyString;
79
80
  /**
80
81
  * Optional regular expression pattern that the string must match.
81
82
  *
@@ -102,16 +103,23 @@ interface StringOptions {
102
103
  };
103
104
  }
104
105
  /**
105
- * Options for creating a {@link choice} parser.
106
+ * Base options for creating a {@link choice} parser.
107
+ * @since 0.9.0
106
108
  */
107
- interface ChoiceOptions {
109
+ interface ChoiceOptionsBase {
108
110
  /**
109
111
  * The metavariable name for this parser. This is used in help messages to
110
112
  * indicate what kind of value this parser expects. Usually a single
111
113
  * word in uppercase, like `TYPE` or `MODE`.
112
114
  * @default `"TYPE"`
113
115
  */
114
- readonly metavar?: string;
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 {
115
123
  /**
116
124
  * If `true`, the parser will perform case-insensitive matching
117
125
  * against the enumerated values. This means that input like "value",
@@ -133,6 +141,31 @@ interface ChoiceOptions {
133
141
  invalidChoice?: Message | ((input: string, choices: readonly string[]) => Message);
134
142
  };
135
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;
136
169
  /**
137
170
  * A predicate function that checks if an object is a {@link ValueParser}.
138
171
  * @param object The object to check.
@@ -151,7 +184,21 @@ declare function isValueParser<T>(object: unknown): object is ValueParser<T>;
151
184
  * @returns A {@link ValueParser} that checks if the input matches one of the
152
185
  * specified values.
153
186
  */
154
- 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>;
155
202
  /**
156
203
  * Creates a {@link ValueParser} for strings.
157
204
  *
@@ -185,7 +232,7 @@ interface IntegerOptionsNumber {
185
232
  * word in uppercase, like `PORT`.
186
233
  * @default `"INTEGER"`
187
234
  */
188
- readonly metavar?: string;
235
+ readonly metavar?: NonEmptyString;
189
236
  /**
190
237
  * Minimum allowed value (inclusive). If not specified,
191
238
  * no minimum is enforced.
@@ -236,7 +283,7 @@ interface IntegerOptionsBigInt {
236
283
  * word in uppercase, like `PORT`.
237
284
  * @default `"INTEGER"`
238
285
  */
239
- readonly metavar?: string;
286
+ readonly metavar?: NonEmptyString;
240
287
  /**
241
288
  * Minimum allowed value (inclusive). If not specified,
242
289
  * no minimum is enforced.
@@ -296,7 +343,7 @@ interface FloatOptions {
296
343
  * word in uppercase, like `RATE` or `PRICE`.
297
344
  * @default `"NUMBER"`
298
345
  */
299
- readonly metavar?: string;
346
+ readonly metavar?: NonEmptyString;
300
347
  /**
301
348
  * Minimum allowed value (inclusive). If not specified,
302
349
  * no minimum is enforced.
@@ -366,7 +413,7 @@ interface UrlOptions {
366
413
  * word in uppercase, like `URL` or `ENDPOINT`.
367
414
  * @default `"URL"`
368
415
  */
369
- readonly metavar?: string;
416
+ readonly metavar?: NonEmptyString;
370
417
  /**
371
418
  * List of allowed URL protocols (e.g., `["http:", "https:"]`).
372
419
  * If specified, the parsed URL must use one of these protocols.
@@ -413,7 +460,7 @@ interface LocaleOptions {
413
460
  * word in uppercase, like `LOCALE` or `LANG`.
414
461
  * @default `"LOCALE"`
415
462
  */
416
- readonly metavar?: string;
463
+ readonly metavar?: NonEmptyString;
417
464
  /**
418
465
  * Custom error messages for locale parsing failures.
419
466
  * @since 0.5.0
@@ -456,7 +503,7 @@ interface UuidOptions {
456
503
  * word in uppercase, like `UUID` or `ID`.
457
504
  * @default `"UUID"`
458
505
  */
459
- readonly metavar?: string;
506
+ readonly metavar?: NonEmptyString;
460
507
  /**
461
508
  * List of allowed UUID versions (e.g., `[4, 5]` for UUIDs version 4 and 5).
462
509
  * If specified, the parser will validate that the UUID matches one of the
@@ -496,4 +543,4 @@ interface UuidOptions {
496
543
  */
497
544
  declare function uuid(options?: UuidOptions): ValueParser<Uuid>;
498
545
  //#endregion
499
- export { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, 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 };
@@ -1,3 +1,4 @@
1
+ import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
1
2
  import { Message } from "./message.js";
2
3
  import { Suggestion } from "./parser.js";
3
4
 
@@ -17,7 +18,7 @@ interface ValueParser<T> {
17
18
  * to indicate what kind of value this parser expects. Usually
18
19
  * a single word in uppercase, like `PORT` or `FILE`.
19
20
  */
20
- readonly metavar: string;
21
+ readonly metavar: NonEmptyString;
21
22
  /**
22
23
  * Parses a string input into a value of type {@link T}.
23
24
  *
@@ -75,7 +76,7 @@ interface StringOptions {
75
76
  * word in uppercase, like `HOST` or `NAME`.
76
77
  * @default `"STRING"`
77
78
  */
78
- readonly metavar?: string;
79
+ readonly metavar?: NonEmptyString;
79
80
  /**
80
81
  * Optional regular expression pattern that the string must match.
81
82
  *
@@ -102,16 +103,23 @@ interface StringOptions {
102
103
  };
103
104
  }
104
105
  /**
105
- * Options for creating a {@link choice} parser.
106
+ * Base options for creating a {@link choice} parser.
107
+ * @since 0.9.0
106
108
  */
107
- interface ChoiceOptions {
109
+ interface ChoiceOptionsBase {
108
110
  /**
109
111
  * The metavariable name for this parser. This is used in help messages to
110
112
  * indicate what kind of value this parser expects. Usually a single
111
113
  * word in uppercase, like `TYPE` or `MODE`.
112
114
  * @default `"TYPE"`
113
115
  */
114
- readonly metavar?: string;
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 {
115
123
  /**
116
124
  * If `true`, the parser will perform case-insensitive matching
117
125
  * against the enumerated values. This means that input like "value",
@@ -133,6 +141,31 @@ interface ChoiceOptions {
133
141
  invalidChoice?: Message | ((input: string, choices: readonly string[]) => Message);
134
142
  };
135
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;
136
169
  /**
137
170
  * A predicate function that checks if an object is a {@link ValueParser}.
138
171
  * @param object The object to check.
@@ -151,7 +184,21 @@ declare function isValueParser<T>(object: unknown): object is ValueParser<T>;
151
184
  * @returns A {@link ValueParser} that checks if the input matches one of the
152
185
  * specified values.
153
186
  */
154
- 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>;
155
202
  /**
156
203
  * Creates a {@link ValueParser} for strings.
157
204
  *
@@ -185,7 +232,7 @@ interface IntegerOptionsNumber {
185
232
  * word in uppercase, like `PORT`.
186
233
  * @default `"INTEGER"`
187
234
  */
188
- readonly metavar?: string;
235
+ readonly metavar?: NonEmptyString;
189
236
  /**
190
237
  * Minimum allowed value (inclusive). If not specified,
191
238
  * no minimum is enforced.
@@ -236,7 +283,7 @@ interface IntegerOptionsBigInt {
236
283
  * word in uppercase, like `PORT`.
237
284
  * @default `"INTEGER"`
238
285
  */
239
- readonly metavar?: string;
286
+ readonly metavar?: NonEmptyString;
240
287
  /**
241
288
  * Minimum allowed value (inclusive). If not specified,
242
289
  * no minimum is enforced.
@@ -296,7 +343,7 @@ interface FloatOptions {
296
343
  * word in uppercase, like `RATE` or `PRICE`.
297
344
  * @default `"NUMBER"`
298
345
  */
299
- readonly metavar?: string;
346
+ readonly metavar?: NonEmptyString;
300
347
  /**
301
348
  * Minimum allowed value (inclusive). If not specified,
302
349
  * no minimum is enforced.
@@ -366,7 +413,7 @@ interface UrlOptions {
366
413
  * word in uppercase, like `URL` or `ENDPOINT`.
367
414
  * @default `"URL"`
368
415
  */
369
- readonly metavar?: string;
416
+ readonly metavar?: NonEmptyString;
370
417
  /**
371
418
  * List of allowed URL protocols (e.g., `["http:", "https:"]`).
372
419
  * If specified, the parsed URL must use one of these protocols.
@@ -413,7 +460,7 @@ interface LocaleOptions {
413
460
  * word in uppercase, like `LOCALE` or `LANG`.
414
461
  * @default `"LOCALE"`
415
462
  */
416
- readonly metavar?: string;
463
+ readonly metavar?: NonEmptyString;
417
464
  /**
418
465
  * Custom error messages for locale parsing failures.
419
466
  * @since 0.5.0
@@ -456,7 +503,7 @@ interface UuidOptions {
456
503
  * word in uppercase, like `UUID` or `ID`.
457
504
  * @default `"UUID"`
458
505
  */
459
- readonly metavar?: string;
506
+ readonly metavar?: NonEmptyString;
460
507
  /**
461
508
  * List of allowed UUID versions (e.g., `[4, 5]` for UUIDs version 4 and 5).
462
509
  * If specified, the parser will validate that the UUID matches one of the
@@ -496,4 +543,4 @@ interface UuidOptions {
496
543
  */
497
544
  declare function uuid(options?: UuidOptions): ValueParser<Uuid>;
498
545
  //#endregion
499
- export { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber, LocaleOptions, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, choice, float, integer, 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 };
@@ -1,4 +1,5 @@
1
1
  import { message, text } from "./message.js";
2
+ import { ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
2
3
 
3
4
  //#region src/valueparser.ts
4
5
  /**
@@ -10,47 +11,68 @@ function isValueParser(object) {
10
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";
11
12
  }
12
13
  /**
13
- * Creates a {@link ValueParser} that accepts one of multiple
14
- * string values, so-called enumerated values.
15
- *
16
- * This parser validates that the input string matches one of
17
- * the specified values. If the input does not match any of the values,
18
- * it returns an error message indicating the valid options.
19
- * @param choices An array of valid string values that this parser can accept.
20
- * @param options Configuration options for the choice parser.
21
- * @returns A {@link ValueParser} that checks if the input matches one of the
22
- * specified values.
14
+ * Implementation of the choice parser for both string and number types.
23
15
  */
24
16
  function choice(choices, options = {}) {
25
- const normalizedValues = options.caseInsensitive ? choices.map((v) => v.toLowerCase()) : choices;
26
- return {
27
- metavar: options.metavar ?? "TYPE",
28
- parse(input) {
29
- const normalizedInput = options.caseInsensitive ? input.toLowerCase() : input;
30
- const index = normalizedValues.indexOf(normalizedInput);
31
- if (index < 0) {
32
- let choicesList = [];
33
- for (let i = 0; i < choices.length; i++) {
34
- if (i > 0) choicesList = [...choicesList, ...message`, `];
35
- choicesList = [...choicesList, ...message`${choices[i]}`];
36
- }
37
- return {
17
+ const metavar = options.metavar ?? "TYPE";
18
+ ensureNonEmptyString(metavar);
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 {
38
33
  success: false,
39
- 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}.`
34
+ error: formatNumberChoiceError(input, numberChoices, numberOptions)
35
+ };
36
+ return {
37
+ success: true,
38
+ value: numberChoices[index]
40
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
+ }));
41
49
  }
50
+ };
51
+ }
52
+ const stringChoices = choices;
53
+ const stringOptions = options;
54
+ const normalizedValues = stringOptions.caseInsensitive ? stringChoices.map((v) => v.toLowerCase()) : stringChoices;
55
+ return {
56
+ metavar,
57
+ parse(input) {
58
+ const normalizedInput = stringOptions.caseInsensitive ? input.toLowerCase() : input;
59
+ const index = normalizedValues.indexOf(normalizedInput);
60
+ if (index < 0) return {
61
+ success: false,
62
+ error: formatStringChoiceError(input, stringChoices, stringOptions)
63
+ };
42
64
  return {
43
65
  success: true,
44
- value: choices[index]
66
+ value: stringChoices[index]
45
67
  };
46
68
  },
47
69
  format(value) {
48
- return value;
70
+ return String(value);
49
71
  },
50
72
  suggest(prefix) {
51
- const normalizedPrefix = options.caseInsensitive ? prefix.toLowerCase() : prefix;
52
- return choices.filter((value) => {
53
- 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;
54
76
  return normalizedValue.startsWith(normalizedPrefix);
55
77
  }).map((value) => ({
56
78
  kind: "literal",
@@ -60,6 +82,31 @@ function choice(choices, options = {}) {
60
82
  };
61
83
  }
62
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
+ /**
63
110
  * Creates a {@link ValueParser} for strings.
64
111
  *
65
112
  * This parser validates that the input is a string and optionally checks
@@ -74,8 +121,10 @@ function choice(choices, options = {}) {
74
121
  * specified options.
75
122
  */
76
123
  function string(options = {}) {
124
+ const metavar = options.metavar ?? "STRING";
125
+ ensureNonEmptyString(metavar);
77
126
  return {
78
- metavar: options.metavar ?? "STRING",
127
+ metavar,
79
128
  parse(input) {
80
129
  if (options.pattern != null && !options.pattern.test(input)) return {
81
130
  success: false,
@@ -125,38 +174,44 @@ function string(options = {}) {
125
174
  * integer type.
126
175
  */
127
176
  function integer(options) {
128
- if (options?.type === "bigint") return {
129
- metavar: options.metavar ?? "INTEGER",
130
- parse(input) {
131
- let value;
132
- try {
133
- value = BigInt(input);
134
- } catch (e) {
135
- if (e instanceof SyntaxError) return {
177
+ if (options?.type === "bigint") {
178
+ const metavar$1 = options.metavar ?? "INTEGER";
179
+ ensureNonEmptyString(metavar$1);
180
+ return {
181
+ metavar: metavar$1,
182
+ parse(input) {
183
+ let value;
184
+ try {
185
+ value = BigInt(input);
186
+ } catch (e) {
187
+ if (e instanceof SyntaxError) return {
188
+ success: false,
189
+ error: options.errors?.invalidInteger ? typeof options.errors.invalidInteger === "function" ? options.errors.invalidInteger(input) : options.errors.invalidInteger : message`Expected a valid integer, but got ${input}.`
190
+ };
191
+ throw e;
192
+ }
193
+ if (options.min != null && value < options.min) return {
136
194
  success: false,
137
- error: options.errors?.invalidInteger ? typeof options.errors.invalidInteger === "function" ? options.errors.invalidInteger(input) : options.errors.invalidInteger : message`Expected a valid integer, but got ${input}.`
195
+ error: options.errors?.belowMinimum ? typeof options.errors.belowMinimum === "function" ? options.errors.belowMinimum(value, options.min) : options.errors.belowMinimum : message`Expected a value greater than or equal to ${text(options.min.toLocaleString("en"))}, but got ${input}.`
138
196
  };
139
- throw e;
197
+ else if (options.max != null && value > options.max) return {
198
+ success: false,
199
+ error: options.errors?.aboveMaximum ? typeof options.errors.aboveMaximum === "function" ? options.errors.aboveMaximum(value, options.max) : options.errors.aboveMaximum : message`Expected a value less than or equal to ${text(options.max.toLocaleString("en"))}, but got ${input}.`
200
+ };
201
+ return {
202
+ success: true,
203
+ value
204
+ };
205
+ },
206
+ format(value) {
207
+ return value.toString();
140
208
  }
141
- if (options.min != null && value < options.min) return {
142
- success: false,
143
- error: options.errors?.belowMinimum ? typeof options.errors.belowMinimum === "function" ? options.errors.belowMinimum(value, options.min) : options.errors.belowMinimum : message`Expected a value greater than or equal to ${text(options.min.toLocaleString("en"))}, but got ${input}.`
144
- };
145
- else if (options.max != null && value > options.max) return {
146
- success: false,
147
- error: options.errors?.aboveMaximum ? typeof options.errors.aboveMaximum === "function" ? options.errors.aboveMaximum(value, options.max) : options.errors.aboveMaximum : message`Expected a value less than or equal to ${text(options.max.toLocaleString("en"))}, but got ${input}.`
148
- };
149
- return {
150
- success: true,
151
- value
152
- };
153
- },
154
- format(value) {
155
- return value.toString();
156
- }
157
- };
209
+ };
210
+ }
211
+ const metavar = options?.metavar ?? "INTEGER";
212
+ ensureNonEmptyString(metavar);
158
213
  return {
159
- metavar: options?.metavar ?? "INTEGER",
214
+ metavar,
160
215
  parse(input) {
161
216
  if (!input.match(/^-?\d+$/)) return {
162
217
  success: false,
@@ -192,8 +247,10 @@ function integer(options) {
192
247
  */
193
248
  function float(options = {}) {
194
249
  const floatRegex = /^[+-]?(?:(?:\d+\.?\d*)|(?:\d*\.\d+))(?:[eE][+-]?\d+)?$/;
250
+ const metavar = options.metavar ?? "NUMBER";
251
+ ensureNonEmptyString(metavar);
195
252
  return {
196
- metavar: options.metavar ?? "NUMBER",
253
+ metavar,
197
254
  parse(input) {
198
255
  let value;
199
256
  const lowerInput = input.toLowerCase();
@@ -239,8 +296,10 @@ function float(options = {}) {
239
296
  */
240
297
  function url(options = {}) {
241
298
  const allowedProtocols = options.allowedProtocols?.map((p) => p.toLowerCase());
299
+ const metavar = options.metavar ?? "URL";
300
+ ensureNonEmptyString(metavar);
242
301
  return {
243
- metavar: options.metavar ?? "URL",
302
+ metavar,
244
303
  parse(input) {
245
304
  if (!URL.canParse(input)) return {
246
305
  success: false,
@@ -281,8 +340,10 @@ function url(options = {}) {
281
340
  * objects.
282
341
  */
283
342
  function locale(options = {}) {
343
+ const metavar = options.metavar ?? "LOCALE";
344
+ ensureNonEmptyString(metavar);
284
345
  return {
285
- metavar: options.metavar ?? "LOCALE",
346
+ metavar,
286
347
  parse(input) {
287
348
  let locale$1;
288
349
  try {
@@ -548,8 +609,10 @@ function locale(options = {}) {
548
609
  */
549
610
  function uuid(options = {}) {
550
611
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
612
+ const metavar = options.metavar ?? "UUID";
613
+ ensureNonEmptyString(metavar);
551
614
  return {
552
- metavar: options.metavar ?? "UUID",
615
+ metavar,
553
616
  parse(input) {
554
617
  if (!uuidRegex.test(input)) return {
555
618
  success: false,
@@ -583,4 +646,4 @@ function uuid(options = {}) {
583
646
  }
584
647
 
585
648
  //#endregion
586
- export { choice, float, integer, isValueParser, locale, string, url, uuid };
649
+ export { choice, ensureNonEmptyString, float, integer, isNonEmptyString, isValueParser, locale, string, url, uuid };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "0.9.0-dev.185+de757baf",
3
+ "version": "0.9.0-dev.187+992128e2",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -99,6 +99,14 @@
99
99
  "import": "./dist/modifiers.js",
100
100
  "require": "./dist/modifiers.cjs"
101
101
  },
102
+ "./nonempty": {
103
+ "types": {
104
+ "import": "./dist/nonempty.d.ts",
105
+ "require": "./dist/nonempty.d.cts"
106
+ },
107
+ "import": "./dist/nonempty.js",
108
+ "require": "./dist/nonempty.cjs"
109
+ },
102
110
  "./parser": {
103
111
  "types": {
104
112
  "import": "./dist/parser.d.ts",