@optique/core 1.2.0-dev.2244 → 1.2.0-dev.2248

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.
@@ -251,14 +251,44 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
251
251
  ] : baseError;
252
252
  }
253
253
  /**
254
+ * Appends a "Did you mean …?" hint to a base message from a pre-filtered list
255
+ * of suggestion strings.
256
+ *
257
+ * Unlike {@link appendValueHint}, this function accepts suggestions that have
258
+ * already been selected by the caller—it only handles formatting and
259
+ * appending. Useful when the caller provides its own distance logic (e.g.,
260
+ * the custom-function form of `choice()`'s `suggest` option).
261
+ *
262
+ * @param base The base error message.
263
+ * @param suggestions The already-filtered suggestion strings to display.
264
+ * @returns `base` with a "Did you mean …?" line appended, or `base` unchanged
265
+ * when `suggestions` is empty.
266
+ * @since 1.2.0
267
+ */
268
+ function appendValueSuggestions(base, suggestions) {
269
+ if (suggestions.length === 0) return base;
270
+ let suggestionMsg;
271
+ if (suggestions.length === 1) suggestionMsg = require_message.message`Did you mean ${require_message.value(suggestions[0])}?`;
272
+ else {
273
+ const parts = [require_message.text("Did you mean one of these?")];
274
+ for (const suggestion of suggestions) parts.push(require_message.lineBreak(), require_message.text(" "), require_message.value(suggestion));
275
+ suggestionMsg = parts;
276
+ }
277
+ return base.length > 0 ? [
278
+ ...base,
279
+ require_message.lineBreak(),
280
+ require_message.lineBreak(),
281
+ ...suggestionMsg
282
+ ] : suggestionMsg;
283
+ }
284
+ /**
254
285
  * Appends a "Did you mean …?" hint to a base error message when the input
255
286
  * is close to one of the candidate values.
256
287
  *
257
288
  * This helper is meant for closed-set value parsers (e.g. `choice()`) that
258
289
  * want to surface near-match suggestions without re-implementing distance
259
- * logic themselves. It wraps {@link findSimilar} and
260
- * {@link createSuggestionMessage} and returns the `base` message unchanged
261
- * when no candidates are close enough.
290
+ * logic themselves. It wraps {@link findSimilar} and returns the `base`
291
+ * message unchanged when no candidates are close enough.
262
292
  *
263
293
  * @param base The base error message to display.
264
294
  * @param input The invalid input the user typed.
@@ -272,24 +302,18 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
272
302
  * ```typescript
273
303
  * const base = message`Invalid color: ${input}.`;
274
304
  * appendValueHint(base, input, ["red", "green", "blue"]);
275
- * // → "Invalid color: redd.\n\nDid you mean `red`?"
305
+ * // → "Invalid color: redd.\n\nDid you mean \"red\"?"
276
306
  * ```
277
307
  *
278
308
  * @since 1.2.0
279
309
  */
280
310
  function appendValueHint(base, input, candidates, options) {
281
311
  const suggestions = findSimilar(input, candidates, options != null ? {
282
- ...DEFAULT_FIND_SIMILAR_OPTIONS,
283
- ...options
312
+ maxDistance: options.maxDistance ?? DEFAULT_FIND_SIMILAR_OPTIONS.maxDistance,
313
+ maxDistanceRatio: DEFAULT_FIND_SIMILAR_OPTIONS.maxDistanceRatio,
314
+ maxSuggestions: options.maxSuggestions ?? DEFAULT_FIND_SIMILAR_OPTIONS.maxSuggestions
284
315
  } : void 0);
285
- const suggestionMsg = createSuggestionMessage(suggestions);
286
- if (suggestionMsg.length === 0) return base;
287
- return base.length > 0 ? [
288
- ...base,
289
- require_message.lineBreak(),
290
- require_message.lineBreak(),
291
- ...suggestionMsg
292
- ] : suggestionMsg;
316
+ return appendValueSuggestions(base, suggestions);
293
317
  }
