cli-kiss 0.2.7 → 0.2.8

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.
Files changed (39) hide show
  1. package/dist/index.d.ts +127 -137
  2. package/dist/index.js +2 -2
  3. package/dist/index.js.map +1 -1
  4. package/docs/.vitepress/config.mts +1 -1
  5. package/docs/.vitepress/theme/Layout.vue +16 -0
  6. package/docs/.vitepress/theme/index.ts +5 -1
  7. package/docs/.vitepress/theme/style.css +5 -1
  8. package/docs/guide/02_commands.md +1 -1
  9. package/docs/guide/03_options.md +11 -11
  10. package/docs/guide/05_input_types.md +9 -10
  11. package/docs/guide/06_run_as_cli.md +1 -1
  12. package/docs/index.md +2 -2
  13. package/docs/public/favicon.ico +0 -0
  14. package/docs/public/logo.png +0 -0
  15. package/package.json +1 -1
  16. package/src/index.ts +1 -1
  17. package/src/lib/Command.ts +45 -39
  18. package/src/lib/Operation.ts +28 -20
  19. package/src/lib/Option.ts +196 -127
  20. package/src/lib/Positional.ts +44 -23
  21. package/src/lib/Reader.ts +194 -226
  22. package/src/lib/Run.ts +19 -8
  23. package/src/lib/Suggest.ts +78 -0
  24. package/src/lib/Type.ts +36 -37
  25. package/src/lib/Typo.ts +58 -55
  26. package/src/lib/Usage.ts +12 -12
  27. package/tests/unit.Reader.commons.ts +92 -116
  28. package/tests/unit.Reader.parsings.ts +14 -26
  29. package/tests/unit.Reader.shortBig.ts +81 -96
  30. package/tests/unit.command.aliases.ts +100 -0
  31. package/tests/unit.command.execute.ts +1 -1
  32. package/tests/unit.command.usage.ts +12 -6
  33. package/tests/unit.fuzzed.alternatives.ts +35 -26
  34. package/tests/unit.runner.colors.ts +8 -33
  35. package/tests/unit.runner.cycle.ts +118 -146
  36. package/tests/unit.runner.errors.ts +25 -22
  37. package/docs/public/hero.png +0 -0
  38. package/src/lib/Similarity.ts +0 -41
  39. package/tests/unit.Reader.aliases.ts +0 -62
