@optique/core 0.8.0-dev.160 → 0.8.0-dev.164

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.
@@ -1030,9 +1030,273 @@ function group(label, parser) {
1030
1030
  }
1031
1031
  };
1032
1032
  }
1033
+ /**
1034
+ * Creates a conditional parser that selects different branch parsers based on
1035
+ * a discriminator option value. This enables discriminated union patterns where
1036
+ * certain options are only required or available when a specific discriminator
1037
+ * value is selected.
1038
+ *
1039
+ * The result type is a tuple: `[discriminatorValue, branchResult]`
1040
+ *
1041
+ * @example
1042
+ * ```typescript
1043
+ * // Basic conditional parsing
1044
+ * const parser = conditional(
1045
+ * option("--reporter", choice(["console", "junit"])),
1046
+ * {
1047
+ * console: object({}),
1048
+ * junit: object({ outputFile: option("--output-file", string()) }),
1049
+ * },
1050
+ * object({}) // default when --reporter is not provided
1051
+ * );
1052
+ *
1053
+ * const result = parse(parser, ["--reporter", "junit", "--output-file", "out.xml"]);
1054
+ * // result.value = ["junit", { outputFile: "out.xml" }]
1055
+ *
1056
+ * // Without --reporter, uses default branch:
1057
+ * const defaultResult = parse(parser, []);
1058
+ * // defaultResult.value = [undefined, {}]
1059
+ * ```
1060
+ *
1061
+ * @since 0.8.0
1062
+ */
1063
+ function conditional(discriminator, branches, defaultBranch, options) {
1064
+ const branchParsers = Object.entries(branches);
1065
+ const allBranchParsers = defaultBranch ? [...branchParsers.map(([_, p]) => p), defaultBranch] : branchParsers.map(([_, p]) => p);
1066
+ const maxPriority = Math.max(discriminator.priority, ...allBranchParsers.map((p) => p.priority));
1067
+ function appendLiteralToUsage(usage$1, literalValue) {
1068
+ const result = [];
1069
+ for (const term of usage$1) if (term.type === "option" && term.metavar !== void 0) {
1070
+ const { metavar: _,...optionWithoutMetavar } = term;
1071
+ result.push(optionWithoutMetavar);
1072
+ result.push({
1073
+ type: "literal",
1074
+ value: literalValue
1075
+ });
1076
+ } else if (term.type === "optional") result.push({
1077
+ ...term,
1078
+ terms: appendLiteralToUsage(term.terms, literalValue)
1079
+ });
1080
+ else if (term.type === "multiple") result.push({
1081
+ ...term,
1082
+ terms: appendLiteralToUsage(term.terms, literalValue)
1083
+ });
1084
+ else if (term.type === "exclusive") result.push({
1085
+ ...term,
1086
+ terms: term.terms.map((t) => appendLiteralToUsage(t, literalValue))
1087
+ });
1088
+ else result.push(term);
1089
+ return result;
1090
+ }
1091
+ const branchUsages = branchParsers.map(([key, p]) => [...appendLiteralToUsage(discriminator.usage, key), ...p.usage]);
1092
+ if (defaultBranch) branchUsages.push(defaultBranch.usage);
1093
+ const usage = branchUsages.length > 1 ? [{
1094
+ type: "exclusive",
1095
+ terms: branchUsages
1096
+ }] : branchUsages[0] ?? [];
1097
+ const initialState = {
1098
+ discriminatorState: discriminator.initialState,
1099
+ discriminatorValue: void 0,
1100
+ selectedBranch: void 0,
1101
+ branchState: void 0
1102
+ };
1103
+ return {
1104
+ $valueType: [],
1105
+ $stateType: [],
1106
+ priority: maxPriority,
1107
+ usage,
1108
+ initialState,
1109
+ parse(context) {
1110
+ const state = context.state ?? initialState;
1111
+ if (state.selectedBranch !== void 0) {
1112
+ const branchParser = state.selectedBranch.kind === "default" ? defaultBranch : branches[state.selectedBranch.key];
1113
+ const branchResult = branchParser.parse({
1114
+ ...context,
1115
+ state: state.branchState,
1116
+ usage: branchParser.usage
1117
+ });
1118
+ if (branchResult.success) return {
1119
+ success: true,
1120
+ next: {
1121
+ ...branchResult.next,
1122
+ state: {
1123
+ ...state,
1124
+ branchState: branchResult.next.state
1125
+ }
1126
+ },
1127
+ consumed: branchResult.consumed
1128
+ };
1129
+ return branchResult;
1130
+ }
1131
+ const discriminatorResult = discriminator.parse({
1132
+ ...context,
1133
+ state: state.discriminatorState
1134
+ });
1135
+ if (discriminatorResult.success && discriminatorResult.consumed.length > 0) {
1136
+ const completionResult = discriminator.complete(discriminatorResult.next.state);
1137
+ if (completionResult.success) {
1138
+ const value = completionResult.value;
1139
+ const branchParser = branches[value];
1140
+ if (branchParser) {
1141
+ const branchParseResult = branchParser.parse({
1142
+ ...context,
1143
+ buffer: discriminatorResult.next.buffer,
1144
+ optionsTerminated: discriminatorResult.next.optionsTerminated,
1145
+ state: branchParser.initialState,
1146
+ usage: branchParser.usage
1147
+ });
1148
+ if (branchParseResult.success) return {
1149
+ success: true,
1150
+ next: {
1151
+ ...branchParseResult.next,
1152
+ state: {
1153
+ discriminatorState: discriminatorResult.next.state,
1154
+ discriminatorValue: value,
1155
+ selectedBranch: {
1156
+ kind: "branch",
1157
+ key: value
1158
+ },
1159
+ branchState: branchParseResult.next.state
1160
+ }
1161
+ },
1162
+ consumed: [...discriminatorResult.consumed, ...branchParseResult.consumed]
1163
+ };
1164
+ return {
1165
+ success: true,
1166
+ next: {
1167
+ ...discriminatorResult.next,
1168
+ state: {
1169
+ discriminatorState: discriminatorResult.next.state,
1170
+ discriminatorValue: value,
1171
+ selectedBranch: {
1172
+ kind: "branch",
1173
+ key: value
1174
+ },
1175
+ branchState: branchParser.initialState
1176
+ }
1177
+ },
1178
+ consumed: discriminatorResult.consumed
1179
+ };
1180
+ }
1181
+ }
1182
+ }
1183
+ if (defaultBranch !== void 0) {
1184
+ const defaultResult = defaultBranch.parse({
1185
+ ...context,
1186
+ state: state.branchState ?? defaultBranch.initialState,
1187
+ usage: defaultBranch.usage
1188
+ });
1189
+ if (defaultResult.success && defaultResult.consumed.length > 0) return {
1190
+ success: true,
1191
+ next: {
1192
+ ...defaultResult.next,
1193
+ state: {
1194
+ ...state,
1195
+ selectedBranch: { kind: "default" },
1196
+ branchState: defaultResult.next.state
1197
+ }
1198
+ },
1199
+ consumed: defaultResult.consumed
1200
+ };
1201
+ }
1202
+ const noMatchContext = analyzeNoMatchContext([discriminator, ...allBranchParsers]);
1203
+ const errorMessage = options?.errors?.noMatch ? typeof options.errors.noMatch === "function" ? options.errors.noMatch(noMatchContext) : options.errors.noMatch : generateNoMatchError(noMatchContext);
1204
+ return {
1205
+ success: false,
1206
+ consumed: 0,
1207
+ error: errorMessage
1208
+ };
1209
+ },
1210
+ complete(state) {
1211
+ if (state.selectedBranch === void 0) {
1212
+ if (defaultBranch !== void 0) {
1213
+ const branchState = state.branchState ?? defaultBranch.initialState;
1214
+ const defaultResult = defaultBranch.complete(branchState);
1215
+ if (!defaultResult.success) return defaultResult;
1216
+ return {
1217
+ success: true,
1218
+ value: [void 0, defaultResult.value]
1219
+ };
1220
+ }
1221
+ return {
1222
+ success: false,
1223
+ error: require_message.message`Missing required discriminator option.`
1224
+ };
1225
+ }
1226
+ const branchParser = state.selectedBranch.kind === "default" ? defaultBranch : branches[state.selectedBranch.key];
1227
+ const branchResult = branchParser.complete(state.branchState);
1228
+ if (!branchResult.success) {
1229
+ if (state.discriminatorValue !== void 0 && options?.errors?.branchError) return {
1230
+ success: false,
1231
+ error: options.errors.branchError(state.discriminatorValue, branchResult.error)
1232
+ };
1233
+ return branchResult;
1234
+ }
1235
+ const discriminatorValue = state.selectedBranch.kind === "default" ? void 0 : state.selectedBranch.key;
1236
+ return {
1237
+ success: true,
1238
+ value: [discriminatorValue, branchResult.value]
1239
+ };
1240
+ },
1241
+ suggest(context, prefix) {
1242
+ const state = context.state ?? initialState;
1243
+ const suggestions = [];
1244
+ if (state.selectedBranch === void 0) {
1245
+ suggestions.push(...discriminator.suggest({
1246
+ ...context,
1247
+ state: state.discriminatorState
1248
+ }, prefix));
1249
+ if (defaultBranch !== void 0) suggestions.push(...defaultBranch.suggest({
1250
+ ...context,
1251
+ state: state.branchState ?? defaultBranch.initialState
1252
+ }, prefix));
1253
+ } else {
1254
+ const branchParser = state.selectedBranch.kind === "default" ? defaultBranch : branches[state.selectedBranch.key];
1255
+ suggestions.push(...branchParser.suggest({
1256
+ ...context,
1257
+ state: state.branchState
1258
+ }, prefix));
1259
+ }
1260
+ const seen = /* @__PURE__ */ new Set();
1261
+ return suggestions.filter((suggestion) => {
1262
+ const key = suggestion.kind === "literal" ? suggestion.text : `__FILE__:${suggestion.type}:${suggestion.extensions?.join(",")}`;
1263
+ if (seen.has(key)) return false;
1264
+ seen.add(key);
1265
+ return true;
1266
+ });
1267
+ },
1268
+ getDocFragments(_state, _defaultValue) {
1269
+ const fragments = [];
1270
+ const discriminatorFragments = discriminator.getDocFragments({ kind: "unavailable" }, void 0);
1271
+ fragments.push(...discriminatorFragments.fragments);
1272
+ for (const [key, branchParser] of branchParsers) {
1273
+ const branchFragments = branchParser.getDocFragments({ kind: "unavailable" }, void 0);
1274
+ const entries = branchFragments.fragments.filter((f) => f.type === "entry");
1275
+ for (const fragment of branchFragments.fragments) if (fragment.type === "section") entries.push(...fragment.entries);
1276
+ if (entries.length > 0) fragments.push({
1277
+ type: "section",
1278
+ title: `Options when ${key}`,
1279
+ entries
1280
+ });
1281
+ }
1282
+ if (defaultBranch !== void 0) {
1283
+ const defaultFragments = defaultBranch.getDocFragments({ kind: "unavailable" }, void 0);
1284
+ const entries = defaultFragments.fragments.filter((f) => f.type === "entry");
1285
+ for (const fragment of defaultFragments.fragments) if (fragment.type === "section") entries.push(...fragment.entries);
1286
+ if (entries.length > 0) fragments.push({
1287
+ type: "section",
1288
+ title: "Default options",
1289
+ entries
1290
+ });
1291
+ }
1292
+ return { fragments };
1293
+ }
1294
+ };
1295
+ }
1033
1296
 
