@optique/core 1.0.0-dev.407 → 1.0.0-dev.427

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,6 @@
1
1
  @optique/core
2
2
  =============
3
3
 
4
- > [!WARNING]
5
- > The API is stabilizing, but may change before the 1.0 release.
6
-
7
4
  The core package of Optique which provides the shared types and parser
8
5
  combinators. It is designed to be used in universal JavaScript runtimes,
9
6
  including Node.js, Deno, Bun, edge functions, and web browsers—although
package/dist/doc.cjs CHANGED
@@ -17,12 +17,22 @@ function classifySection(section) {
17
17
  return 2;
18
18
  }
19
19
  /**
20
+ * Scores a section for the default smart sort. Untitled sections receive
21
+ * a bonus of `-1` so that the main (untitled) section appears before titled
22
+ * sections of a similar classification.
23
+ */
24
+ function scoreSection(section) {
25
+ return classifySection(section) + (section.title == null ? -1 : 0);
26
+ }
27
+ /**
20
28
  * The default section comparator: command-only sections come first, then
21
- * mixed sections, then option/argument-only sections. Sections with the
29
+ * mixed sections, then option/argument-only sections. Untitled sections
30
+ * receive a slight priority boost so the main (untitled) section appears
31
+ * before titled sections of a similar classification. Sections with the
22
32
  * same score preserve their original relative order (stable sort).
23
33
  */
24
34
  function defaultSectionOrder(a, b) {
25
- return classifySection(a) - classifySection(b);
35
+ return scoreSection(a) - scoreSection(b);
26
36
  }
27
37
  /**
28
38
  * Formats a documentation page into a human-readable string.
@@ -109,18 +119,37 @@ function formatDocPage(programName, page, options = {}) {
109
119
  optionsSeparator: ", ",
110
120
  maxWidth: options.maxWidth == null ? void 0 : options.maxWidth - termIndent
111
121
  });
112
- let description = entry.description == null ? "" : require_message.formatMessage(entry.description, {
122
+ const descColumnWidth = options.maxWidth == null ? void 0 : options.maxWidth - termIndent - termWidth - 2;
123
+ const termVisibleWidth = lastLineVisibleLength(term);
124
+ const extraTermOffset = descColumnWidth != null ? Math.max(0, termVisibleWidth - termWidth) : 0;
125
+ const currentExtraOffset = () => description.includes("\n") ? 0 : extraTermOffset;
126
+ const descFormatOptions = {
113
127
  colors: options.colors,
114
128
  quotes: !options.colors,
115
- maxWidth: options.maxWidth == null ? void 0 : options.maxWidth - termIndent - termWidth - 2
116
- });
129
+ maxWidth: descColumnWidth,
130
+ startWidth: extraTermOffset > 0 ? extraTermOffset : void 0
131
+ };
132
+ let description = entry.description == null ? "" : require_message.formatMessage(entry.description, descFormatOptions);
117
133
  if (options.showDefault && entry.default != null) {
118
134
  const prefix = typeof options.showDefault === "object" ? options.showDefault.prefix ?? " [" : " [";
119
135
  const suffix = typeof options.showDefault === "object" ? options.showDefault.suffix ?? "]" : "]";
120
- const defaultText = `${prefix}${require_message.formatMessage(entry.default, {
136
+ let defaultStartWidth;
137
+ if (descColumnWidth != null) {
138
+ const lastW = lastLineVisibleLength(description);
139
+ const effectiveLastW = lastW + currentExtraOffset();
140
+ if (effectiveLastW + prefix.length >= descColumnWidth) {
141
+ description += "\n";
142
+ defaultStartWidth = prefix.length;
143
+ } else defaultStartWidth = effectiveLastW + prefix.length;
144
+ }
145
+ const defaultFormatOptions = {
121
146
  colors: options.colors ? { resetSuffix: "\x1B[2m" } : false,
122
- quotes: !options.colors
123
- })}${suffix}`;
147
+ quotes: !options.colors,
148
+ maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - suffix.length,
149
+ startWidth: defaultStartWidth
150
+ };
151
+ const defaultContent = require_message.formatMessage(entry.default, defaultFormatOptions);
152
+ const defaultText = `${prefix}${defaultContent}${suffix}`;
124
153
  const formattedDefault = options.colors ? `\x1b[2m${defaultText}\x1b[0m` : defaultText;
125
154
  description += formattedDefault;
126
155
  }
@@ -145,10 +174,23 @@ function formatDocPage(programName, page, options = {}) {
145
174
  }
146
175
  if (truncated) truncatedTerms = [...terms.slice(0, cutIndex), require_message.text(", ...")];
147
176
  }
148
- const choicesDisplay = require_message.formatMessage(truncatedTerms, {
177
+ let choicesStartWidth;
178
+ if (descColumnWidth != null) {
179
+ const lastW = lastLineVisibleLength(description);
180
+ const effectiveLastW = lastW + currentExtraOffset();
181
+ const prefixLabelLen = prefix.length + label.length;
182
+ if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
183
+ description += "\n";
184
+ choicesStartWidth = prefixLabelLen;
185
+ } else choicesStartWidth = effectiveLastW + prefixLabelLen;
186
+ }
187
+ const choicesFormatOptions = {
149
188
  colors: options.colors ? { resetSuffix: "\x1B[2m" } : false,
150
- quotes: false
151
- });
189
+ quotes: false,
190
+ maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - suffix.length,
191
+ startWidth: choicesStartWidth
192
+ };
193
+ const choicesDisplay = require_message.formatMessage(truncatedTerms, choicesFormatOptions);
152
194
  const choicesText = `${prefix}${label}${choicesDisplay}${suffix}`;
153
195
  const formattedChoices = options.colors ? `\x1b[2m${choicesText}\x1b[0m` : choicesText;
154
196
  description += formattedChoices;
@@ -205,12 +247,17 @@ function formatDocPage(programName, page, options = {}) {
205
247
  function indentLines(text$1, indent) {
206
248
  return text$1.split("\n").join("\n" + " ".repeat(indent));
207
249
  }
250
+ const ansiEscapeCodeRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
208
251
  function ansiAwareRightPad(text$1, length, char = " ") {
209
- const ansiEscapeCodeRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
210
252
  const strippedText = text$1.replace(ansiEscapeCodeRegex, "");
211
253
  if (strippedText.length >= length) return text$1;
212
254
  return text$1 + char.repeat(length - strippedText.length);
213
255
  }
256
+ function lastLineVisibleLength(text$1) {
257
+ const lastNewline = text$1.lastIndexOf("\n");
258
+ const lastLine = lastNewline === -1 ? text$1 : text$1.slice(lastNewline + 1);
259
+ return lastLine.replace(ansiEscapeCodeRegex, "").length;
260
+ }
214
261
 
215
262
  //#endregion
216
263
  exports.formatDocPage = formatDocPage;
package/dist/doc.js CHANGED
@@ -17,12 +17,22 @@ function classifySection(section) {
17
17
  return 2;
18
18
  }
19
19
  /**
20
+ * Scores a section for the default smart sort. Untitled sections receive
21
+ * a bonus of `-1` so that the main (untitled) section appears before titled
22
+ * sections of a similar classification.
23
+ */
24
+ function scoreSection(section) {
25
+ return classifySection(section) + (section.title == null ? -1 : 0);
26
+ }
27
+ /**
20
28
  * The default section comparator: command-only sections come first, then
21
- * mixed sections, then option/argument-only sections. Sections with the
29
+ * mixed sections, then option/argument-only sections. Untitled sections
30
+ * receive a slight priority boost so the main (untitled) section appears
31
+ * before titled sections of a similar classification. Sections with the
22
32
  * same score preserve their original relative order (stable sort).
23
33
  */