@@ -0,0 +1,78 @@
1
+ import { TypoSegment, TypoString, TypoText } from "./Typo";
2
+
3
+ export function suggestTextPushMessage(
4
+ text: TypoText,
5
+ query: string,
6
+ candidates: Array<{ reference: string; hint: TypoSegment }>,
7
+ ) {
8
+ const reasonableHints = suggestReasonablePayloads(
9
+ query,
10
+ candidates.map(({ reference, hint }) => ({ reference, payload: hint })),
11
+ );
12
+ if (reasonableHints.length === 0) {
13
+ return;
14
+ }
15
+ text.push(new TypoString(" Did you mean: "));
16
+ text.pushJoined(reasonableHints, new TypoString(", "), 3);
17
+ text.push(new TypoString(` ?`));
18
+ }
19
+
20
+ function suggestReasonablePayloads<Payload>(
21
+ query: string,
22
+ candidates: Array<{ reference: string; payload: Payload }>,
23
+ ): Array<Payload> {
24
+ if (candidates.length === 0) {
25
+ return [];
26
+ }
27
+ const sortedAlternatives = computeAndSortByDivergences(query, candidates);
28
+ const divergenceThreshold = sortedAlternatives[0]!.divergence + 0.25;
29
+ const acceptablePayloads = new Array<Payload>();
30
+ for (const { divergence, payload } of sortedAlternatives) {
31
+ if (divergence > divergenceThreshold) {
32
+ break;
33
+ }
34
+ acceptablePayloads.push(payload);
35
+ }
36
+ return acceptablePayloads;
37
+ }
38
+
39
+ function computeAndSortByDivergences<Payload>(
40
+ query: string,
41
+ candidates: Array<{ reference: string; payload: Payload }>,
42
+ ): Array<{ divergence: number; payload: Payload }> {
43
+ const queryNormalized = query.toLowerCase().slice(0, 100);
44
+ const scored = candidates.map(({ reference, payload }) => {
45
+ const referenceNormalized = reference.toLowerCase().slice(0, 100);
46
+ const divergence =
47
+ distanceDamerauLevenshtein(queryNormalized, referenceNormalized) /
48
+ Math.max(queryNormalized.length, referenceNormalized.length);
49
+ return { divergence, reference, payload };
50
+ });
51
+ return scored.sort((a, b) => a.divergence - b.divergence);
52
+ }
53
+
54
+ function distanceDamerauLevenshtein(a: string, b: string): number {
55
+ const m = a.length;
56
+ const n = b.length;
57
+ const dp = Array.from({ length: m + 1 }, () => Array<number>(n + 1).fill(0));
58
+ for (let i = 0; i <= m; i++) {
59
+ dp[i]![0] = i;
60
+ }
61
+ for (let j = 0; j <= n; j++) {
62
+ dp[0]![j] = j;
63
+ }
64
+ for (let i = 1; i <= m; i++) {
65
+ for (let j = 1; j <= n; j++) {
66
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
67
+ dp[i]![j] = Math.min(
68
+ dp[i - 1]![j]! + 1,
69
+ dp[i]![j - 1]! + 1,
70
+ dp[i - 1]![j - 1]! + cost,
71
+ );
72
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
73
+ dp[i]![j] = Math.min(dp[i]![j]!, dp[i - 2]![j - 2]! + cost);
74
+ }
75
+ }
76
+ }
77
+ return dp[m]![n]!;
78
+ }
package/src/lib/Type.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { statSync } from "fs";
2
- import { similaritySort } from "./Similarity";
2
+ import { suggestTextPushMessage } from "./Suggest";
3
3
  import {
4
4
  TypoError,
5
5
  TypoString,
@@ -20,15 +20,15 @@ import {
20
20
  */
21
21
  export type Type<Value> = {
22
22
  /**
23
- * Human-readable name shown in help and errors (e.g. `"name"`, `"number"`).
23
+ * Human-readable name shown in help and errors (e.g. `"name"`, `"context"`).
24
24
  */
25
- content: string;
25
+ content: string; // TODO - add an enforcement mechanism for casing ?
26
26
  /**
27
27
  * Decodes a raw CLI string into `Value`.
28
28
  *
29
29
  * @param input - Raw string from the command line.
30
30
  * @returns The decoded value.
31
- * @throws {@link TypoError} on invalid input.
31
+ * @throws on invalid input.
32
32
  */
33
33
  decoder(input: string): Value;
34
34
  };
@@ -62,8 +62,8 @@ export function typeBoolean(name?: string): Type<boolean> {
62
62
  },
63
63
  };
64
64
  }
65
- export const typeBooleanValuesTrue = new Set(["true", "yes", "on", "y"]);
66
- export const typeBooleanValuesFalse = new Set(["false", "no", "off", "n"]);
65
+ const typeBooleanValuesTrue = new Set(["true", "yes", "on", "y"]);
66
+ const typeBooleanValuesFalse = new Set(["false", "no", "off", "n"]);
67
67
 
68
68
  /**
69
69
  * Parses a date/time string via `Date.parse`.
@@ -94,7 +94,7 @@ export function typeDatetime(name?: string): Type<Date> {
94
94
  }
95
95
 
96
96
  /**
97
- * Parses a string to `number` via `Number()`; `NaN` throws {@link TypoError}.
97
+ * Parses a string to `number` via `Number()`; `NaN` throws.
98
98
  *
99
99
  * @example
100
100
  * ```ts
@@ -122,7 +122,7 @@ export function typeNumber(name?: string): Type<number> {
122
122
 
123
123
  /**
124
124
  * Parses an integer string to `bigint` via `BigInt()`.
125
- * Floats and non-numeric strings throw {@link TypoError}.
125
+ * Floats and non-numeric strings throws.
126
126
  *
127
127
  * @example
128
128
  * ```ts
@@ -146,7 +146,7 @@ export function typeInteger(name?: string): Type<bigint> {
146
146
 
147
147
  /**
148
148
  * Parses an absolute URL string to a `URL` object.
149
- * Relative or malformed URLs throw {@link TypoError}.
149
+ * Relative or malformed URLs throws.
150
150
  *
151
151
  * @example
152
152
  * ```ts
@@ -315,7 +315,6 @@ export function typePath(
315
315
 
316
316
  /**
317
317
  * Creates a {@link Type}`<string>` that only accepts a fixed set of values.
318
- * Out-of-set inputs throw {@link TypoError} listing up to 5 valid options.
319
318
  *
320
319
  * @param name - Name shown in help and errors.
321
320
  * @param values - Ordered list of accepted values.
@@ -325,52 +324,50 @@ export function typePath(
325
324
  * ```ts
326
325
  * const typeEnv = typeChoice("environment", ["dev", "staging", "prod"]);
327
326
  * typeEnv.decoder("prod") // → "prod"
328
- * typeEnv.decoder("unknown") // throws TypoError: Invalid value: "unknown" (expected one of: "dev" | "staging" | "prod")
327
+ * typeEnv.decoder("unknown") // throws
329
328
  * ```
330
329
  */