1034
1297
  //#endregion
1035
1298
  exports.concat = concat;
1299
+ exports.conditional = conditional;
1036
1300
  exports.group = group;
1037
1301
  exports.longestMatch = longestMatch;
1038
1302
  exports.merge = merge;
@@ -1258,5 +1258,93 @@ declare function concat<TA extends readonly unknown[], TB extends readonly unkno
1258
1258
  * @since 0.4.0
1259
1259
  */
1260
1260
  declare function group<TValue, TState>(label: string, parser: Parser<TValue, TState>): Parser<TValue, TState>;
1261
+ /**
1262
+ * Tagged union type representing which branch is selected.
1263
+ * Uses tagged union to avoid collision with discriminator values.
1264
+ * @internal
1265
+ */
1266
+ type SelectedBranch<TDiscriminator extends string> = {
1267
+ readonly kind: "branch";
1268
+ readonly key: TDiscriminator;
1269
+ } | {
1270
+ readonly kind: "default";
1271
+ };
1272
+ /**
1273
+ * State type for the conditional parser.
1274
+ * @internal
1275
+ */
1276
+ interface ConditionalState<TDiscriminator extends string> {
1277
+ readonly discriminatorState: unknown;
1278
+ readonly discriminatorValue: TDiscriminator | undefined;
1279
+ readonly selectedBranch: SelectedBranch<TDiscriminator> | undefined;
1280
+ readonly branchState: unknown;
1281
+ }
1282
+ /**
1283
+ * Options for customizing error messages in the {@link conditional} combinator.
1284
+ * @since 0.8.0
1285
+ */
1286
+ interface ConditionalErrorOptions {
1287
+ /**
1288
+ * Custom error message when branch parser fails.
1289
+ * Receives the discriminator value for context.
1290
+ */
1291
+ branchError?: (discriminatorValue: string | undefined, error: Message) => Message;
1292
+ /**
1293
+ * Custom error message for no matching input.
1294
+ */
1295
+ noMatch?: Message | ((context: NoMatchContext) => Message);
1296
+ }
1297
+ /**
1298
+ * Options for customizing the {@link conditional} combinator behavior.
1299
+ * @since 0.8.0
1300
+ */
1301
+ interface ConditionalOptions {
1302
+ /**
1303
+ * Custom error messages.
1304
+ */
1305
+ errors?: ConditionalErrorOptions;
1306
+ }
1307
+ /**
1308
+ * Helper type to infer result type without default branch.
1309
+ * @internal
1310
+ */
1311
+ type ConditionalResultWithoutDefault<TDiscriminator extends string, TBranches extends Record<string, Parser<unknown, unknown>>> = { [K in keyof TBranches & string]: readonly [K, InferValue<TBranches[K]>] }[keyof TBranches & string];
1312
+ /**
1313
+ * Helper type to infer result type with default branch.
1314
+ * @internal
1315
+ */
1316
+ type ConditionalResultWithDefault<TDiscriminator extends string, TBranches extends Record<string, Parser<unknown, unknown>>, TDefault extends Parser<unknown, unknown>> = ConditionalResultWithoutDefault<TDiscriminator, TBranches> | readonly [undefined, InferValue<TDefault>];
1317
+ /**
1318
+ * Helper type to infer value type from a parser.
1319
+ * @internal
1320
+ */
1321
+ type InferValue<T> = T extends Parser<infer V, unknown> ? V : never;
1322
+ /**
1323
+ * Creates a conditional parser without a default branch.
1324
+ * The discriminator option is required; parsing fails if not provided.
1325
+ *
1326
+ * @template TDiscriminator The string literal union type of discriminator values.
1327
+ * @template TBranches Record mapping discriminator values to branch parsers.
1328
+ * @param discriminator Parser for the discriminator option (typically using choice()).
1329
+ * @param branches Object mapping each discriminator value to its branch parser.
1330
+ * @returns A parser that produces a tuple `[discriminatorValue, branchResult]`.
1331
+ * @since 0.8.0
1332
+ */
1333
+ declare function conditional<TDiscriminator extends string, TBranches extends { [K in TDiscriminator]: Parser<unknown, unknown> }>(discriminator: Parser<TDiscriminator, unknown>, branches: TBranches): Parser<ConditionalResultWithoutDefault<TDiscriminator, TBranches>, ConditionalState<TDiscriminator>>;
1334
+ /**
1335
+ * Creates a conditional parser with a default branch.
1336
+ * The default branch is used when the discriminator option is not provided.
1337
+ *
1338
+ * @template TDiscriminator The string literal union type of discriminator values.
1339
+ * @template TBranches Record mapping discriminator values to branch parsers.
1340
+ * @template TDefault The default branch parser type.
1341
+ * @param discriminator Parser for the discriminator option (typically using choice()).
1342
+ * @param branches Object mapping each discriminator value to its branch parser.
1343
+ * @param defaultBranch Parser to use when discriminator is not provided.
1344
+ * @param options Optional configuration for error messages.
1345
+ * @returns A parser that produces a tuple `[discriminatorValue | undefined, branchResult]`.
1346
+ * @since 0.8.0
1347
+ */
1348
+ declare function conditional<TDiscriminator extends string, TBranches extends { [K in TDiscriminator]: Parser<unknown, unknown> }, TDefault extends Parser<unknown, unknown>>(discriminator: Parser<TDiscriminator, unknown>, branches: TBranches, defaultBranch: TDefault, options?: ConditionalOptions): Parser<ConditionalResultWithDefault<TDiscriminator, TBranches, TDefault>, ConditionalState<TDiscriminator>>;
1261
1349
  //#endregion