24
34
  function defaultSectionOrder(a, b) {
25
- return classifySection(a) - classifySection(b);
35
+ return scoreSection(a) - scoreSection(b);
26
36
  }
27
37
  /**
28
38
  * Formats a documentation page into a human-readable string.
@@ -109,18 +119,37 @@ function formatDocPage(programName, page, options = {}) {
109
119
  optionsSeparator: ", ",
110
120
  maxWidth: options.maxWidth == null ? void 0 : options.maxWidth - termIndent
111
121
  });
112
- let description = entry.description == null ? "" : formatMessage(entry.description, {
122
+ const descColumnWidth = options.maxWidth == null ? void 0 : options.maxWidth - termIndent - termWidth - 2;
123
+ const termVisibleWidth = lastLineVisibleLength(term);
124
+ const extraTermOffset = descColumnWidth != null ? Math.max(0, termVisibleWidth - termWidth) : 0;
125
+ const currentExtraOffset = () => description.includes("\n") ? 0 : extraTermOffset;
126
+ const descFormatOptions = {
113
127
  colors: options.colors,
114
128
  quotes: !options.colors,
115
- maxWidth: options.maxWidth == null ? void 0 : options.maxWidth - termIndent - termWidth - 2
116
- });
129
+ maxWidth: descColumnWidth,
130
+ startWidth: extraTermOffset > 0 ? extraTermOffset : void 0
131
+ };
132
+ let description = entry.description == null ? "" : formatMessage(entry.description, descFormatOptions);
117
133
  if (options.showDefault && entry.default != null) {
118
134
  const prefix = typeof options.showDefault === "object" ? options.showDefault.prefix ?? " [" : " [";
119
135
  const suffix = typeof options.showDefault === "object" ? options.showDefault.suffix ?? "]" : "]";
120
- const defaultText = `${prefix}${formatMessage(entry.default, {
136
+ let defaultStartWidth;
137
+ if (descColumnWidth != null) {
138
+ const lastW = lastLineVisibleLength(description);
139
+ const effectiveLastW = lastW + currentExtraOffset();
140
+ if (effectiveLastW + prefix.length >= descColumnWidth) {
141
+ description += "\n";
142
+ defaultStartWidth = prefix.length;
143
+ } else defaultStartWidth = effectiveLastW + prefix.length;
144
+ }
145
+ const defaultFormatOptions = {
121
146
  colors: options.colors ? { resetSuffix: "\x1B[2m" } : false,
122
- quotes: !options.colors
123
- })}${suffix}`;
147
+ quotes: !options.colors,
148
+ maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - suffix.length,
149
+ startWidth: defaultStartWidth
150
+ };
151
+ const defaultContent = formatMessage(entry.default, defaultFormatOptions);
152
+ const defaultText = `${prefix}${defaultContent}${suffix}`;
124
153
  const formattedDefault = options.colors ? `\x1b[2m${defaultText}\x1b[0m` : defaultText;
125
154
  description += formattedDefault;
126
155
  }
@@ -145,10 +174,23 @@ function formatDocPage(programName, page, options = {}) {
145
174
  }
146
175
  if (truncated) truncatedTerms = [...terms.slice(0, cutIndex), text(", ...")];
147
176
  }
148
- const choicesDisplay = formatMessage(truncatedTerms, {
177
+ let choicesStartWidth;
178
+ if (descColumnWidth != null) {
179
+ const lastW = lastLineVisibleLength(description);
180
+ const effectiveLastW = lastW + currentExtraOffset();
181
+ const prefixLabelLen = prefix.length + label.length;
182
+ if (effectiveLastW + prefixLabelLen >= descColumnWidth) {
183
+ description += "\n";
184
+ choicesStartWidth = prefixLabelLen;
185
+ } else choicesStartWidth = effectiveLastW + prefixLabelLen;
186
+ }
187
+ const choicesFormatOptions = {
149
188
  colors: options.colors ? { resetSuffix: "\x1B[2m" } : false,
150
- quotes: false
151
- });
189
+ quotes: false,
190
+ maxWidth: descColumnWidth == null ? void 0 : descColumnWidth - suffix.length,
191
+ startWidth: choicesStartWidth
192
+ };
193
+ const choicesDisplay = formatMessage(truncatedTerms, choicesFormatOptions);
152
194
  const choicesText = `${prefix}${label}${choicesDisplay}${suffix}`;
153
195
  const formattedChoices = options.colors ? `\x1b[2m${choicesText}\x1b[0m` : choicesText;
154
196
  description += formattedChoices;
@@ -205,12 +247,17 @@ function formatDocPage(programName, page, options = {}) {
205
247
  function indentLines(text$1, indent) {
206
248
  return text$1.split("\n").join("\n" + " ".repeat(indent));
207
249
  }
250
+ const ansiEscapeCodeRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
208
251
  function ansiAwareRightPad(text$1, length, char = " ") {
209
- const ansiEscapeCodeRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
210
252
  const strippedText = text$1.replace(ansiEscapeCodeRegex, "");
211
253
  if (strippedText.length >= length) return text$1;
212
254
  return text$1 + char.repeat(length - strippedText.length);
213
255
  }
256
+ function lastLineVisibleLength(text$1) {
257
+ const lastNewline = text$1.lastIndexOf("\n");
258
+ const lastLine = lastNewline === -1 ? text$1 : text$1.slice(lastNewline + 1);
259
+ return lastLine.replace(ansiEscapeCodeRegex, "").length;
260
+ }
214
261
 
215
262
  //#endregion
216
263
  export { formatDocPage };
package/dist/facade.cjs CHANGED
@@ -543,6 +543,9 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
543
543
  const helpAsCommand = help === "command" || help === "both";
544
544
  const versionAsCommand = version === "command" || version === "both";
545
545
  const completionAsCommand = completion === "command" || completion === "both";
546
+ const helpAsOption = help === "option" || help === "both";
547
+ const versionAsOption = version === "option" || version === "both";
548
+ const completionAsOption = completion === "option" || completion === "both";
546
549
  const requestedCommand = classified.commands[0];
547
550
  if ((requestedCommand === "completion" || requestedCommand === "completions") && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
548
551
  else if (requestedCommand === "help" && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
@@ -561,6 +564,9 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
561
564
  commandParsers.push(...ungroupedMeta);
562
565
  for (const [label, parsers] of Object.entries(groupedMeta)) if (parsers.length === 1) commandParsers.push(require_constructs.group(label, parsers[0]));
563
566
  else commandParsers.push(require_constructs.group(label, require_constructs.longestMatch(...parsers)));
567
+ if (helpAsOption && helpParsers.helpOption) commandParsers.push(helpParsers.helpOption);
568
+ if (versionAsOption && versionParsers.versionOption) commandParsers.push(versionParsers.versionOption);
569
+ if (completionAsOption && completionParsers.completionOption) commandParsers.push(completionParsers.completionOption);
564
570
  if (commandParsers.length === 1) helpGeneratorParser = commandParsers[0];
565
571
  else if (commandParsers.length === 2) helpGeneratorParser = require_constructs.longestMatch(commandParsers[0], commandParsers[1]);
566
572
  else helpGeneratorParser = require_constructs.longestMatch(...commandParsers);
@@ -1045,7 +1051,7 @@ function runWithAsync(parser, programName, contexts, options) {
1045
1051
  */