294
318
  /**
295
319
  * Creates a unique key for a suggestion to enable deduplication.
@@ -355,6 +379,7 @@ function deduplicateSuggestions(suggestions) {
355
379
  //#endregion
356
380
  exports.DEFAULT_FIND_SIMILAR_OPTIONS = DEFAULT_FIND_SIMILAR_OPTIONS;
357
381
  exports.appendValueHint = appendValueHint;
382
+ exports.appendValueSuggestions = appendValueSuggestions;
358
383
  exports.createErrorWithSuggestions = createErrorWithSuggestions;
359
384
  exports.createSuggestionMessage = createSuggestionMessage;
360
385
  exports.deduplicateSuggestions = deduplicateSuggestions;
@@ -154,15 +154,30 @@ declare function expandCommandAliasSuggestions(usage: Usage, suggestions: readon
154
154
  * @since 0.7.0
155
155
  */
156
156
  declare function createErrorWithSuggestions(baseError: Message, invalidInput: string, usage: Usage, type?: "option" | "command" | "both", customFormatter?: (suggestions: readonly string[]) => Message): Message;
157
+ /**
158
+ * Appends a "Did you mean …?" hint to a base message from a pre-filtered list
159
+ * of suggestion strings.
160
+ *
161
+ * Unlike {@link appendValueHint}, this function accepts suggestions that have
162
+ * already been selected by the caller—it only handles formatting and
163
+ * appending. Useful when the caller provides its own distance logic (e.g.,
164
+ * the custom-function form of `choice()`'s `suggest` option).
165
+ *
166
+ * @param base The base error message.
167
+ * @param suggestions The already-filtered suggestion strings to display.
168
+ * @returns `base` with a "Did you mean …?" line appended, or `base` unchanged
169
+ * when `suggestions` is empty.
170
+ * @since 1.2.0
171
+ */
172
+ declare function appendValueSuggestions(base: Message, suggestions: readonly string[]): Message;
157
173
  /**
158
174
  * Appends a "Did you mean …?" hint to a base error message when the input
159
175
  * is close to one of the candidate values.
160
176
  *
161
177
  * This helper is meant for closed-set value parsers (e.g. `choice()`) that
162
178
  * want to surface near-match suggestions without re-implementing distance
163
- * logic themselves. It wraps {@link findSimilar} and
164
- * {@link createSuggestionMessage} and returns the `base` message unchanged
165
- * when no candidates are close enough.
179
+ * logic themselves. It wraps {@link findSimilar} and returns the `base`
180
+ * message unchanged when no candidates are close enough.
166
181
  *
167
182
  * @param base The base error message to display.
168
183
  * @param input The invalid input the user typed.
@@ -176,7 +191,7 @@ declare function createErrorWithSuggestions(baseError: Message, invalidInput: st
176
191
  * ```typescript
177
192
  * const base = message`Invalid color: ${input}.`;
178
193
  * appendValueHint(base, input, ["red", "green", "blue"]);
179
- * // → "Invalid color: redd.\n\nDid you mean `red`?"
194
+ * // → "Invalid color: redd.\n\nDid you mean \"red\"?"
180
195
  * ```
181
196
  *
182
197
  * @since 1.2.0
@@ -213,4 +228,4 @@ declare function appendValueHint(base: Message, input: string, candidates: reado
213
228
  */
214
229
  declare function deduplicateSuggestions(suggestions: readonly Suggestion[]): Suggestion[];
215
230
  //#endregion