1262
- export { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple };
1350
+ export { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple };
@@ -1258,5 +1258,93 @@ declare function concat<TA extends readonly unknown[], TB extends readonly unkno
1258
1258
  * @since 0.4.0
1259
1259
  */
1260
1260
  declare function group<TValue, TState>(label: string, parser: Parser<TValue, TState>): Parser<TValue, TState>;
1261
+ /**
1262
+ * Tagged union type representing which branch is selected.
1263
+ * Uses tagged union to avoid collision with discriminator values.
1264
+ * @internal
1265
+ */
1266
+ type SelectedBranch<TDiscriminator extends string> = {
1267
+ readonly kind: "branch";
1268
+ readonly key: TDiscriminator;
1269
+ } | {
1270
+ readonly kind: "default";
1271
+ };
1272
+ /**
1273
+ * State type for the conditional parser.
1274
+ * @internal
1275
+ */
1276
+ interface ConditionalState<TDiscriminator extends string> {
1277
+ readonly discriminatorState: unknown;
1278
+ readonly discriminatorValue: TDiscriminator | undefined;
1279
+ readonly selectedBranch: SelectedBranch<TDiscriminator> | undefined;
1280
+ readonly branchState: unknown;
1281
+ }
1282
+ /**
1283
+ * Options for customizing error messages in the {@link conditional} combinator.
1284
+ * @since 0.8.0
1285
+ */
1286
+ interface ConditionalErrorOptions {
1287
+ /**
1288
+ * Custom error message when branch parser fails.
1289
+ * Receives the discriminator value for context.
1290
+ */
1291
+ branchError?: (discriminatorValue: string | undefined, error: Message) => Message;
1292
+ /**
1293
+ * Custom error message for no matching input.
1294
+ */
1295
+ noMatch?: Message | ((context: NoMatchContext) => Message);
1296
+ }
1297
+ /**
1298
+ * Options for customizing the {@link conditional} combinator behavior.
1299
+ * @since 0.8.0
1300
+ */
1301
+ interface ConditionalOptions {
1302
+ /**
1303
+ * Custom error messages.
1304
+ */
1305
+ errors?: ConditionalErrorOptions;
1306
+ }
1307
+ /**
1308
+ * Helper type to infer result type without default branch.
1309
+ * @internal
1310
+ */
1311
+ type ConditionalResultWithoutDefault<TDiscriminator extends string, TBranches extends Record<string, Parser<unknown, unknown>>> = { [K in keyof TBranches & string]: readonly [K, InferValue<TBranches[K]>] }[keyof TBranches & string];
1312
+ /**
1313
+ * Helper type to infer result type with default branch.
1314
+ * @internal
1315
+ */
1316
+ type ConditionalResultWithDefault<TDiscriminator extends string, TBranches extends Record<string, Parser<unknown, unknown>>, TDefault extends Parser<unknown, unknown>> = ConditionalResultWithoutDefault<TDiscriminator, TBranches> | readonly [undefined, InferValue<TDefault>];
1317
+ /**
1318
+ * Helper type to infer value type from a parser.
1319
+ * @internal
1320
+ */
1321
+ type InferValue<T> = T extends Parser<infer V, unknown> ? V : never;
1322
+ /**
1323
+ * Creates a conditional parser without a default branch.
1324
+ * The discriminator option is required; parsing fails if not provided.
1325
+ *
1326
+ * @template TDiscriminator The string literal union type of discriminator values.
1327
+ * @template TBranches Record mapping discriminator values to branch parsers.
1328
+ * @param discriminator Parser for the discriminator option (typically using choice()).
1329
+ * @param branches Object mapping each discriminator value to its branch parser.
1330
+ * @returns A parser that produces a tuple `[discriminatorValue, branchResult]`.
1331
+ * @since 0.8.0
1332
+ */
1333
+ declare function conditional<TDiscriminator extends string, TBranches extends { [K in TDiscriminator]: Parser<unknown, unknown> }>(discriminator: Parser<TDiscriminator, unknown>, branches: TBranches): Parser<ConditionalResultWithoutDefault<TDiscriminator, TBranches>, ConditionalState<TDiscriminator>>;
1334
+ /**
1335
+ * Creates a conditional parser with a default branch.
1336
+ * The default branch is used when the discriminator option is not provided.
1337
+ *
1338
+ * @template TDiscriminator The string literal union type of discriminator values.
1339
+ * @template TBranches Record mapping discriminator values to branch parsers.
1340
+ * @template TDefault The default branch parser type.
1341
+ * @param discriminator Parser for the discriminator option (typically using choice()).
1342
+ * @param branches Object mapping each discriminator value to its branch parser.
1343
+ * @param defaultBranch Parser to use when discriminator is not provided.
1344
+ * @param options Optional configuration for error messages.
1345
+ * @returns A parser that produces a tuple `[discriminatorValue | undefined, branchResult]`.
1346
+ * @since 0.8.0
1347
+ */
1348
+ declare function conditional<TDiscriminator extends string, TBranches extends { [K in TDiscriminator]: Parser<unknown, unknown> }, TDefault extends Parser<unknown, unknown>>(discriminator: Parser<TDiscriminator, unknown>, branches: TBranches, defaultBranch: TDefault, options?: ConditionalOptions): Parser<ConditionalResultWithDefault<TDiscriminator, TBranches, TDefault>, ConditionalState<TDiscriminator>>;
1261
1349
  //#endregion