1046
1052
  function injectAnnotationsIntoParser(parser, annotations) {
1047
1053
  const newInitialState = {
1048
- ...parser.initialState,
1054
+ ...typeof parser.initialState === "object" && parser.initialState !== null ? parser.initialState : {},
1049
1055
  [require_annotations.annotationKey]: annotations
1050
1056
  };
1051
1057
  return {
package/dist/facade.js CHANGED
@@ -543,6 +543,9 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
543
543
  const helpAsCommand = help === "command" || help === "both";
544
544
  const versionAsCommand = version === "command" || version === "both";
545
545
  const completionAsCommand = completion === "command" || completion === "both";
546
+ const helpAsOption = help === "option" || help === "both";
547
+ const versionAsOption = version === "option" || version === "both";
548
+ const completionAsOption = completion === "option" || completion === "both";
546
549
  const requestedCommand = classified.commands[0];
547
550
  if ((requestedCommand === "completion" || requestedCommand === "completions") && completionAsCommand && completionParsers.completionCommand) helpGeneratorParser = completionParsers.completionCommand;
548
551
  else if (requestedCommand === "help" && helpAsCommand && helpParsers.helpCommand) helpGeneratorParser = helpParsers.helpCommand;
@@ -561,6 +564,9 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
561
564
  commandParsers.push(...ungroupedMeta);
562
565
  for (const [label, parsers] of Object.entries(groupedMeta)) if (parsers.length === 1) commandParsers.push(group(label, parsers[0]));
563
566
  else commandParsers.push(group(label, longestMatch(...parsers)));
567
+ if (helpAsOption && helpParsers.helpOption) commandParsers.push(helpParsers.helpOption);
568
+ if (versionAsOption && versionParsers.versionOption) commandParsers.push(versionParsers.versionOption);
569
+ if (completionAsOption && completionParsers.completionOption) commandParsers.push(completionParsers.completionOption);
564
570
  if (commandParsers.length === 1) helpGeneratorParser = commandParsers[0];
565
571
  else if (commandParsers.length === 2) helpGeneratorParser = longestMatch(commandParsers[0], commandParsers[1]);
566
572
  else helpGeneratorParser = longestMatch(...commandParsers);
@@ -1045,7 +1051,7 @@ function runWithAsync(parser, programName, contexts, options) {
1045
1051
  */
1046
1052
  function injectAnnotationsIntoParser(parser, annotations) {
1047
1053
  const newInitialState = {
1048
- ...parser.initialState,
1054
+ ...typeof parser.initialState === "object" && parser.initialState !== null ? parser.initialState : {},
1049
1055
  [annotationKey]: annotations
1050
1056
  };
1051
1057
  return {
package/dist/message.cjs CHANGED
@@ -237,14 +237,6 @@ function valueSet(values$1, options) {
237
237
  });
238
238
  return result;
239
239
  }
240
- /**
241
- * Formats a {@link Message} into a human-readable string for
242
- * the terminal.
243
- * @param msg The message to format, which is an array of
244
- * {@link MessageTerm} objects.
245
- * @param options Optional formatting options to customize the output.
246
- * @returns A formatted string representation of the message.
247
- */
248
240
  function formatMessage(msg, options = {}) {
249
241
  const colorConfig = options.colors ?? false;
250
242
  const useColors = typeof colorConfig === "boolean" ? colorConfig : true;
@@ -253,118 +245,130 @@ function formatMessage(msg, options = {}) {
253
245
  const resetSequence = `\x1b[0m${resetSuffix}`;
254
246
  function* stream() {
255
247
  const wordPattern = /\s*\S+\s*/g;
256
- for (const term of msg) if (term.type === "text") if (term.text.includes("\n\n")) {
257
- const paragraphs = term.text.split(/\n\n+/);
258
- for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
259
- if (paragraphIndex > 0) yield {
260
- text: "\n\n",
261
- width: -1
262
- };
263
- const paragraph = paragraphs[paragraphIndex].replace(/\n/g, " ");
264
- wordPattern.lastIndex = 0;
265
- while (true) {
266
- const match = wordPattern.exec(paragraph);
267
- if (match == null) break;
268
- yield {
269
- text: match[0],
270
- width: match[0].length
248
+ let prevWasLineBreak = false;
249
+ for (const term of msg) {
250
+ const isAfterLineBreak = prevWasLineBreak;
251
+ prevWasLineBreak = false;
252
+ if (term.type === "text") {
253
+ const rawText = isAfterLineBreak ? term.text.replace(/^\n(?!\n)/, "") : term.text;
254
+ if (rawText.includes("\n\n")) {
255
+ const paragraphs = rawText.split(/\n\n+/);
256
+ for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
257
+ if (paragraphIndex > 0) {
258
+ const breakText = isAfterLineBreak && paragraphIndex === 1 ? "\n" : "\n\n";
259
+ yield {
260
+ text: breakText,
261
+ width: -1
262
+ };
263
+ }
264
+ const paragraph = paragraphs[paragraphIndex].replace(/\n/g, " ");
265
+ wordPattern.lastIndex = 0;
266
+ while (true) {
267
+ const match = wordPattern.exec(paragraph);
268
+ if (match == null) break;
269
+ yield {
270
+ text: match[0],
271
+ width: match[0].length
272
+ };
273
+ }
274
+ }
275
+ } else {
276
+ const normalizedText = rawText.replace(/\n/g, " ");
277
+ if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
278
+ text: " ",
279
+ width: 1
271
280
  };
281
+ else {
282
+ wordPattern.lastIndex = 0;
283
+ while (true) {
284
+ const match = wordPattern.exec(normalizedText);
285
+ if (match == null) break;
286
+ yield {
287
+ text: match[0],
288
+ width: match[0].length
289
+ };
290
+ }
291
+ }
272
292
  }
273
- }
274
- } else {
275
- const normalizedText = term.text.replace(/\n/g, " ");
276
- if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
277
- text: " ",
278
- width: 1
279
- };
280
- else {
281
- wordPattern.lastIndex = 0;
282
- while (true) {
283
- const match = wordPattern.exec(normalizedText);
284
- if (match == null) break;
293
+ } else if (term.type === "optionName") {
294
+ const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
295
+ yield {
296
+ text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
297
+ width: name.length
298
+ };
299
+ } else if (term.type === "optionNames") {
300
+ const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
301
+ let i = 0;
302
+ for (const name of names) {
303
+ if (i > 0) yield {
304
+ text: "/",
305
+ width: 1
306
+ };
285
307
  yield {
286
- text: match[0],
287
- width: match[0].length
308
+ text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
309
+ width: name.length
288
310
  };
311
+ i++;
289
312
  }
290
- }
291
- }
292
- else if (term.type === "optionName") {
293
- const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
294
- yield {
295
- text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
296
- width: name.length
297
- };
298
- } else if (term.type === "optionNames") {
299
- const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
300
- let i = 0;
301
- for (const name of names) {
313
+ } else if (term.type === "metavar") {
314
+ const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
315
+ yield {
316
+ text: useColors ? `\x1b[1m${metavar$1}${resetSequence}` : metavar$1,
317
+ width: metavar$1.length
318
+ };
319
+ } else if (term.type === "value") {
320
+ const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
321
+ yield {
322
+ text: useColors ? `\x1b[32m${value$1}${resetSequence}` : value$1,
323
+ width: value$1.length
324
+ };
325
+ } else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
302
326
  if (i > 0) yield {
303
- text: "/",
327
+ text: " ",
304
328
  width: 1
305
329
  };
330
+ const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
306
331
  yield {
307
- text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
308
- width: name.length
332
+ text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
333
+ width: value$1.length
309
334
  };
310
- i++;
311
335
  }
312
- } else if (term.type === "metavar") {
313
- const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
314
- yield {
315
- text: useColors ? `\x1b[1m${metavar$1}${resetSequence}` : metavar$1,
316
- width: metavar$1.length
317
- };
318
- } else if (term.type === "value") {
319
- const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
320
- yield {
321
- text: useColors ? `\x1b[32m${value$1}${resetSequence}` : value$1,
322
- width: value$1.length
323
- };
324
- } else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
325
- if (i > 0) yield {
326
- text: " ",
327
- width: 1
328
- };
329
- const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
330
- yield {
331
- text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
332
- width: value$1.length
333
- };
334
- }
335
- else if (term.type === "envVar") {
336
- const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
337
- yield {
338
- text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
339
- width: envVar$1.length
340
- };
341
- } else if (term.type === "commandLine") {
342
- const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
343
- yield {
344
- text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
345
- width: cmd.length
346
- };
347
- } else if (term.type === "lineBreak") yield {
348
- text: "\n",
349
- width: -1
350
- };
351
- else if (term.type === "url") {
352
- const urlString = term.url.href;
353
- const displayText = useQuotes ? `<${urlString}>` : urlString;
354
- if (useColors) {
355
- const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
336
+ else if (term.type === "envVar") {
337
+ const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
338
+ yield {
339
+ text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
340
+ width: envVar$1.length
341
+ };
342
+ } else if (term.type === "commandLine") {
343
+ const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
356
344
  yield {
357
- text: hyperlink,
345
+ text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
346
+ width: cmd.length
347
+ };
348
+ } else if (term.type === "lineBreak") {
349
+ yield {
350
+ text: "\n",
351
+ width: -1
352
+ };
353
+ prevWasLineBreak = true;
354
+ } else if (term.type === "url") {
355
+ const urlString = term.url.href;
356
+ const displayText = useQuotes ? `<${urlString}>` : urlString;
357
+ if (useColors) {
358
+ const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
359
+ yield {
360
+ text: hyperlink,
361
+ width: displayText.length
362
+ };
363
+ } else yield {
364
+ text: displayText,
358
365
  width: displayText.length
359
366
  };
360
- } else yield {
361
- text: displayText,
362
- width: displayText.length
363
- };
364
- } else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
367
+ } else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
368
+ }
365
369
  }
366
370
  let output = "";
367
- let totalWidth = 0;
371
+ let totalWidth = options.startWidth ?? 0;
368
372
  for (const { text: text$1, width } of stream()) {
369
373
  if (width === -1) {
370
374
  output += text$1;
package/dist/message.js CHANGED
@@ -236,14 +236,6 @@ function valueSet(values$1, options) {
236
236
  });
237
237
  return result;
238
238
  }
239
- /**
240
- * Formats a {@link Message} into a human-readable string for
241
- * the terminal.
242
- * @param msg The message to format, which is an array of
243
- * {@link MessageTerm} objects.
244
- * @param options Optional formatting options to customize the output.
245
- * @returns A formatted string representation of the message.
246
- */
247
239
  function formatMessage(msg, options = {}) {
248
240
  const colorConfig = options.colors ?? false;
249
241
  const useColors = typeof colorConfig === "boolean" ? colorConfig : true;
@@ -252,118 +244,130 @@ function formatMessage(msg, options = {}) {
252
244
  const resetSequence = `\x1b[0m${resetSuffix}`;
253
245
  function* stream() {
254
246
  const wordPattern = /\s*\S+\s*/g;
255
- for (const term of msg) if (term.type === "text") if (term.text.includes("\n\n")) {
256
- const paragraphs = term.text.split(/\n\n+/);
257
- for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
258
- if (paragraphIndex > 0) yield {
259
- text: "\n\n",
260
- width: -1
261
- };
262
- const paragraph = paragraphs[paragraphIndex].replace(/\n/g, " ");
263
- wordPattern.lastIndex = 0;
264
- while (true) {
265
- const match = wordPattern.exec(paragraph);
266
- if (match == null) break;
267
- yield {
268
- text: match[0],
269
- width: match[0].length
247
+ let prevWasLineBreak = false;
248
+ for (const term of msg) {
249
+ const isAfterLineBreak = prevWasLineBreak;
250
+ prevWasLineBreak = false;
251
+ if (term.type === "text") {
252
+ const rawText = isAfterLineBreak ? term.text.replace(/^\n(?!\n)/, "") : term.text;
253
+ if (rawText.includes("\n\n")) {
254
+ const paragraphs = rawText.split(/\n\n+/);
255
+ for (let paragraphIndex = 0; paragraphIndex < paragraphs.length; paragraphIndex++) {
256
+ if (paragraphIndex > 0) {
257
+ const breakText = isAfterLineBreak && paragraphIndex === 1 ? "\n" : "\n\n";
258
+ yield {
259
+ text: breakText,
260
+ width: -1
261
+ };
262
+ }
263
+ const paragraph = paragraphs[paragraphIndex].replace(/\n/g, " ");
264
+ wordPattern.lastIndex = 0;
265
+ while (true) {
266
+ const match = wordPattern.exec(paragraph);
267
+ if (match == null) break;
268
+ yield {
269
+ text: match[0],
270
+ width: match[0].length
271
+ };
272
+ }
273
+ }
274
+ } else {
275
+ const normalizedText = rawText.replace(/\n/g, " ");
276
+ if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
277
+ text: " ",
278
+ width: 1
270
279
  };
280
+ else {
281
+ wordPattern.lastIndex = 0;
282
+ while (true) {
283
+ const match = wordPattern.exec(normalizedText);
284
+ if (match == null) break;
285
+ yield {
286
+ text: match[0],
287
+ width: match[0].length
288
+ };
289
+ }
290
+ }
271
291
  }
272
- }
273
- } else {
274
- const normalizedText = term.text.replace(/\n/g, " ");
275
- if (normalizedText.trim() === "" && normalizedText.length > 0) yield {
276
- text: " ",
277
- width: 1
278
- };
279
- else {
280
- wordPattern.lastIndex = 0;
281
- while (true) {
282
- const match = wordPattern.exec(normalizedText);
283
- if (match == null) break;
292
+ } else if (term.type === "optionName") {
293
+ const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
294
+ yield {
295
+ text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
296
+ width: name.length
297
+ };
298
+ } else if (term.type === "optionNames") {
299
+ const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
300
+ let i = 0;
301
+ for (const name of names) {
302
+ if (i > 0) yield {
303
+ text: "/",
304
+ width: 1
305
+ };
284
306
  yield {
285
- text: match[0],
286
- width: match[0].length
307
+ text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
308
+ width: name.length
287
309
  };
310
+ i++;
288
311
  }
289
- }
290
- }
291
- else if (term.type === "optionName") {
292
- const name = useQuotes ? `\`${term.optionName}\`` : term.optionName;
293
- yield {
294
- text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
295
- width: name.length
296
- };
297
- } else if (term.type === "optionNames") {
298
- const names = term.optionNames.map((name) => useQuotes ? `\`${name}\`` : name);
299
- let i = 0;
300
- for (const name of names) {
312
+ } else if (term.type === "metavar") {
313
+ const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
314
+ yield {
315
+ text: useColors ? `\x1b[1m${metavar$1}${resetSequence}` : metavar$1,
316
+ width: metavar$1.length
317
+ };
318
+ } else if (term.type === "value") {
319
+ const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
320
+ yield {
321
+ text: useColors ? `\x1b[32m${value$1}${resetSequence}` : value$1,
322
+ width: value$1.length
323
+ };
324
+ } else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
301
325
  if (i > 0) yield {
302
- text: "/",
326
+ text: " ",
303
327
  width: 1
304
328
  };
329
+ const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
305
330
  yield {
306
- text: useColors ? `\x1b[3m${name}${resetSequence}` : name,
307
- width: name.length
331
+ text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
332
+ width: value$1.length
308
333
  };
309
- i++;
310
334
  }
311
- } else if (term.type === "metavar") {
312
- const metavar$1 = useQuotes ? `\`${term.metavar}\`` : term.metavar;
313
- yield {
314
- text: useColors ? `\x1b[1m${metavar$1}${resetSequence}` : metavar$1,
315
- width: metavar$1.length
316
- };
317
- } else if (term.type === "value") {
318
- const value$1 = useQuotes ? `${JSON.stringify(term.value)}` : term.value;
319
- yield {
320
- text: useColors ? `\x1b[32m${value$1}${resetSequence}` : value$1,
321
- width: value$1.length
322
- };
323
- } else if (term.type === "values") for (let i = 0; i < term.values.length; i++) {
324
- if (i > 0) yield {
325
- text: " ",
326
- width: 1
327
- };
328
- const value$1 = useQuotes ? JSON.stringify(term.values[i]) : term.values[i];
329
- yield {
330
- text: useColors ? i <= 0 ? `\x1b[32m${value$1}` : i + 1 >= term.values.length ? `${value$1}${resetSequence}` : value$1 : value$1,
331
- width: value$1.length
332
- };
333
- }
334
- else if (term.type === "envVar") {
335
- const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
336
- yield {
337
- text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
338
- width: envVar$1.length
339
- };
340
- } else if (term.type === "commandLine") {
341
- const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
342
- yield {
343
- text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
344
- width: cmd.length
345
- };
346
- } else if (term.type === "lineBreak") yield {
347
- text: "\n",
348
- width: -1
349
- };
350
- else if (term.type === "url") {
351
- const urlString = term.url.href;
352
- const displayText = useQuotes ? `<${urlString}>` : urlString;
353
- if (useColors) {
354
- const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
335
+ else if (term.type === "envVar") {
336
+ const envVar$1 = useQuotes ? `\`${term.envVar}\`` : term.envVar;
337
+ yield {
338
+ text: useColors ? `\x1b[1;4m${envVar$1}${resetSequence}` : envVar$1,
339
+ width: envVar$1.length
340
+ };
341
+ } else if (term.type === "commandLine") {
342
+ const cmd = useQuotes ? `\`${term.commandLine}\`` : term.commandLine;
355
343
  yield {
356
- text: hyperlink,
344
+ text: useColors ? `\x1b[36m${cmd}${resetSequence}` : cmd,
345
+ width: cmd.length
346
+ };
347
+ } else if (term.type === "lineBreak") {
348
+ yield {
349
+ text: "\n",
350
+ width: -1
351
+ };
352
+ prevWasLineBreak = true;
353
+ } else if (term.type === "url") {
354
+ const urlString = term.url.href;
355
+ const displayText = useQuotes ? `<${urlString}>` : urlString;
356
+ if (useColors) {
357
+ const hyperlink = `\x1b]8;;${urlString}\x1b\\${displayText}\x1b]8;;\x1b\\${resetSuffix}`;
358
+ yield {
359
+ text: hyperlink,
360
+ width: displayText.length
361
+ };
362
+ } else yield {
363
+ text: displayText,
357
364
  width: displayText.length
358
365
  };
359
- } else yield {
360
- text: displayText,
361
- width: displayText.length
362
- };
363
- } else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
366
+ } else throw new TypeError(`Invalid MessageTerm type: ${term["type"]}.`);
367
+ }
364
368
  }
365
369
  let output = "";
366
- let totalWidth = 0;
370
+ let totalWidth = options.startWidth ?? 0;
367
371
  for (const { text: text$1, width } of stream()) {
368
372
  if (width === -1) {
369
373
  output += text$1;
@@ -12,7 +12,7 @@ const require_mode_dispatch = require('./mode-dispatch.cjs');
12
12
  * @internal
13
13
  */
14
14
  function parseOptionalStyleSync(context, parser) {
15
- const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
15
+ const innerState = Array.isArray(context.state) ? context.state[0] : parser.initialState;
16
16
  const result = parser.parse({
17
17
  ...context,
18
18
  state: innerState
@@ -24,7 +24,7 @@ function parseOptionalStyleSync(context, parser) {
24
24
  * @internal
25
25
  */
26
26
  async function parseOptionalStyleAsync(context, parser) {
27
- const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
27
+ const innerState = Array.isArray(context.state) ? context.state[0] : parser.initialState;
28
28
  const result = await parser.parse({
29
29
  ...context,
30
30
  state: innerState
@@ -76,14 +76,14 @@ function processOptionalStyleResult(result, innerState, context) {
76
76
  function optional(parser) {
77
77
  const syncParser = parser;
78
78
  function* suggestSync(context, prefix) {
79
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
79
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
80
80
  yield* syncParser.suggest({
81
81
  ...context,
82
82
  state: innerState
83
83
  }, prefix);
84
84
  }
85
85
  async function* suggestAsync(context, prefix) {
86
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
86
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
87
87
  const suggestions = parser.suggest({
88
88
  ...context,
89
89
  state: innerState
@@ -110,7 +110,7 @@ function optional(parser) {
110
110
  return require_mode_dispatch.dispatchByMode(parser.$mode, () => parseOptionalStyleSync(context, syncParser), () => parseOptionalStyleAsync(context, parser));
111
111
  },
112
112
  complete(state) {
113
- if (typeof state === "undefined") {
113
+ if (!Array.isArray(state)) {
114
114
  if (innerHasWrappedDependency && wrappedPendingState) return require_mode_dispatch.dispatchByMode(parser.$mode, () => syncParser.complete([wrappedPendingState]), () => parser.complete([wrappedPendingState]));
115
115
  return {
116
116
  success: true,
@@ -175,14 +175,14 @@ var WithDefaultError = class extends Error {
175
175
  function withDefault(parser, defaultValue, options) {
176
176
  const syncParser = parser;
177
177
  function* suggestSync(context, prefix) {
178
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
178
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
179
179
  yield* syncParser.suggest({
180
180
  ...context,
181
181
  state: innerState
182
182
  }, prefix);
183
183
  }
184
184
  async function* suggestAsync(context, prefix) {
185
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
185
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
186
186
  const suggestions = parser.suggest({
187
187
  ...context,
188
188
  state: innerState
@@ -206,7 +206,7 @@ function withDefault(parser, defaultValue, options) {
206
206
  return require_mode_dispatch.dispatchByMode(parser.$mode, () => parseOptionalStyleSync(context, syncParser), () => parseOptionalStyleAsync(context, parser));
207
207
  },
208
208
  complete(state) {
209
- if (typeof state === "undefined") {
209
+ if (!Array.isArray(state)) {
210
210
  if (require_dependency.transformsDependencyValue(parser)) {
211
211
  const innerResult = require_mode_dispatch.dispatchByMode(parser.$mode, () => syncParser.complete(void 0), () => parser.complete(void 0));
212
212
  const handleInnerResult = (res) => {
package/dist/modifiers.js CHANGED
@@ -12,7 +12,7 @@ import { dispatchByMode, dispatchIterableByMode } from "./mode-dispatch.js";
12
12
  * @internal
13
13
  */
14
14
  function parseOptionalStyleSync(context, parser) {
15
- const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
15
+ const innerState = Array.isArray(context.state) ? context.state[0] : parser.initialState;
16
16
  const result = parser.parse({
17
17
  ...context,
18
18
  state: innerState
@@ -24,7 +24,7 @@ function parseOptionalStyleSync(context, parser) {
24
24
  * @internal
25
25
  */
26
26
  async function parseOptionalStyleAsync(context, parser) {
27
- const innerState = typeof context.state === "undefined" ? parser.initialState : context.state[0];
27
+ const innerState = Array.isArray(context.state) ? context.state[0] : parser.initialState;
28
28
  const result = await parser.parse({
29
29
  ...context,
30
30
  state: innerState
@@ -76,14 +76,14 @@ function processOptionalStyleResult(result, innerState, context) {
76
76
  function optional(parser) {
77
77
  const syncParser = parser;
78
78
  function* suggestSync(context, prefix) {
79
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
79
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
80
80
  yield* syncParser.suggest({
81
81
  ...context,
82
82
  state: innerState
83
83
  }, prefix);
84
84
  }
85
85
  async function* suggestAsync(context, prefix) {
86
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
86
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
87
87
  const suggestions = parser.suggest({
88
88
  ...context,
89
89
  state: innerState
@@ -110,7 +110,7 @@ function optional(parser) {
110
110
  return dispatchByMode(parser.$mode, () => parseOptionalStyleSync(context, syncParser), () => parseOptionalStyleAsync(context, parser));
111
111
  },
112
112
  complete(state) {
113
- if (typeof state === "undefined") {
113
+ if (!Array.isArray(state)) {
114
114
  if (innerHasWrappedDependency && wrappedPendingState) return dispatchByMode(parser.$mode, () => syncParser.complete([wrappedPendingState]), () => parser.complete([wrappedPendingState]));
115
115
  return {
116
116
  success: true,
@@ -175,14 +175,14 @@ var WithDefaultError = class extends Error {
175
175
  function withDefault(parser, defaultValue, options) {
176
176
  const syncParser = parser;
177
177
  function* suggestSync(context, prefix) {
178
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
178
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
179
179
  yield* syncParser.suggest({
180
180
  ...context,
181
181
  state: innerState
182
182
  }, prefix);
183
183
  }
184
184
  async function* suggestAsync(context, prefix) {
185
- const innerState = typeof context.state === "undefined" ? syncParser.initialState : context.state[0];
185
+ const innerState = Array.isArray(context.state) ? context.state[0] : syncParser.initialState;
186
186
  const suggestions = parser.suggest({
187
187
  ...context,
188
188
  state: innerState
@@ -206,7 +206,7 @@ function withDefault(parser, defaultValue, options) {
206
206
  return dispatchByMode(parser.$mode, () => parseOptionalStyleSync(context, syncParser), () => parseOptionalStyleAsync(context, parser));
207
207
  },
208
208
  complete(state) {
209
- if (typeof state === "undefined") {
209
+ if (!Array.isArray(state)) {
210
210
  if (transformsDependencyValue(parser)) {
211
211
  const innerResult = dispatchByMode(parser.$mode, () => syncParser.complete(void 0), () => parser.complete(void 0));
212
212
  const handleInnerResult = (res) => {
package/dist/parser.cjs CHANGED
@@ -350,11 +350,11 @@ function getDocPageSyncImpl(parser, args, options) {
350
350
  state: initialState,
351
351
  usage: parser.usage
352
352
  };
353
- do {
353
+ while (context.buffer.length > 0) {
354
354
  const result = parser.parse(context);
355
355
  if (!result.success) break;
356
356
  context = result.next;
357
- } while (context.buffer.length > 0);
357
+ }
358
358
  return buildDocPage(parser, context, args);
359
359
  }
360
360
  /**
@@ -372,11 +372,11 @@ async function getDocPageAsyncImpl(parser, args, options) {
372
372
  state: initialState,
373
373
  usage: parser.usage
374
374
  };
375
- do {
375
+ while (context.buffer.length > 0) {
376
376
  const result = await parser.parse(context);
377
377
  if (!result.success) break;
378
378
  context = result.next;
379
- } while (context.buffer.length > 0);
379
+ }
380
380
  return buildDocPage(parser, context, args);
381
381
  }
382
382
  /**
package/dist/parser.js CHANGED
@@ -350,11 +350,11 @@ function getDocPageSyncImpl(parser, args, options) {
350
350
  state: initialState,
351
351
  usage: parser.usage
352
352
  };
353
- do {
353
+ while (context.buffer.length > 0) {
354
354
  const result = parser.parse(context);
355
355
  if (!result.success) break;
356
356
  context = result.next;
357
- } while (context.buffer.length > 0);
357
+ }
358
358
  return buildDocPage(parser, context, args);
359
359
  }
360
360
  /**
@@ -372,11 +372,11 @@ async function getDocPageAsyncImpl(parser, args, options) {
372
372
  state: initialState,
373
373
  usage: parser.usage
374
374
  };
375
- do {
375
+ while (context.buffer.length > 0) {
376
376
  const result = await parser.parse(context);
377
377
  if (!result.success) break;
378
378
  context = result.next;
379
- } while (context.buffer.length > 0);
379
+ }
380
380
  return buildDocPage(parser, context, args);
381
381
  }
382
382
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.407+21363ee4",
3
+ "version": "1.0.0-dev.427+497e26fe",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",