216
- export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, appendValueHint, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
231
+ export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, appendValueHint, appendValueSuggestions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
@@ -154,15 +154,30 @@ declare function expandCommandAliasSuggestions(usage: Usage, suggestions: readon
154
154
  * @since 0.7.0
155
155
  */
156
156
  declare function createErrorWithSuggestions(baseError: Message, invalidInput: string, usage: Usage, type?: "option" | "command" | "both", customFormatter?: (suggestions: readonly string[]) => Message): Message;
157
+ /**
158
+ * Appends a "Did you mean …?" hint to a base message from a pre-filtered list
159
+ * of suggestion strings.
160
+ *
161
+ * Unlike {@link appendValueHint}, this function accepts suggestions that have
162
+ * already been selected by the caller—it only handles formatting and
163
+ * appending. Useful when the caller provides its own distance logic (e.g.,
164
+ * the custom-function form of `choice()`'s `suggest` option).
165
+ *
166
+ * @param base The base error message.
167
+ * @param suggestions The already-filtered suggestion strings to display.
168
+ * @returns `base` with a "Did you mean …?" line appended, or `base` unchanged
169
+ * when `suggestions` is empty.
170
+ * @since 1.2.0
171
+ */
172
+ declare function appendValueSuggestions(base: Message, suggestions: readonly string[]): Message;
157
173
  /**
158
174
  * Appends a "Did you mean …?" hint to a base error message when the input
159
175
  * is close to one of the candidate values.
160
176
  *
161
177
  * This helper is meant for closed-set value parsers (e.g. `choice()`) that
162
178
  * want to surface near-match suggestions without re-implementing distance
163
- * logic themselves. It wraps {@link findSimilar} and
164
- * {@link createSuggestionMessage} and returns the `base` message unchanged
165
- * when no candidates are close enough.
179
+ * logic themselves. It wraps {@link findSimilar} and returns the `base`
180
+ * message unchanged when no candidates are close enough.
166
181
  *
167
182
  * @param base The base error message to display.
168
183
  * @param input The invalid input the user typed.
@@ -176,7 +191,7 @@ declare function createErrorWithSuggestions(baseError: Message, invalidInput: st
176
191
  * ```typescript
177
192
  * const base = message`Invalid color: ${input}.`;
178
193
  * appendValueHint(base, input, ["red", "green", "blue"]);
179
- * // → "Invalid color: redd.\n\nDid you mean `red`?"
194
+ * // → "Invalid color: redd.\n\nDid you mean \"red\"?"
180
195
  * ```
181
196
  *
182
197
  * @since 1.2.0
@@ -213,4 +228,4 @@ declare function appendValueHint(base: Message, input: string, candidates: reado
213
228
  */
214
229
  declare function deduplicateSuggestions(suggestions: readonly Suggestion[]): Suggestion[];
215
230
  //#endregion
216
- export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, appendValueHint, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
231
+ export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, appendValueHint, appendValueSuggestions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
@@ -1,4 +1,4 @@
1
- import { lineBreak, message, optionName, text } from "./message.js";
1
+ import { lineBreak, message, optionName, text, value } from "./message.js";
2
2
  import { extractCommandNames, extractOptionNames, isSuggestionHidden } from "./usage.js";
3
3
 
4
4
  //#region src/suggestion.ts
@@ -251,14 +251,44 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
251
251
  ] : baseError;
252
252
  }
253
253
  /**
254
+ * Appends a "Did you mean …?" hint to a base message from a pre-filtered list
255
+ * of suggestion strings.
256
+ *
257
+ * Unlike {@link appendValueHint}, this function accepts suggestions that have
258
+ * already been selected by the caller—it only handles formatting and
259
+ * appending. Useful when the caller provides its own distance logic (e.g.,
260
+ * the custom-function form of `choice()`'s `suggest` option).
261
+ *
262
+ * @param base The base error message.
263
+ * @param suggestions The already-filtered suggestion strings to display.
264
+ * @returns `base` with a "Did you mean …?" line appended, or `base` unchanged
265
+ * when `suggestions` is empty.
266
+ * @since 1.2.0
267
+ */
268
+ function appendValueSuggestions(base, suggestions) {
269
+ if (suggestions.length === 0) return base;
270
+ let suggestionMsg;
271
+ if (suggestions.length === 1) suggestionMsg = message`Did you mean ${value(suggestions[0])}?`;
272
+ else {
273
+ const parts = [text("Did you mean one of these?")];
274
+ for (const suggestion of suggestions) parts.push(lineBreak(), text(" "), value(suggestion));
275
+ suggestionMsg = parts;
276
+ }
277
+ return base.length > 0 ? [
278
+ ...base,
279
+ lineBreak(),
280
+ lineBreak(),
281
+ ...suggestionMsg
282
+ ] : suggestionMsg;
283
+ }
284
+ /**
254
285
  * Appends a "Did you mean …?" hint to a base error message when the input
255
286
  * is close to one of the candidate values.
256
287
  *
257
288
  * This helper is meant for closed-set value parsers (e.g. `choice()`) that
258
289
  * want to surface near-match suggestions without re-implementing distance
259
- * logic themselves. It wraps {@link findSimilar} and
260
- * {@link createSuggestionMessage} and returns the `base` message unchanged
261
- * when no candidates are close enough.
290
+ * logic themselves. It wraps {@link findSimilar} and returns the `base`
291
+ * message unchanged when no candidates are close enough.
262
292
  *
263
293
  * @param base The base error message to display.
264
294
  * @param input The invalid input the user typed.
@@ -272,24 +302,18 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
272
302
  * ```typescript
273
303
  * const base = message`Invalid color: ${input}.`;
274
304
  * appendValueHint(base, input, ["red", "green", "blue"]);
275
- * // → "Invalid color: redd.\n\nDid you mean `red`?"
305
+ * // → "Invalid color: redd.\n\nDid you mean \"red\"?"
276
306
  * ```
277
307
  *
278
308
  * @since 1.2.0
279
309
  */