1262
- export { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple };
1350
+ export { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple };
@@ -1030,6 +1030,269 @@ function group(label, parser) {
1030
1030
  }
1031
1031
  };
1032
1032
  }
1033
+ /**
1034
+ * Creates a conditional parser that selects different branch parsers based on
1035
+ * a discriminator option value. This enables discriminated union patterns where
1036
+ * certain options are only required or available when a specific discriminator
1037
+ * value is selected.
1038
+ *
1039
+ * The result type is a tuple: `[discriminatorValue, branchResult]`
1040
+ *
1041
+ * @example
1042
+ * ```typescript
1043
+ * // Basic conditional parsing
1044
+ * const parser = conditional(
1045
+ * option("--reporter", choice(["console", "junit"])),
1046
+ * {
1047
+ * console: object({}),
1048
+ * junit: object({ outputFile: option("--output-file", string()) }),
1049
+ * },
1050
+ * object({}) // default when --reporter is not provided
1051
+ * );
1052
+ *
1053
+ * const result = parse(parser, ["--reporter", "junit", "--output-file", "out.xml"]);
1054
+ * // result.value = ["junit", { outputFile: "out.xml" }]
1055
+ *
1056
+ * // Without --reporter, uses default branch:
1057
+ * const defaultResult = parse(parser, []);
1058
+ * // defaultResult.value = [undefined, {}]
1059
+ * ```
1060
+ *
1061
+ * @since 0.8.0
1062
+ */
1063
+ function conditional(discriminator, branches, defaultBranch, options) {
1064
+ const branchParsers = Object.entries(branches);
1065
+ const allBranchParsers = defaultBranch ? [...branchParsers.map(([_, p]) => p), defaultBranch] : branchParsers.map(([_, p]) => p);
1066
+ const maxPriority = Math.max(discriminator.priority, ...allBranchParsers.map((p) => p.priority));
1067
+ function appendLiteralToUsage(usage$1, literalValue) {
1068
+ const result = [];
1069
+ for (const term of usage$1) if (term.type === "option" && term.metavar !== void 0) {
1070
+ const { metavar: _,...optionWithoutMetavar } = term;
1071
+ result.push(optionWithoutMetavar);
1072
+ result.push({
1073
+ type: "literal",
1074
+ value: literalValue
1075
+ });
1076
+ } else if (term.type === "optional") result.push({
1077
+ ...term,
1078
+ terms: appendLiteralToUsage(term.terms, literalValue)
1079
+ });
1080
+ else if (term.type === "multiple") result.push({
1081
+ ...term,
1082
+ terms: appendLiteralToUsage(term.terms, literalValue)
1083
+ });
1084
+ else if (term.type === "exclusive") result.push({
1085
+ ...term,
1086
+ terms: term.terms.map((t) => appendLiteralToUsage(t, literalValue))
1087
+ });
1088
+ else result.push(term);
1089
+ return result;
1090
+ }
1091
+ const branchUsages = branchParsers.map(([key, p]) => [...appendLiteralToUsage(discriminator.usage, key), ...p.usage]);
1092
+ if (defaultBranch) branchUsages.push(defaultBranch.usage);
1093
+ const usage = branchUsages.length > 1 ? [{
1094
+ type: "exclusive",
1095
+ terms: branchUsages
1096
+ }] : branchUsages[0] ?? [];
1097
+ const initialState = {
1098
+ discriminatorState: discriminator.initialState,
1099
+ discriminatorValue: void 0,
1100
+ selectedBranch: void 0,
1101
+ branchState: void 0
1102
+ };
1103
+ return {
1104
+ $valueType: [],
1105
+ $stateType: [],
1106
+ priority: maxPriority,
1107
+ usage,
1108
+ initialState,
1109
+ parse(context) {
1110
+ const state = context.state ?? initialState;
1111
+ if (state.selectedBranch !== void 0) {
1112
+ const branchParser = state.selectedBranch.kind === "default" ? defaultBranch : branches[state.selectedBranch.key];
1113
+ const branchResult = branchParser.parse({
1114
+ ...context,
1115
+ state: state.branchState,
1116
+ usage: branchParser.usage
1117
+ });
1118
+ if (branchResult.success) return {
1119
+ success: true,
1120
+ next: {
1121
+ ...branchResult.next,
1122
+ state: {
1123
+ ...state,
1124
+ branchState: branchResult.next.state
1125
+ }
1126
+ },
1127
+ consumed: branchResult.consumed
1128
+ };
1129
+ return branchResult;
1130
+ }
1131
+ const discriminatorResult = discriminator.parse({
1132
+ ...context,
1133
+ state: state.discriminatorState
1134
+ });
1135
+ if (discriminatorResult.success && discriminatorResult.consumed.length > 0) {
1136
+ const completionResult = discriminator.complete(discriminatorResult.next.state);
1137
+ if (completionResult.success) {
1138
+ const value = completionResult.value;
1139
+ const branchParser = branches[value];
1140
+ if (branchParser) {
1141
+ const branchParseResult = branchParser.parse({
1142
+ ...context,
1143
+ buffer: discriminatorResult.next.buffer,
1144
+ optionsTerminated: discriminatorResult.next.optionsTerminated,
1145
+ state: branchParser.initialState,
1146
+ usage: branchParser.usage
1147
+ });
1148
+ if (branchParseResult.success) return {
1149
+ success: true,
1150
+ next: {
1151
+ ...branchParseResult.next,
1152
+ state: {
1153
+ discriminatorState: discriminatorResult.next.state,
1154
+ discriminatorValue: value,
1155
+ selectedBranch: {
1156
+ kind: "branch",
1157
+ key: value
1158
+ },
1159
+ branchState: branchParseResult.next.state
1160
+ }
1161
+ },
1162
+ consumed: [...discriminatorResult.consumed, ...branchParseResult.consumed]
1163
+ };
1164
+ return {
1165
+ success: true,
1166
+ next: {
1167
+ ...discriminatorResult.next,
1168
+ state: {
1169
+ discriminatorState: discriminatorResult.next.state,
1170
+ discriminatorValue: value,
1171
+ selectedBranch: {
1172
+ kind: "branch",
1173
+ key: value
1174
+ },
1175
+ branchState: branchParser.initialState
1176
+ }
1177
+ },
1178
+ consumed: discriminatorResult.consumed
1179
+ };
1180
+ }
1181
+ }
1182
+ }
1183
+ if (defaultBranch !== void 0) {
1184
+ const defaultResult = defaultBranch.parse({
1185
+ ...context,
1186
+ state: state.branchState ?? defaultBranch.initialState,
1187
+ usage: defaultBranch.usage
1188
+ });
1189
+ if (defaultResult.success && defaultResult.consumed.length > 0) return {
1190
+ success: true,
1191
+ next: {
1192
+ ...defaultResult.next,
1193
+ state: {
1194
+ ...state,
1195
+ selectedBranch: { kind: "default" },
1196
+ branchState: defaultResult.next.state
1197
+ }
1198
+ },
1199
+ consumed: defaultResult.consumed
1200
+ };
1201
+ }
1202
+ const noMatchContext = analyzeNoMatchContext([discriminator, ...allBranchParsers]);
1203
+ const errorMessage = options?.errors?.noMatch ? typeof options.errors.noMatch === "function" ? options.errors.noMatch(noMatchContext) : options.errors.noMatch : generateNoMatchError(noMatchContext);
1204
+ return {
1205
+ success: false,
1206
+ consumed: 0,
1207
+ error: errorMessage
1208
+ };
1209
+ },
1210
+ complete(state) {
1211
+ if (state.selectedBranch === void 0) {
1212
+ if (defaultBranch !== void 0) {
1213
+ const branchState = state.branchState ?? defaultBranch.initialState;
1214
+ const defaultResult = defaultBranch.complete(branchState);
1215
+ if (!defaultResult.success) return defaultResult;
1216
+ return {
1217
+ success: true,
1218
+ value: [void 0, defaultResult.value]
1219
+ };
1220
+ }
1221
+ return {
1222
+ success: false,
1223
+ error: message`Missing required discriminator option.`
1224
+ };
1225
+ }
1226
+ const branchParser = state.selectedBranch.kind === "default" ? defaultBranch : branches[state.selectedBranch.key];
1227
+ const branchResult = branchParser.complete(state.branchState);
1228
+ if (!branchResult.success) {
1229
+ if (state.discriminatorValue !== void 0 && options?.errors?.branchError) return {
1230
+ success: false,
1231
+ error: options.errors.branchError(state.discriminatorValue, branchResult.error)
1232
+ };
1233
+ return branchResult;
1234
+ }
1235
+ const discriminatorValue = state.selectedBranch.kind === "default" ? void 0 : state.selectedBranch.key;
1236
+ return {
1237
+ success: true,
1238
+ value: [discriminatorValue, branchResult.value]
1239
+ };
1240
+ },
1241
+ suggest(context, prefix) {
1242
+ const state = context.state ?? initialState;
1243
+ const suggestions = [];
1244
+ if (state.selectedBranch === void 0) {
1245
+ suggestions.push(...discriminator.suggest({
1246
+ ...context,
1247
+ state: state.discriminatorState
1248
+ }, prefix));
1249
+ if (defaultBranch !== void 0) suggestions.push(...defaultBranch.suggest({
1250
+ ...context,
1251
+ state: state.branchState ?? defaultBranch.initialState
1252
+ }, prefix));
1253
+ } else {
1254
+ const branchParser = state.selectedBranch.kind === "default" ? defaultBranch : branches[state.selectedBranch.key];
1255
+ suggestions.push(...branchParser.suggest({
1256
+ ...context,
1257
+ state: state.branchState
1258
+ }, prefix));
1259
+ }
1260
+ const seen = /* @__PURE__ */ new Set();
1261
+ return suggestions.filter((suggestion) => {
1262
+ const key = suggestion.kind === "literal" ? suggestion.text : `__FILE__:${suggestion.type}:${suggestion.extensions?.join(",")}`;
1263
+ if (seen.has(key)) return false;
1264
+ seen.add(key);
1265
+ return true;
1266
+ });
1267
+ },
1268
+ getDocFragments(_state, _defaultValue) {
1269
+ const fragments = [];
1270
+ const discriminatorFragments = discriminator.getDocFragments({ kind: "unavailable" }, void 0);
1271
+ fragments.push(...discriminatorFragments.fragments);
1272
+ for (const [key, branchParser] of branchParsers) {
1273
+ const branchFragments = branchParser.getDocFragments({ kind: "unavailable" }, void 0);
1274
+ const entries = branchFragments.fragments.filter((f) => f.type === "entry");
1275
+ for (const fragment of branchFragments.fragments) if (fragment.type === "section") entries.push(...fragment.entries);
1276
+ if (entries.length > 0) fragments.push({
1277
+ type: "section",
1278
+ title: `Options when ${key}`,
1279
+ entries
1280
+ });
1281
+ }
1282
+ if (defaultBranch !== void 0) {
1283
+ const defaultFragments = defaultBranch.getDocFragments({ kind: "unavailable" }, void 0);
1284
+ const entries = defaultFragments.fragments.filter((f) => f.type === "entry");
1285
+ for (const fragment of defaultFragments.fragments) if (fragment.type === "section") entries.push(...fragment.entries);
1286
+ if (entries.length > 0) fragments.push({
1287
+ type: "section",
1288
+ title: "Default options",
1289
+ entries
1290
+ });
1291
+ }
1292
+ return { fragments };
1293
+ }
1294
+ };
1295
+ }
1033
1296
 