331
330
  export function typeChoice<const Value extends string>(
332
331
  name: string,
333
332
  values: Array<Value>,
334
- caseSensitive: boolean = false,
333
+ caseSensitive: boolean = true,
335
334
  ): Type<Value> {
336
335
  if (values.length === 0) {
337
336
  throw new Error("At least one value is required");
338
337
  }
339
338
  const normalize = caseSensitive
340
- ? (s: string) => s
341
- : (s: string) => s.toLowerCase();
342
- const valueByNormalizedKey = new Map(
339
+ ? (input: string) => input
340
+ : (input: string) => input.toLowerCase();
341
+ const valueByNormalized = new Map(
343
342
  values.map((value) => [normalize(value), value]),
344
343
  );
345
344
  return {
346
345
  content: name,
347
346
  decoder(input: string) {
348
- const normalizedKey = normalize(input);
349
- const value = valueByNormalizedKey.get(normalizedKey);
347
+ const value = valueByNormalized.get(normalize(input));
350
348
  if (value !== undefined) {
351
349
  return value;
352
350
  }
353
- const text = new TypoText();
354
- text.push(new TypoString(`Unknown value: `));
355
- text.push(new TypoString(`"${input}"`, typoStyleQuote));
356
- const suggestions = similaritySort(
357
- normalizedKey,
358
- [...valueByNormalizedKey.entries()].map(([normalizedKey, value]) => ({
359
- key: normalizedKey,
360
- value: new TypoString(`"${value}"`, typoStyleQuote),
351
+ const errorText = new TypoText();
352
+ errorText.push(new TypoString(`Unknown value: `));
353
+ errorText.push(new TypoString(`"${input}"`, typoStyleQuote));
354
+ errorText.push(new TypoString(`.`));
355
+ suggestTextPushMessage(
356
+ errorText,
357
+ input,
358
+ values.map((value) => ({
359
+ reference: value,
360
+ hint: new TypoString(`"${value}"`, typoStyleQuote),
361
361
  })),
362
- ).slice(0, 3);
363
- text.push(new TypoString(`: did you mean: `));
364
- text.push(TypoText.join(suggestions, new TypoString(`, `)));
365
- text.push(new TypoString(` ?`));
366
- throw new TypoError(text);
362
+ );
363
+ throw new TypoError(errorText);
367
364
  },
368
365
  };
369
366
  }
370
367
 
371
368
  /**
372
369
  * Splits a delimited string into a typed tuple.
373
- * Each part is decoded by the corresponding element type; wrong count or decode failure throws {@link TypoError}.
370
+ * Each part is decoded by the corresponding element type; wrong count or decode failure throws.
374
371
  *
375
372
  * @typeParam Elements - Tuple of decoded value types (inferred from `elementTypes`).
376
373
  *
@@ -382,8 +379,9 @@ export function typeChoice<const Value extends string>(
382
379
  * ```ts
383
380
  * const typePoint = typeTuple([typeNumber("x"), typeNumber("y")]);
384
381
  * typePoint.decoder("3.14,2.71") // → [3.14, 2.71]
385
- * typePoint.decoder("1,2,3") // → [1, 2]
386
- * typePoint.decoder("x,2") // throws TypoError: at 0: Number: Unable to parse: "x"
382
+ * typePoint.decoder("1") // throws
383
+ * typePoint.decoder("1,2,3") // throws
384
+ * typePoint.decoder("x,2") // throws
387
385
  * ```
388
386
  */
389
387
  export function typeTuple<const Elements extends Array<any>>(
@@ -402,6 +400,7 @@ export function typeTuple<const Elements extends Array<any>>(
402
400
  new TypoString(`Found ${splits.length} splits: `),
403
401
  new TypoString(`Expected ${elementTypes.length} splits from: `),
404
402
  new TypoString(`"${input}"`, typoStyleQuote),
403
+ new TypoString(`.`),
405
404
  ),
406
405
  );
407
406
  }
@@ -422,7 +421,7 @@ export function typeTuple<const Elements extends Array<any>>(
422
421
 
423
422
  /**
424
423
  * Splits a delimited string into a typed array.
425
- * Each part is decoded by `elementType`; failed decodes throw {@link TypoError}.
424
+ * Each part is decoded by `elementType`; failed decodes throws.
426
425
  * Note: splitting an empty string yields one empty element — prefer {@link optionRepeatable} for a zero-default.
427
426
  *
428
427
  * @typeParam Value - Element type produced by `elementType.decoder`.
@@ -433,10 +432,9 @@ export function typeTuple<const Elements extends Array<any>>(
433
432
  *
434
433
  * @example
435
434
  * ```ts
436
- * const typeNumbers = typeList(typeNumber);
435
+ * const typeNumbers = typeList(typeNumber("v"));
437
436
  * typeNumbers.decoder("1,2,3") // → [1, 2, 3]
438
- * typeNumbers.decoder("1,x,3") // throws TypoError: at 1: Number: Unable to parse: "x"
439
- *
437
+ * typeNumbers.decoder("1,x,3") // throws
440
438
  * const typePaths = typeList(typePath(), ":");
441
439
  * typePaths.decoder("/usr/bin:/usr/local/bin") // → ["/usr/bin", "/usr/local/bin"]
442
440
  * ```
@@ -468,6 +466,7 @@ function throwInvalidValue(kind: string, input: string): never {
468
466
  new TypoText(
469
467
  new TypoString(`Not ${kind}: `),
470
468
  new TypoString(`"${input}"`, typoStyleQuote),
469
+ new TypoString(`.`),
471
470
  ),
472
471
  );
473
472
  }
package/src/lib/Typo.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { typeBooleanValuesFalse } from "./Type";
2
-
3
1
  /**
4
2
  * Color names for terminal styling, used by {@link TypoStyle}.
5
3
  * `dark*` = standard ANSI (30–37); `bright*` = high-intensity (90–97).
@@ -125,14 +123,14 @@ export const typoStyleRegularWeaker: TypoStyle = {
125
123
  */
126
124
  export class TypoString {
127
125
  #value: string;
128
- #typoStyle: TypoStyle | undefined;
126
+ #style: TypoStyle | undefined;
129
127
  /**
130
128
  * @param value - Raw text content.
131
- * @param typoStyle - Style to apply when rendering. Defaults to `undefined` (no style).
129
+ * @param style - Style to apply when rendering.
132
130
  */
133
- constructor(value: string, typoStyle?: TypoStyle) {
131
+ constructor(value: string, style?: TypoStyle) {
134
132
  this.#value = value;
135
- this.#typoStyle = typoStyle;
133
+ this.#style = style;
136
134
  }
137
135
  /**
138
136
  * Returns the text styled by `typoSupport`.
@@ -140,51 +138,75 @@ export class TypoString {
140
138
  * @param typoSupport - Rendering mode.
141
139
  */
142
140
  computeStyledString(typoSupport: TypoSupport): string {
143
- return typoSupport.computeStyledString(this.#value, this.#typoStyle);
141
+ return typoSupport.computeStyledString(this.#value, this.#style);
144
142
  }
145
143
  /**
146
144
  * Returns the raw text.
147
145
  */
148
146
  getRawString(): string {
147
+ // TODO - should there be a global config or smthg instead of passing support everywhere?
149
148
  return this.#value;
150
149
  }
150
+ /**
151
+ * Predefined ellipsis segment with subtle styling.
152
+ */
153
+ static elipsis = new TypoString("...", typoStyleRegularWeaker);
151
154
  }
152
155
 
153
156
  /**
154
157
  * A segment of styled text, a string, or an array of segments.
155
158
  */
156
- export type TypoSegment = TypoText | TypoString | string | Array<TypoSegment>;
159
+ export type TypoSegment = TypoText | TypoString | Array<TypoSegment>;
157
160
 
158
161
  /**
159
162
  * Mutable sequence of {@link TypoString} segments.
160
163
  */
161
164
  export class TypoText {
162
- #typoStrings: Array<TypoString>;
165
+ #strings: Array<TypoString>;
163
166
  /**
164
167
  * @param segments - Initial text segments
165
168
  */
166
- constructor(...segments: TypoSegment[]) {
167
- this.#typoStrings = [];
169
+ constructor(...segments: Array<TypoSegment>) {
170
+ this.#strings = [];
168
171
  for (const segment of segments) {
169
172
  this.push(segment);
170
173
  }
171
174
  }
172
175
  /**
173
176
  * Appends new text segment(s).
174
- *
175
- * @param segment - Text segment(s) to append.
176
177
  */
177
- push(segment: TypoSegment) {
178
- if (typeof segment === "string") {
179
- this.#typoStrings.push(new TypoString(segment));
180
- } else if (segment instanceof TypoText) {
181
- this.#typoStrings.push(...segment.#typoStrings);
182
- } else if (Array.isArray(segment)) {
183
- for (const typoString of segment) {
184
- this.push(typoString);
178
+ push(...segments: Array<TypoSegment>): void {
179
+ for (const segment of segments) {
180
+ if (typeof segment === "string") {
181
+ this.#strings.push(new TypoString(segment));
182
+ } else if (segment instanceof TypoString) {
183
+ this.#strings.push(segment);
184
+ } else if (segment instanceof TypoText) {
185
+ this.#strings.push(...segment.#strings);
186
+ } else if (Array.isArray(segment)) {
187
+ for (const part of segment) {
188
+ this.push(part);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ /**
194
+ * Appends separated segments, optionally truncating with an ellipsis.
195
+ */
196
+ pushJoined(
197
+ segments: Array<TypoSegment>,
198
+ separator: TypoSegment,
199
+ ellipsisLimit: number,
200
+ ): void {
201
+ for (let index = 0; index < segments.length; index++) {
202
+ if (index > 0) {
203
+ this.push(separator);
204
+ }
205
+ if (ellipsisLimit !== undefined && index >= ellipsisLimit) {
206
+ this.push(TypoString.elipsis);
207
+ break;
185
208
  }
186
- } else {
187
- this.#typoStrings.push(segment);
209
+ this.push(segments[index]!);
188
210
  }
189
211
  }
190
212
  /**
@@ -194,7 +216,7 @@ export class TypoText {
194
216
  * @returns Concatenated styled string.
195
217
  */
196
218
  computeStyledString(typoSupport: TypoSupport): string {
197
- return this.#typoStrings
219
+ return this.#strings
198
220
  .map((t) => t.computeStyledString(typoSupport))
199
221
  .join("");
200
222
  }
@@ -202,32 +224,18 @@ export class TypoText {
202
224
  * Returns the concatenated raw text.
203
225
  */
204
226
  computeRawString(): string {
205
- return this.#typoStrings.map((t) => t.getRawString()).join("");
227
+ return this.#strings.map((t) => t.getRawString()).join("");
206
228
  }
207
229
  /**
208
230
  * Returns the total raw character count.
209
231
  */
210
232
  computeRawLength(): number {
211
233
  let length = 0;
212
- for (const typoString of this.#typoStrings) {
234
+ for (const typoString of this.#strings) {
213
235
  length += typoString.getRawString().length;
214
236
  }
215
237
  return length;
216
238
  }
217
- /**
218
- * Joins multiple segments with a separator.
219
- * @returns A new {@link TypoText} containing the joined segments.
220
- */
221
- static join(segments: Array<TypoSegment>, separator: TypoSegment): TypoText {
222
- const result = new TypoText();
223
- for (let index = 0; index < segments.length; index++) {
224
- if (index > 0) {
225
- result.push(separator);
226
- }
227
- result.push(segments[index]!);
228
- }
229
- return result;
230
- }
231
239
  }
232
240
 
233
241
  /**
@@ -377,15 +385,6 @@ export class TypoSupport {
377
385
  * Auto-detects styling mode from the process environment on best-effort basis.
378
386
  */
379
387
  static inferFromEnv(): TypoSupport {
380
- /*
381
- console.warn({
382
- no: readEnvVar("NO_COLOR"),
383
- force: readEnvVar("FORCE_COLOR"),
384
- mock: readEnvVar("MOCK_COLOR"),
385
- term: readEnvVar("TERM"),
386
- tty: process.stdout.isTTY,
387
- });
388
- */
389
388
  if (!process || !process.env || !process.stdout) {
390
389
  return TypoSupport.none();
391
390
  }
@@ -396,13 +395,17 @@ export class TypoSupport {
396
395
  if (envForceColor === "0") {
397
396
  return TypoSupport.none();
398
397
  }
399
- if (envForceColor !== undefined) {
400
- if (!typeBooleanValuesFalse.has(envForceColor.toLowerCase())) {
401
- return TypoSupport.tty();
402
- }
398
+ if (envForceColor === "1") {
399
+ return TypoSupport.tty();
400
+ }
401
+ if (envForceColor === "2") {
402
+ return TypoSupport.tty();
403
+ }
404
+ if (envForceColor === "3") {
405
+ return TypoSupport.tty(); // TODO - should there be fancy colors possible?
403
406
  }
404
- if (readEnvVar("MOCK_COLOR")) {
405
- return TypoSupport.mock();
407
+ if (envForceColor) {
408
+ return TypoSupport.tty();
406
409
  }
407
410
  if (!process.stdout.isTTY) {
408
411
  return TypoSupport.none();
package/src/lib/Usage.ts CHANGED
@@ -93,7 +93,7 @@ export type UsageOption = {
93
93
  /**
94
94
  * Extra annotation appended to the option label in help.
95
95
  */
96
- annotation?: string | undefined;
96
+ annotation?: string | undefined; // TODO - maybe prefix/postfix would be more useful
97
97
  /**
98
98
  * Help text.
99
99
  */
@@ -175,7 +175,7 @@ export function usageToStyledLines(params: {
175
175
  lines.push("");
176
176
  const introText = new TypoText();
177
177
  introText.push(textUsageText(usage.information.description));
178
- if (usage.information.hint) {
178
+ if (usage.information.hint !== undefined) {
179
179
  introText.push(textDelimiter(" "));
180
180
  introText.push(textSubtleInfo(`(${usage.information.hint})`));
181
181
  }
@@ -221,7 +221,7 @@ export function usageToStyledLines(params: {
221
221
  for (const optionUsage of usage.options) {
222
222
  const typoGridRow = new Array<TypoText>();
223
223
  typoGridRow.push(new TypoText(textDelimiter(" ")));
224
- if (optionUsage.short) {
224
+ if (optionUsage.short !== undefined) {
225
225
  typoGridRow.push(
226
226
  new TypoText(
227
227
  textConstants(`-${optionUsage.short}`),
@@ -234,11 +234,11 @@ export function usageToStyledLines(params: {
234
234
  const longOptionText = new TypoText(
235
235
  textConstants(`--${optionUsage.long}`),
236
236
  );
237
- if (optionUsage.label) {
237
+ if (optionUsage.label !== undefined) {
238
238
  longOptionText.push(textDelimiter(" "));
239
239
  longOptionText.push(textUserInput(optionUsage.label));
240
240
  }
241
- if (optionUsage.annotation) {
241
+ if (optionUsage.annotation !== undefined) {
242
242
  longOptionText.push(textSubtleInfo(optionUsage.annotation));
243
243
  }
244
244
  typoGridRow.push(longOptionText);
@@ -248,14 +248,14 @@ export function usageToStyledLines(params: {
248
248
  lines.push(...typoGrid.computeStyledLines(typoSupport));
249
249
  }
250
250
 
251
- if (usage.information.examples) {
251
+ if (usage.information.examples !== undefined) {
252
252
  lines.push("");
253
253
  lines.push(textBlockTitle("Examples:").computeStyledString(typoSupport));
254
254
  for (const example of usage.information.examples) {
255
- const exampleExplanationText = new TypoText();
256
- exampleExplanationText.push(textDelimiter(" "));
257
- exampleExplanationText.push(textSubtleInfo(`# ${example.explanation}`));
258
- lines.push(exampleExplanationText.computeStyledString(typoSupport));
255
+ const explanationText = new TypoText();
256
+ explanationText.push(textDelimiter(" "));
257
+ explanationText.push(textSubtleInfo(`# ${example.explanation}`));
258
+ lines.push(explanationText.computeStyledString(typoSupport));
259
259
  const commandLineText = new TypoText();
260
260
  commandLineText.push(textDelimiter(" "));
261
261
  commandLineText.push(textConstants(cliName));
@@ -299,11 +299,11 @@ function createInformationals(usage: {
299
299
  hint?: string | undefined;
300
300
  }): Array<TypoText> {
301
301
  const informationals = [];
302
- if (usage.description) {
302
+ if (usage.description !== undefined) {
303
303
  informationals.push(textDelimiter(" "));
304
304
  informationals.push(textUsefulInfo(usage.description));
305
305
  }
306
- if (usage.hint) {
306
+ if (usage.hint !== undefined) {
307
307
  informationals.push(textDelimiter(" "));
308
308
  informationals.push(textSubtleInfo(`(${usage.hint})`));
309
309
  }