280
310
  function appendValueHint(base, input, candidates, options) {
281
311
  const suggestions = findSimilar(input, candidates, options != null ? {
282
- ...DEFAULT_FIND_SIMILAR_OPTIONS,
283
- ...options
312
+ maxDistance: options.maxDistance ?? DEFAULT_FIND_SIMILAR_OPTIONS.maxDistance,
313
+ maxDistanceRatio: DEFAULT_FIND_SIMILAR_OPTIONS.maxDistanceRatio,
314
+ maxSuggestions: options.maxSuggestions ?? DEFAULT_FIND_SIMILAR_OPTIONS.maxSuggestions
284
315
  } : void 0);
285
- const suggestionMsg = createSuggestionMessage(suggestions);
286
- if (suggestionMsg.length === 0) return base;
287
- return base.length > 0 ? [
288
- ...base,
289
- lineBreak(),
290
- lineBreak(),
291
- ...suggestionMsg
292
- ] : suggestionMsg;
316
+ return appendValueSuggestions(base, suggestions);
293
317
  }
294
318
  /**
295
319
  * Creates a unique key for a suggestion to enable deduplication.
@@ -353,4 +377,4 @@ function deduplicateSuggestions(suggestions) {
353
377
  }
354
378
 
355
379
  //#endregion
356
- export { DEFAULT_FIND_SIMILAR_OPTIONS, appendValueHint, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
380
+ export { DEFAULT_FIND_SIMILAR_OPTIONS, appendValueHint, appendValueSuggestions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
@@ -151,8 +151,8 @@ function choice(choices, options = {}) {
151
151
  }
152
152
  if (isValidObject) {
153
153
  const obj = stringSuggest;
154
- if (obj.maxDistance !== void 0 && (typeof obj.maxDistance !== "number" || !isFinite(obj.maxDistance))) throw new TypeError(`Expected suggest.maxDistance to be a finite number, but got ${typeof obj.maxDistance}: ${String(obj.maxDistance)}.`);
155
- if (obj.maxSuggestions !== void 0 && (typeof obj.maxSuggestions !== "number" || !isFinite(obj.maxSuggestions))) throw new TypeError(`Expected suggest.maxSuggestions to be a finite number, but got ${typeof obj.maxSuggestions}: ${String(obj.maxSuggestions)}.`);
154
+ if (obj.maxDistance !== void 0 && (typeof obj.maxDistance !== "number" || !Number.isInteger(obj.maxDistance) || obj.maxDistance < 0)) throw new TypeError(`Expected suggest.maxDistance to be a non-negative integer, but got ${typeof obj.maxDistance}: ${String(obj.maxDistance)}.`);
155
+ if (obj.maxSuggestions !== void 0 && (typeof obj.maxSuggestions !== "number" || !Number.isInteger(obj.maxSuggestions) || obj.maxSuggestions < 1)) throw new TypeError(`Expected suggest.maxSuggestions to be a positive integer, but got ${typeof obj.maxSuggestions}: ${String(obj.maxSuggestions)}.`);
156
156
  }
157
157
  }
158
158
  return {
@@ -278,14 +278,8 @@ function formatStringChoiceError(input, choices, invalidChoice, suggest) {
278
278
  if (suggest === "nearest") return require_suggestion.appendValueHint(base, input, choices);
279
279
  if (typeof suggest === "function") {
280
280
  const hints = suggest(input, choices);
281
- if (!Array.isArray(hints) || hints.length === 0) return base;
282
- const suggestionMsg = require_suggestion.createSuggestionMessage(hints);
283
- return suggestionMsg.length > 0 ? [
284
- ...base,
285
- require_message.lineBreak(),
286
- require_message.lineBreak(),
287
- ...suggestionMsg
288
- ] : base;
281
+ if (!Array.isArray(hints)) return base;
282
+ return require_suggestion.appendValueSuggestions(base, hints);
289
283
  }
290
284
  return require_suggestion.appendValueHint(base, input, choices, suggest);
291
285
  }
@@ -1,6 +1,6 @@
1
1
  import { cloneMessage, lineBreak, message, metavar, text, valueSet } from "./message.js";
2
2
  import { isDerivedValueParser } from "./internal/dependency.js";
3
- import { appendValueHint, createSuggestionMessage, deduplicateSuggestions } from "./suggestion.js";
3
+ import { appendValueHint, appendValueSuggestions, deduplicateSuggestions } from "./suggestion.js";
4
4
  import { ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
5
5
 
6
6
  //#region src/valueparser.ts
@@ -151,8 +151,8 @@ function choice(choices, options = {}) {
151
151
  }
152
152
  if (isValidObject) {
153
153
  const obj = stringSuggest;
154
- if (obj.maxDistance !== void 0 && (typeof obj.maxDistance !== "number" || !isFinite(obj.maxDistance))) throw new TypeError(`Expected suggest.maxDistance to be a finite number, but got ${typeof obj.maxDistance}: ${String(obj.maxDistance)}.`);
155
- if (obj.maxSuggestions !== void 0 && (typeof obj.maxSuggestions !== "number" || !isFinite(obj.maxSuggestions))) throw new TypeError(`Expected suggest.maxSuggestions to be a finite number, but got ${typeof obj.maxSuggestions}: ${String(obj.maxSuggestions)}.`);
154
+ if (obj.maxDistance !== void 0 && (typeof obj.maxDistance !== "number" || !Number.isInteger(obj.maxDistance) || obj.maxDistance < 0)) throw new TypeError(`Expected suggest.maxDistance to be a non-negative integer, but got ${typeof obj.maxDistance}: ${String(obj.maxDistance)}.`);
155
+ if (obj.maxSuggestions !== void 0 && (typeof obj.maxSuggestions !== "number" || !Number.isInteger(obj.maxSuggestions) || obj.maxSuggestions < 1)) throw new TypeError(`Expected suggest.maxSuggestions to be a positive integer, but got ${typeof obj.maxSuggestions}: ${String(obj.maxSuggestions)}.`);
156
156
  }
157
157
  }
158
158
  return {
@@ -278,14 +278,8 @@ function formatStringChoiceError(input, choices, invalidChoice, suggest) {
278
278
  if (suggest === "nearest") return appendValueHint(base, input, choices);
279
279
  if (typeof suggest === "function") {
280
280
  const hints = suggest(input, choices);
281
- if (!Array.isArray(hints) || hints.length === 0) return base;
282
- const suggestionMsg = createSuggestionMessage(hints);
283
- return suggestionMsg.length > 0 ? [
284
- ...base,
285
- lineBreak(),
286
- lineBreak(),
287
- ...suggestionMsg
288
- ] : base;
281
+ if (!Array.isArray(hints)) return base;
282
+ return appendValueSuggestions(base, hints);
289
283
  }
290
284
  return appendValueHint(base, input, choices, suggest);
291
285
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.2.0-dev.2244",
3
+ "version": "1.2.0-dev.2248",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",
@@ -216,7 +216,7 @@
216
216
  "fast-check": "^4.7.0",
217
217
  "tsdown": "^0.13.0",
218
218
  "typescript": "^5.8.3",
219
- "@optique/env": "1.2.0-dev.2244+b3c70b3c"
219
+ "@optique/env": "1.2.0-dev.2248+e16654a6"
220
220
  },
221
221
  "scripts": {
222
222
  "build": "tsdown",