1034
1297
  //#endregion
1035
- export { concat, group, longestMatch, merge, object, or, tuple };
1298
+ export { concat, conditional, group, longestMatch, merge, object, or, tuple };
package/dist/index.cjs CHANGED
@@ -17,6 +17,7 @@ exports.choice = require_valueparser.choice;
17
17
  exports.command = require_primitives.command;
18
18
  exports.commandLine = require_message.commandLine;
19
19
  exports.concat = require_constructs.concat;
20
+ exports.conditional = require_constructs.conditional;
20
21
  exports.constant = require_primitives.constant;
21
22
  exports.envVar = require_message.envVar;
22
23
  exports.extractArgumentMetavars = require_usage.extractArgumentMetavars;
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@ import { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.cjs";
7
7
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.cjs";
8
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
8
+ import { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
9
9
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.cjs";
10
10
  import { RunError, RunOptions, run } from "./facade.cjs";
11
- export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, 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, Result, RunError, RunOptions, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, 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, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
11
+ export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, 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, Result, RunError, RunOptions, 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, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ChoiceOptions, FloatOptions, IntegerOptionsBigInt, IntegerOptionsNumber
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.js";
7
7
  import { DocState, InferValue, Parser, ParserContext, ParserResult, Result, Suggestion, getDocPage, parse, suggest } from "./parser.js";
8
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
8
+ import { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
9
9
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.js";
10
10
  import { RunError, RunOptions, run } from "./facade.js";
11
- export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, 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, Result, RunError, RunOptions, ShellCompletion, ShowDefaultOptions, StringOptions, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, WithDefaultError, WithDefaultOptions, argument, bash, choice, command, commandLine, concat, 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, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
11
+ export { ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, 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, Result, RunError, RunOptions, 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, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { commandLine, envVar, formatMessage, message, metavar, optionName, optionNames, text, value, values } from "./message.js";
2
2
  import { extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, normalizeUsage } from "./usage.js";
3
- import { concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
3
+ import { concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
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";
@@ -9,4 +9,4 @@ import { argument, command, constant, flag, option } from "./primitives.js";
9
9
  import { getDocPage, parse, suggest } from "./parser.js";
10
10
  import { RunError, run } from "./facade.js";
11
11
 
12
- export { RunError, WithDefaultError, argument, bash, choice, command, commandLine, concat, 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, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
12
+ export { RunError, 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, pwsh, run, string, suggest, text, tuple, url, uuid, value, values, withDefault, zsh };
@@ -23,18 +23,29 @@ function optional(parser) {
23
23
  }],
24
24
  initialState: void 0,
25
25
  parse(context) {
26
+ const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
26
27
  const result = parser.parse({
27
28
  ...context,
28
- state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
29
+ state: innerState
29
30
  });
30
- if (result.success) return {
31
- success: true,
32
- next: {
33
- ...result.next,
34
- state: [result.next.state]
35
- },
36
- consumed: result.consumed
37
- };
31
+ if (result.success) {
32
+ if (result.next.state !== innerState || result.consumed.length === 0) return {
33
+ success: true,
34
+ next: {
35
+ ...result.next,
36
+ state: [result.next.state]
37
+ },
38
+ consumed: result.consumed
39
+ };
40
+ return {
41
+ success: true,
42
+ next: {
43
+ ...result.next,
44
+ state: context.state
45
+ },
46
+ consumed: result.consumed
47
+ };
48
+ }
38
49
  if (result.consumed === 0) return {
39
50
  success: true,
40
51
  next: context,
@@ -110,18 +121,29 @@ function withDefault(parser, defaultValue, options) {
110
121
  }],
111
122
  initialState: void 0,
112
123
  parse(context) {
124
+ const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
113
125
  const result = parser.parse({
114
126
  ...context,
115
- state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
127
+ state: innerState
116
128
  });
117
- if (result.success) return {
118
- success: true,
119
- next: {
120
- ...result.next,
121
- state: [result.next.state]
122
- },
123
- consumed: result.consumed
124
- };
129
+ if (result.success) {
130
+ if (result.next.state !== innerState || result.consumed.length === 0) return {
131
+ success: true,
132
+ next: {
133
+ ...result.next,
134
+ state: [result.next.state]
135
+ },
136
+ consumed: result.consumed
137
+ };
138
+ return {
139
+ success: true,
140
+ next: {
141
+ ...result.next,
142
+ state: context.state
143
+ },
144
+ consumed: result.consumed
145
+ };
146
+ }
125
147
  if (result.consumed === 0) return {
126
148
  success: true,
127
149
  next: context,
package/dist/modifiers.js CHANGED
@@ -23,18 +23,29 @@ function optional(parser) {
23
23
  }],
24
24
  initialState: void 0,
25
25
  parse(context) {
26
+ const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
26
27
  const result = parser.parse({
27
28
  ...context,
28
- state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
29
+ state: innerState
29
30
  });
30
- if (result.success) return {
31
- success: true,
32
- next: {
33
- ...result.next,
34
- state: [result.next.state]
35
- },
36
- consumed: result.consumed
37
- };
31
+ if (result.success) {
32
+ if (result.next.state !== innerState || result.consumed.length === 0) return {
33
+ success: true,
34
+ next: {
35
+ ...result.next,
36
+ state: [result.next.state]
37
+ },
38
+ consumed: result.consumed
39
+ };
40
+ return {
41
+ success: true,
42
+ next: {
43
+ ...result.next,
44
+ state: context.state
45
+ },
46
+ consumed: result.consumed
47
+ };
48
+ }
38
49
  if (result.consumed === 0) return {
39
50
  success: true,
40
51
  next: context,
@@ -110,18 +121,29 @@ function withDefault(parser, defaultValue, options) {
110
121
  }],
111
122
  initialState: void 0,
112
123
  parse(context) {
124
+ const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
113
125
  const result = parser.parse({
114
126
  ...context,
115
- state: typeof context.state === "undefined" ? parser.initialState : context.state[0]
127
+ state: innerState
116
128
  });
117
- if (result.success) return {
118
- success: true,
119
- next: {
120
- ...result.next,
121
- state: [result.next.state]
122
- },
123
- consumed: result.consumed
124
- };
129
+ if (result.success) {
130
+ if (result.next.state !== innerState || result.consumed.length === 0) return {
131
+ success: true,
132
+ next: {
133
+ ...result.next,
134
+ state: [result.next.state]
135
+ },
136
+ consumed: result.consumed
137
+ };
138
+ return {
139
+ success: true,
140
+ next: {
141
+ ...result.next,
142
+ state: context.state
143
+ },
144
+ consumed: result.consumed
145
+ };
146
+ }
125
147
  if (result.consumed === 0) return {
126
148
  success: true,
127
149
  next: context,
package/dist/parser.cjs CHANGED
@@ -176,6 +176,7 @@ exports.WithDefaultError = require_modifiers.WithDefaultError;
176
176
  exports.argument = require_primitives.argument;
177
177
  exports.command = require_primitives.command;
178
178
  exports.concat = require_constructs.concat;
179
+ exports.conditional = require_constructs.conditional;
179
180
  exports.constant = require_primitives.constant;
180
181
  exports.flag = require_primitives.flag;
181
182
  exports.getDocPage = getDocPage;
package/dist/parser.d.cts CHANGED
@@ -4,7 +4,7 @@ import { DocFragments, DocPage } from "./doc.cjs";
4
4
  import { ValueParserResult } from "./valueparser.cjs";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.cjs";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.cjs";
7
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
7
+ import { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
8
8
 
9
9
  //#region src/parser.d.ts
10
10
 
@@ -323,4 +323,4 @@ declare function suggest<T>(parser: Parser<T, unknown>, args: readonly [string,
323
323
  */
324
324
  declare function getDocPage(parser: Parser<unknown, unknown>, args?: readonly string[]): DocPage | undefined;
325
325
  //#endregion
326
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
326
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
package/dist/parser.d.ts CHANGED
@@ -4,7 +4,7 @@ import { DocFragments, DocPage } from "./doc.js";
4
4
  import { ValueParserResult } from "./valueparser.js";
5
5
  import { MultipleErrorOptions, MultipleOptions, WithDefaultError, WithDefaultOptions, map, multiple, optional, withDefault } from "./modifiers.js";
6
6
  import { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, FlagErrorOptions, FlagOptions, OptionErrorOptions, OptionOptions, argument, command, constant, flag, option } from "./primitives.js";
7
- import { LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
7
+ import { ConditionalErrorOptions, ConditionalOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
8
8
 
9
9
  //#region src/parser.d.ts
10
10
 
@@ -323,4 +323,4 @@ declare function suggest<T>(parser: Parser<T, unknown>, args: readonly [string,
323
323
  */
324
324
  declare function getDocPage(parser: Parser<unknown, unknown>, args?: readonly string[]): DocPage | undefined;
325
325
  //#endregion
326
- export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
326
+ export { ArgumentErrorOptions, ArgumentOptions, CommandErrorOptions, CommandOptions, ConditionalErrorOptions, ConditionalOptions, DocState, FlagErrorOptions, FlagOptions, InferValue, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, MultipleErrorOptions, MultipleOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionOptions, OrErrorOptions, OrOptions, Parser, ParserContext, ParserResult, Result, Suggestion, TupleOptions, WithDefaultError, WithDefaultOptions, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
package/dist/parser.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { message } from "./message.js";
2
2
  import { normalizeUsage } from "./usage.js";
3
- import { concat, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
3
+ import { concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
4
4
  import { WithDefaultError, map, multiple, optional, withDefault } from "./modifiers.js";
5
5
  import { argument, command, constant, flag, option } from "./primitives.js";
6
6
 
@@ -172,4 +172,4 @@ function getDocPage(parser, args = []) {
172
172
  }
173
173
 
174
174
  //#endregion
175
- export { WithDefaultError, argument, command, concat, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
175
+ export { WithDefaultError, argument, command, concat, conditional, constant, flag, getDocPage, group, longestMatch, map, merge, multiple, object, option, optional, or, parse, suggest, tuple, withDefault };
package/dist/usage.cjs CHANGED
@@ -327,7 +327,11 @@ function* formatUsageTermInternal(term, options) {
327
327
  text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
328
328
  width: 1
329
329
  };
330
- } else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
330
+ } else if (term.type === "literal") yield {
331
+ text: term.value,
332
+ width: term.value.length
333
+ };
334
+ else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
331
335
  }
332
336
 
333
337
  //#endregion
package/dist/usage.d.cts CHANGED
@@ -108,6 +108,21 @@ type UsageTerm =
108
108
  * arguments, options, commands, or other usage terms.
109
109
  */
110
110
  readonly terms: readonly Usage[];
111
+ }
112
+ /**
113
+ * A literal term, which represents a fixed string value in the command-line
114
+ * usage. Unlike metavars which are placeholders for user-provided values,
115
+ * literals represent exact strings that must be typed as-is.
116
+ * @since 0.8.0
117
+ */ | {
118
+ /**
119
+ * The type of the term, which is always `"literal"` for this term.
120
+ */
121
+ readonly type: "literal";
122
+ /**
123
+ * The literal value that must be provided exactly as written.
124
+ */
125
+ readonly value: string;
111
126
  };
112
127
  /**
113
128
  * Represents a command-line usage description, which is a sequence of
package/dist/usage.d.ts CHANGED
@@ -108,6 +108,21 @@ type UsageTerm =
108
108
  * arguments, options, commands, or other usage terms.
109
109
  */
110
110
  readonly terms: readonly Usage[];
111
+ }
112
+ /**
113
+ * A literal term, which represents a fixed string value in the command-line
114
+ * usage. Unlike metavars which are placeholders for user-provided values,
115
+ * literals represent exact strings that must be typed as-is.
116
+ * @since 0.8.0
117
+ */ | {
118
+ /**
119
+ * The type of the term, which is always `"literal"` for this term.
120
+ */
121
+ readonly type: "literal";
122
+ /**
123
+ * The literal value that must be provided exactly as written.
124
+ */
125
+ readonly value: string;
111
126
  };
112
127
  /**
113
128
  * Represents a command-line usage description, which is a sequence of
package/dist/usage.js CHANGED
@@ -326,7 +326,11 @@ function* formatUsageTermInternal(term, options) {
326
326
  text: options?.colors ? `\x1b[2m]\x1b[0m` : "]",
327
327
  width: 1
328
328
  };
329
- } else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
329
+ } else if (term.type === "literal") yield {
330
+ text: term.value,
331
+ width: term.value.length
332
+ };
333
+ else throw new TypeError(`Unknown usage term type: ${term["type"]}.`);
330
334
  }
331
335
 
332
336
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "0.8.0-dev.160+4ae9b5c5",
3
+ "version": "0.8.0-dev.164+250237ef",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",