@optique/core 0.10.7 → 1.0.0-dev.1116
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 +4 -6
- package/dist/annotations.cjs +209 -1
- package/dist/annotations.d.cts +78 -1
- package/dist/annotations.d.ts +78 -1
- package/dist/annotations.js +201 -1
- package/dist/completion.cjs +194 -52
- package/dist/completion.js +194 -52
- package/dist/constructs.cjs +310 -78
- package/dist/constructs.d.cts +525 -644
- package/dist/constructs.d.ts +525 -644
- package/dist/constructs.js +311 -79
- package/dist/context.cjs +43 -3
- package/dist/context.d.cts +113 -5
- package/dist/context.d.ts +113 -5
- package/dist/context.js +41 -3
- package/dist/dependency.cjs +172 -66
- package/dist/dependency.d.cts +22 -2
- package/dist/dependency.d.ts +22 -2
- package/dist/dependency.js +172 -66
- package/dist/doc.cjs +46 -1
- package/dist/doc.d.cts +24 -0
- package/dist/doc.d.ts +24 -0
- package/dist/doc.js +46 -1
- package/dist/facade.cjs +702 -322
- package/dist/facade.d.cts +124 -190
- package/dist/facade.d.ts +124 -190
- package/dist/facade.js +703 -323
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/message.cjs +7 -4
- package/dist/message.js +7 -4
- package/dist/mode-dispatch.cjs +23 -1
- package/dist/mode-dispatch.d.cts +55 -0
- package/dist/mode-dispatch.d.ts +55 -0
- package/dist/mode-dispatch.js +21 -1
- package/dist/modifiers.cjs +210 -55
- package/dist/modifiers.js +211 -56
- package/dist/parser.cjs +80 -47
- package/dist/parser.d.cts +18 -3
- package/dist/parser.d.ts +18 -3
- package/dist/parser.js +82 -50
- package/dist/primitives.cjs +102 -37
- package/dist/primitives.d.cts +81 -24
- package/dist/primitives.d.ts +81 -24
- package/dist/primitives.js +103 -39
- package/dist/usage.cjs +88 -6
- package/dist/usage.d.cts +51 -13
- package/dist/usage.d.ts +51 -13
- package/dist/usage.js +85 -7
- package/dist/valueparser.cjs +391 -106
- package/dist/valueparser.d.cts +62 -10
- package/dist/valueparser.d.ts +62 -10
- package/dist/valueparser.js +391 -106
- package/package.json +10 -1
package/dist/dependency.js
CHANGED
|
@@ -66,6 +66,7 @@ const suggestWithDependency = Symbol.for("@optique/core/dependency/suggestWithDe
|
|
|
66
66
|
* // Create a derived parser that depends on the directory
|
|
67
67
|
* const branchParser = cwdParser.derive({
|
|
68
68
|
* metavar: "BRANCH",
|
|
69
|
+
* mode: "sync",
|
|
69
70
|
* factory: (dir) => gitBranch({ dir }),
|
|
70
71
|
* defaultValue: () => process.cwd(),
|
|
71
72
|
* });
|
|
@@ -79,14 +80,15 @@ function dependency(parser) {
|
|
|
79
80
|
[dependencySourceMarker]: true,
|
|
80
81
|
[dependencyId]: id,
|
|
81
82
|
derive(options) {
|
|
82
|
-
|
|
83
|
+
if (options.mode !== "sync" && options.mode !== "async") throw new TypeError("derive() requires an explicit mode field (\"sync\" or \"async\").");
|
|
84
|
+
return createDerivedValueParser(id, parser, options, options.mode);
|
|
83
85
|
},
|
|
84
86
|
deriveSync(options) {
|
|
85
87
|
if (parser.$mode === "async") return createAsyncDerivedParserFromSyncFactory(id, options);
|
|
86
88
|
return createSyncDerivedParser(id, options);
|
|
87
89
|
},
|
|
88
90
|
deriveAsync(options) {
|
|
89
|
-
return
|
|
91
|
+
return createAsyncDerivedParserFromAsyncFactory(id, options);
|
|
90
92
|
}
|
|
91
93
|
};
|
|
92
94
|
return result;
|
|
@@ -132,6 +134,7 @@ function isDerivedValueParser(parser) {
|
|
|
132
134
|
*
|
|
133
135
|
* const configParser = deriveFrom({
|
|
134
136
|
* metavar: "CONFIG",
|
|
137
|
+
* mode: "sync",
|
|
135
138
|
* dependencies: [dirParser, modeParser] as const,
|
|
136
139
|
* factory: (dir, mode) =>
|
|
137
140
|
* choice(mode === "dev"
|
|
@@ -140,12 +143,14 @@ function isDerivedValueParser(parser) {
|
|
|
140
143
|
* defaultValues: () => ["/config", "dev"],
|
|
141
144
|
* });
|
|
142
145
|
* ```
|
|
146
|
+
* @throws {TypeError} If the `mode` field is missing or invalid.
|
|
143
147
|
* @since 0.10.0
|
|
144
148
|
*/
|
|
145
149
|
function deriveFrom(options) {
|
|
150
|
+
if (options.mode !== "sync" && options.mode !== "async") throw new TypeError("deriveFrom() requires an explicit mode field (\"sync\" or \"async\").");
|
|
146
151
|
const depsAsync = options.dependencies.some((dep) => dep.$mode === "async");
|
|
147
|
-
const factoryReturnsAsync = determineFactoryModeForDeriveFrom(options);
|
|
148
152
|
const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencyId] : Symbol();
|
|
153
|
+
const factoryReturnsAsync = options.mode === "async";
|
|
149
154
|
const isAsync = depsAsync || factoryReturnsAsync;
|
|
150
155
|
if (isAsync) {
|
|
151
156
|
if (factoryReturnsAsync) return createAsyncDerivedFromParserFromAsyncFactory(sourceId, options);
|
|
@@ -191,14 +196,6 @@ function deriveFromAsync(options) {
|
|
|
191
196
|
const sourceId = options.dependencies.length > 0 ? options.dependencies[0][dependencyId] : Symbol();
|
|
192
197
|
return createAsyncDerivedFromParserFromAsyncFactory(sourceId, options);
|
|
193
198
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Determines if the factory returns an async parser for deriveFrom options.
|
|
196
|
-
*/
|
|
197
|
-
function determineFactoryModeForDeriveFrom(options) {
|
|
198
|
-
const defaultValues$1 = options.defaultValues();
|
|
199
|
-
const parser = options.factory(...defaultValues$1);
|
|
200
|
-
return parser.$mode === "async";
|
|
201
|
-
}
|
|
202
199
|
function isAsyncModeParser(parser) {
|
|
203
200
|
return parser.$mode === "async";
|
|
204
201
|
}
|
|
@@ -212,8 +209,17 @@ function createSyncDerivedFromParser(sourceId, options) {
|
|
|
212
209
|
[dependencyIds]: alldependencyIds,
|
|
213
210
|
[defaultValues]: options.defaultValues,
|
|
214
211
|
parse(input) {
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
let derivedParser;
|
|
213
|
+
try {
|
|
214
|
+
const sourceValues = options.defaultValues();
|
|
215
|
+
derivedParser = options.factory(...sourceValues);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
218
|
+
return {
|
|
219
|
+
success: false,
|
|
220
|
+
error: message`Derived parser error: ${msg}`
|
|
221
|
+
};
|
|
222
|
+
}
|
|
217
223
|
if (isAsyncModeParser(derivedParser)) return {
|
|
218
224
|
success: false,
|
|
219
225
|
error: message`Factory returned an async parser where a sync parser is required.`
|
|
@@ -238,14 +244,25 @@ function createSyncDerivedFromParser(sourceId, options) {
|
|
|
238
244
|
return derivedParser.parse(input);
|
|
239
245
|
},
|
|
240
246
|
format(value) {
|
|
241
|
-
|
|
242
|
-
|
|
247
|
+
let derivedParser;
|
|
248
|
+
try {
|
|
249
|
+
const sourceValues = options.defaultValues();
|
|
250
|
+
derivedParser = options.factory(...sourceValues);
|
|
251
|
+
} catch {
|
|
252
|
+
return String(value);
|
|
253
|
+
}
|
|
243
254
|
return derivedParser.format(value);
|
|
244
255
|
},
|
|
245
256
|
*suggest(prefix) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
let derivedParser;
|
|
258
|
+
try {
|
|
259
|
+
const sourceValues = options.defaultValues();
|
|
260
|
+
derivedParser = options.factory(...sourceValues);
|
|
261
|
+
} catch {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (isAsyncModeParser(derivedParser) || !derivedParser.suggest) return;
|
|
265
|
+
yield* derivedParser.suggest(prefix);
|
|
249
266
|
},
|
|
250
267
|
*[suggestWithDependency](prefix, dependencyValue) {
|
|
251
268
|
let derivedParser;
|
|
@@ -259,7 +276,8 @@ function createSyncDerivedFromParser(sourceId, options) {
|
|
|
259
276
|
return;
|
|
260
277
|
}
|
|
261
278
|
}
|
|
262
|
-
if (derivedParser
|
|
279
|
+
if (isAsyncModeParser(derivedParser) || !derivedParser.suggest) return;
|
|
280
|
+
yield* derivedParser.suggest(prefix);
|
|
263
281
|
}
|
|
264
282
|
};
|
|
265
283
|
}
|
|
@@ -277,9 +295,18 @@ function createAsyncDerivedFromParserFromAsyncFactory(sourceId, options) {
|
|
|
277
295
|
[dependencyIds]: alldependencyIds,
|
|
278
296
|
[defaultValues]: options.defaultValues,
|
|
279
297
|
parse(input) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
298
|
+
let derivedParser;
|
|
299
|
+
try {
|
|
300
|
+
const sourceValues = options.defaultValues();
|
|
301
|
+
derivedParser = options.factory(...sourceValues);
|
|
302
|
+
} catch (e) {
|
|
303
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
304
|
+
return Promise.resolve({
|
|
305
|
+
success: false,
|
|
306
|
+
error: message`Derived parser error: ${msg}`
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return Promise.resolve(derivedParser.parse(input));
|
|
283
310
|
},
|
|
284
311
|
[parseWithDependency](input, dependencyValue) {
|
|
285
312
|
let derivedParser;
|
|
@@ -292,16 +319,26 @@ function createAsyncDerivedFromParserFromAsyncFactory(sourceId, options) {
|
|
|
292
319
|
error: message`Factory error: ${msg}`
|
|
293
320
|
});
|
|
294
321
|
}
|
|
295
|
-
return derivedParser.parse(input);
|
|
322
|
+
return Promise.resolve(derivedParser.parse(input));
|
|
296
323
|
},
|
|
297
324
|
format(value) {
|
|
298
|
-
|
|
299
|
-
|
|
325
|
+
let derivedParser;
|
|
326
|
+
try {
|
|
327
|
+
const sourceValues = options.defaultValues();
|
|
328
|
+
derivedParser = options.factory(...sourceValues);
|
|
329
|
+
} catch {
|
|
330
|
+
return String(value);
|
|
331
|
+
}
|
|
300
332
|
return derivedParser.format(value);
|
|
301
333
|
},
|
|
302
334
|
async *suggest(prefix) {
|
|
303
|
-
|
|
304
|
-
|
|
335
|
+
let derivedParser;
|
|
336
|
+
try {
|
|
337
|
+
const sourceValues = options.defaultValues();
|
|
338
|
+
derivedParser = options.factory(...sourceValues);
|
|
339
|
+
} catch {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
305
342
|
if (derivedParser.suggest) for await (const suggestion of derivedParser.suggest(prefix)) yield suggestion;
|
|
306
343
|
},
|
|
307
344
|
async *[suggestWithDependency](prefix, dependencyValue) {
|
|
@@ -334,8 +371,17 @@ function createAsyncDerivedFromParserFromSyncFactory(sourceId, options) {
|
|
|
334
371
|
[dependencyIds]: alldependencyIds,
|
|
335
372
|
[defaultValues]: options.defaultValues,
|
|
336
373
|
parse(input) {
|
|
337
|
-
|
|
338
|
-
|
|
374
|
+
let derivedParser;
|
|
375
|
+
try {
|
|
376
|
+
const sourceValues = options.defaultValues();
|
|
377
|
+
derivedParser = options.factory(...sourceValues);
|
|
378
|
+
} catch (e) {
|
|
379
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
380
|
+
return Promise.resolve({
|
|
381
|
+
success: false,
|
|
382
|
+
error: message`Derived parser error: ${msg}`
|
|
383
|
+
});
|
|
384
|
+
}
|
|
339
385
|
return Promise.resolve(derivedParser.parse(input));
|
|
340
386
|
},
|
|
341
387
|
[parseWithDependency](input, dependencyValue) {
|
|
@@ -352,13 +398,23 @@ function createAsyncDerivedFromParserFromSyncFactory(sourceId, options) {
|
|
|
352
398
|
return Promise.resolve(derivedParser.parse(input));
|
|
353
399
|
},
|
|
354
400
|
format(value) {
|
|
355
|
-
|
|
356
|
-
|
|
401
|
+
let derivedParser;
|
|
402
|
+
try {
|
|
403
|
+
const sourceValues = options.defaultValues();
|
|
404
|
+
derivedParser = options.factory(...sourceValues);
|
|
405
|
+
} catch {
|
|
406
|
+
return String(value);
|
|
407
|
+
}
|
|
357
408
|
return derivedParser.format(value);
|
|
358
409
|
},
|
|
359
410
|
async *suggest(prefix) {
|
|
360
|
-
|
|
361
|
-
|
|
411
|
+
let derivedParser;
|
|
412
|
+
try {
|
|
413
|
+
const sourceValues = options.defaultValues();
|
|
414
|
+
derivedParser = options.factory(...sourceValues);
|
|
415
|
+
} catch {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
362
418
|
if (derivedParser.suggest) yield* derivedParser.suggest(prefix);
|
|
363
419
|
},
|
|
364
420
|
*[suggestWithDependency](prefix, dependencyValue) {
|
|
@@ -377,8 +433,8 @@ function createAsyncDerivedFromParserFromSyncFactory(sourceId, options) {
|
|
|
377
433
|
}
|
|
378
434
|
};
|
|
379
435
|
}
|
|
380
|
-
function createDerivedValueParser(sourceId, sourceParser, options) {
|
|
381
|
-
const factoryReturnsAsync =
|
|
436
|
+
function createDerivedValueParser(sourceId, sourceParser, options, factoryMode) {
|
|
437
|
+
const factoryReturnsAsync = factoryMode === "async";
|
|
382
438
|
const isAsync = sourceParser.$mode === "async" || factoryReturnsAsync;
|
|
383
439
|
if (isAsync) {
|
|
384
440
|
if (factoryReturnsAsync) return createAsyncDerivedParserFromAsyncFactory(sourceId, options);
|
|
@@ -386,15 +442,6 @@ function createDerivedValueParser(sourceId, sourceParser, options) {
|
|
|
386
442
|
}
|
|
387
443
|
return createSyncDerivedParser(sourceId, options);
|
|
388
444
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Determines if the factory returns an async parser by calling it with
|
|
391
|
-
* the default value and checking the mode.
|
|
392
|
-
*/
|
|
393
|
-
function determineFactoryMode(options) {
|
|
394
|
-
const defaultValue = options.defaultValue();
|
|
395
|
-
const parser = options.factory(defaultValue);
|
|
396
|
-
return parser.$mode === "async";
|
|
397
|
-
}
|
|
398
445
|
function createSyncDerivedParser(sourceId, options) {
|
|
399
446
|
return {
|
|
400
447
|
$mode: "sync",
|
|
@@ -402,8 +449,17 @@ function createSyncDerivedParser(sourceId, options) {
|
|
|
402
449
|
[derivedValueParserMarker]: true,
|
|
403
450
|
[dependencyId]: sourceId,
|
|
404
451
|
parse(input) {
|
|
405
|
-
|
|
406
|
-
|
|
452
|
+
let derivedParser;
|
|
453
|
+
try {
|
|
454
|
+
const sourceValue = options.defaultValue();
|
|
455
|
+
derivedParser = options.factory(sourceValue);
|
|
456
|
+
} catch (e) {
|
|
457
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
458
|
+
return {
|
|
459
|
+
success: false,
|
|
460
|
+
error: message`Derived parser error: ${msg}`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
407
463
|
if (isAsyncModeParser(derivedParser)) return {
|
|
408
464
|
success: false,
|
|
409
465
|
error: message`Factory returned an async parser where a sync parser is required.`
|
|
@@ -428,14 +484,25 @@ function createSyncDerivedParser(sourceId, options) {
|
|
|
428
484
|
return derivedParser.parse(input);
|
|
429
485
|
},
|
|
430
486
|
format(value) {
|
|
431
|
-
|
|
432
|
-
|
|
487
|
+
let derivedParser;
|
|
488
|
+
try {
|
|
489
|
+
const sourceValue = options.defaultValue();
|
|
490
|
+
derivedParser = options.factory(sourceValue);
|
|
491
|
+
} catch {
|
|
492
|
+
return String(value);
|
|
493
|
+
}
|
|
433
494
|
return derivedParser.format(value);
|
|
434
495
|
},
|
|
435
496
|
*suggest(prefix) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
497
|
+
let derivedParser;
|
|
498
|
+
try {
|
|
499
|
+
const sourceValue = options.defaultValue();
|
|
500
|
+
derivedParser = options.factory(sourceValue);
|
|
501
|
+
} catch {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (isAsyncModeParser(derivedParser) || !derivedParser.suggest) return;
|
|
505
|
+
yield* derivedParser.suggest(prefix);
|
|
439
506
|
},
|
|
440
507
|
*[suggestWithDependency](prefix, dependencyValue) {
|
|
441
508
|
let derivedParser;
|
|
@@ -448,7 +515,8 @@ function createSyncDerivedParser(sourceId, options) {
|
|
|
448
515
|
return;
|
|
449
516
|
}
|
|
450
517
|
}
|
|
451
|
-
if (derivedParser
|
|
518
|
+
if (isAsyncModeParser(derivedParser) || !derivedParser.suggest) return;
|
|
519
|
+
yield* derivedParser.suggest(prefix);
|
|
452
520
|
}
|
|
453
521
|
};
|
|
454
522
|
}
|
|
@@ -463,9 +531,18 @@ function createAsyncDerivedParserFromAsyncFactory(sourceId, options) {
|
|
|
463
531
|
[derivedValueParserMarker]: true,
|
|
464
532
|
[dependencyId]: sourceId,
|
|
465
533
|
parse(input) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
534
|
+
let derivedParser;
|
|
535
|
+
try {
|
|
536
|
+
const sourceValue = options.defaultValue();
|
|
537
|
+
derivedParser = options.factory(sourceValue);
|
|
538
|
+
} catch (e) {
|
|
539
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
540
|
+
return Promise.resolve({
|
|
541
|
+
success: false,
|
|
542
|
+
error: message`Derived parser error: ${msg}`
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return Promise.resolve(derivedParser.parse(input));
|
|
469
546
|
},
|
|
470
547
|
[parseWithDependency](input, dependencyValue) {
|
|
471
548
|
let derivedParser;
|
|
@@ -478,16 +555,26 @@ function createAsyncDerivedParserFromAsyncFactory(sourceId, options) {
|
|
|
478
555
|
error: message`Factory error: ${msg}`
|
|
479
556
|
});
|
|
480
557
|
}
|
|
481
|
-
return derivedParser.parse(input);
|
|
558
|
+
return Promise.resolve(derivedParser.parse(input));
|
|
482
559
|
},
|
|
483
560
|
format(value) {
|
|
484
|
-
|
|
485
|
-
|
|
561
|
+
let derivedParser;
|
|
562
|
+
try {
|
|
563
|
+
const sourceValue = options.defaultValue();
|
|
564
|
+
derivedParser = options.factory(sourceValue);
|
|
565
|
+
} catch {
|
|
566
|
+
return String(value);
|
|
567
|
+
}
|
|
486
568
|
return derivedParser.format(value);
|
|
487
569
|
},
|
|
488
570
|
async *suggest(prefix) {
|
|
489
|
-
|
|
490
|
-
|
|
571
|
+
let derivedParser;
|
|
572
|
+
try {
|
|
573
|
+
const sourceValue = options.defaultValue();
|
|
574
|
+
derivedParser = options.factory(sourceValue);
|
|
575
|
+
} catch {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
491
578
|
if (derivedParser.suggest) for await (const suggestion of derivedParser.suggest(prefix)) yield suggestion;
|
|
492
579
|
},
|
|
493
580
|
async *[suggestWithDependency](prefix, dependencyValue) {
|
|
@@ -516,8 +603,17 @@ function createAsyncDerivedParserFromSyncFactory(sourceId, options) {
|
|
|
516
603
|
[derivedValueParserMarker]: true,
|
|
517
604
|
[dependencyId]: sourceId,
|
|
518
605
|
parse(input) {
|
|
519
|
-
|
|
520
|
-
|
|
606
|
+
let derivedParser;
|
|
607
|
+
try {
|
|
608
|
+
const sourceValue = options.defaultValue();
|
|
609
|
+
derivedParser = options.factory(sourceValue);
|
|
610
|
+
} catch (e) {
|
|
611
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
612
|
+
return Promise.resolve({
|
|
613
|
+
success: false,
|
|
614
|
+
error: message`Derived parser error: ${msg}`
|
|
615
|
+
});
|
|
616
|
+
}
|
|
521
617
|
return Promise.resolve(derivedParser.parse(input));
|
|
522
618
|
},
|
|
523
619
|
[parseWithDependency](input, dependencyValue) {
|
|
@@ -534,13 +630,23 @@ function createAsyncDerivedParserFromSyncFactory(sourceId, options) {
|
|
|
534
630
|
return Promise.resolve(derivedParser.parse(input));
|
|
535
631
|
},
|
|
536
632
|
format(value) {
|
|
537
|
-
|
|
538
|
-
|
|
633
|
+
let derivedParser;
|
|
634
|
+
try {
|
|
635
|
+
const sourceValue = options.defaultValue();
|
|
636
|
+
derivedParser = options.factory(sourceValue);
|
|
637
|
+
} catch {
|
|
638
|
+
return String(value);
|
|
639
|
+
}
|
|
539
640
|
return derivedParser.format(value);
|
|
540
641
|
},
|
|
541
642
|
async *suggest(prefix) {
|
|
542
|
-
|
|
543
|
-
|
|
643
|
+
let derivedParser;
|
|
644
|
+
try {
|
|
645
|
+
const sourceValue = options.defaultValue();
|
|
646
|
+
derivedParser = options.factory(sourceValue);
|
|
647
|
+
} catch {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
544
650
|
if (derivedParser.suggest) yield* derivedParser.suggest(prefix);
|
|
545
651
|
},
|
|
546
652
|
*[suggestWithDependency](prefix, dependencyValue) {
|
package/dist/doc.cjs
CHANGED
|
@@ -3,6 +3,39 @@ const require_usage = require('./usage.cjs');
|
|
|
3
3
|
|
|
4
4
|
//#region src/doc.ts
|
|
5
5
|
/**
|
|
6
|
+
* Classifies a {@link DocSection} by its content type for use in the
|
|
7
|
+
* default smart sort.
|
|
8
|
+
*
|
|
9
|
+
* @returns `0` for command-only sections, `1` for mixed sections, `2` for
|
|
10
|
+
* option/argument/passthrough-only sections.
|
|
11
|
+
*/
|
|
12
|
+
function classifySection(section) {
|
|
13
|
+
const hasCommand = section.entries.some((e) => e.term.type === "command");
|
|
14
|
+
const hasNonCommand = section.entries.some((e) => e.term.type !== "command");
|
|
15
|
+
if (hasCommand && !hasNonCommand) return 0;
|
|
16
|
+
if (hasCommand && hasNonCommand) return 1;
|
|
17
|
+
return 2;
|
|
18
|
+
}
|
|
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
|
+
/**
|
|
28
|
+
* The default section comparator: command-only sections come first, then
|
|
29
|
+
* mixed sections, then option/argument-only sections. Untitled sections
|
|
30
|
+
* receive a score bonus of -1 via {@link scoreSection} so that untitled
|
|
31
|
+
* command-only sections naturally sort before titled command-only sections.
|
|
32
|
+
* Sections with the same score preserve their original relative order
|
|
33
|
+
* (stable sort).
|
|
34
|
+
*/
|
|
35
|
+
function defaultSectionOrder(a, b) {
|
|
36
|
+
return scoreSection(a) - scoreSection(b);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
6
39
|
* Formats a documentation page into a human-readable string.
|
|
7
40
|
*
|
|
8
41
|
* This function takes a structured {@link DocPage} and converts it into
|
|
@@ -14,6 +47,8 @@ const require_usage = require('./usage.cjs');
|
|
|
14
47
|
* @param page The documentation page to format
|
|
15
48
|
* @param options Formatting options to customize the output
|
|
16
49
|
* @returns A formatted string representation of the documentation page
|
|
50
|
+
* @throws {TypeError} If `programName` or any non-empty section's title
|
|
51
|
+
* contains a CR or LF character.
|
|
17
52
|
*
|
|
18
53
|
* @example
|
|
19
54
|
* ```typescript
|
|
@@ -34,6 +69,7 @@ const require_usage = require('./usage.cjs');
|
|
|
34
69
|
* ```
|
|
35
70
|
*/
|
|
36
71
|
function formatDocPage(programName, page, options = {}) {
|
|
72
|
+
if (/[\r\n]/.test(programName)) throw new TypeError("Program name must not contain newlines.");
|
|
37
73
|
const termIndent = options.termIndent ?? 2;
|
|
38
74
|
const termWidth = options.termWidth ?? 26;
|
|
39
75
|
let output = "";
|
|
@@ -64,11 +100,20 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
64
100
|
});
|
|
65
101
|
output += "\n";
|
|
66
102
|
}
|
|
67
|
-
const
|
|
103
|
+
const comparator = options.sectionOrder ?? defaultSectionOrder;
|
|
104
|
+
const sections = page.sections.map((s, i) => ({
|
|
105
|
+
section: s,
|
|
106
|
+
index: i
|
|
107
|
+
})).toSorted((a, b) => {
|
|
108
|
+
const cmp = comparator(a.section, b.section);
|
|
109
|
+
if (cmp !== 0) return cmp;
|
|
110
|
+
return a.index - b.index;
|
|
111
|
+
}).map(({ section }) => section);
|
|
68
112
|
for (const section of sections) {
|
|
69
113
|
if (section.entries.length < 1) continue;
|
|
70
114
|
output += "\n";
|
|
71
115
|
if (section.title != null) {
|
|
116
|
+
if (/[\r\n]/.test(section.title)) throw new TypeError("Section title must not contain newlines.");
|
|
72
117
|
const sectionLabel = options.colors ? `\x1b[1;2m${section.title}:\x1b[0m\n` : `${section.title}:\n`;
|
|
73
118
|
output += sectionLabel;
|
|
74
119
|
}
|
package/dist/doc.d.cts
CHANGED
|
@@ -228,6 +228,28 @@ interface DocPageFormatOptions {
|
|
|
228
228
|
* ```
|
|
229
229
|
*/
|
|
230
230
|
showChoices?: boolean | ShowChoicesOptions;
|
|
231
|
+
/**
|
|
232
|
+
* A custom comparator function to control the order of sections in the
|
|
233
|
+
* help output. When provided, it is used instead of the default smart
|
|
234
|
+
* sort (command-only sections first, then mixed, then option/argument-only
|
|
235
|
+
* sections). Sections that compare equal (return `0`) preserve their
|
|
236
|
+
* original relative order (stable sort).
|
|
237
|
+
*
|
|
238
|
+
* @param a The first section to compare.
|
|
239
|
+
* @param b The second section to compare.
|
|
240
|
+
* @returns A negative number if `a` should appear before `b`, a positive
|
|
241
|
+
* number if `a` should appear after `b`, or `0` if they are equal.
|
|
242
|
+
* @since 1.0.0
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // Sort sections alphabetically by title
|
|
247
|
+
* {
|
|
248
|
+
* sectionOrder: (a, b) => (a.title ?? "").localeCompare(b.title ?? "")
|
|
249
|
+
* }
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
sectionOrder?: (a: DocSection, b: DocSection) => number;
|
|
231
253
|
}
|
|
232
254
|
/**
|
|
233
255
|
* Formats a documentation page into a human-readable string.
|
|
@@ -241,6 +263,8 @@ interface DocPageFormatOptions {
|
|
|
241
263
|
* @param page The documentation page to format
|
|
242
264
|
* @param options Formatting options to customize the output
|
|
243
265
|
* @returns A formatted string representation of the documentation page
|
|
266
|
+
* @throws {TypeError} If `programName` or any non-empty section's title
|
|
267
|
+
* contains a CR or LF character.
|
|
244
268
|
*
|
|
245
269
|
* @example
|
|
246
270
|
* ```typescript
|
package/dist/doc.d.ts
CHANGED
|
@@ -228,6 +228,28 @@ interface DocPageFormatOptions {
|
|
|
228
228
|
* ```
|
|
229
229
|
*/
|
|
230
230
|
showChoices?: boolean | ShowChoicesOptions;
|
|
231
|
+
/**
|
|
232
|
+
* A custom comparator function to control the order of sections in the
|
|
233
|
+
* help output. When provided, it is used instead of the default smart
|
|
234
|
+
* sort (command-only sections first, then mixed, then option/argument-only
|
|
235
|
+
* sections). Sections that compare equal (return `0`) preserve their
|
|
236
|
+
* original relative order (stable sort).
|
|
237
|
+
*
|
|
238
|
+
* @param a The first section to compare.
|
|
239
|
+
* @param b The second section to compare.
|
|
240
|
+
* @returns A negative number if `a` should appear before `b`, a positive
|
|
241
|
+
* number if `a` should appear after `b`, or `0` if they are equal.
|
|
242
|
+
* @since 1.0.0
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* // Sort sections alphabetically by title
|
|
247
|
+
* {
|
|
248
|
+
* sectionOrder: (a, b) => (a.title ?? "").localeCompare(b.title ?? "")
|
|
249
|
+
* }
|
|
250
|
+
* ```
|
|
251
|
+
*/
|
|
252
|
+
sectionOrder?: (a: DocSection, b: DocSection) => number;
|
|
231
253
|
}
|
|
232
254
|
/**
|
|
233
255
|
* Formats a documentation page into a human-readable string.
|
|
@@ -241,6 +263,8 @@ interface DocPageFormatOptions {
|
|
|
241
263
|
* @param page The documentation page to format
|
|
242
264
|
* @param options Formatting options to customize the output
|
|
243
265
|
* @returns A formatted string representation of the documentation page
|
|
266
|
+
* @throws {TypeError} If `programName` or any non-empty section's title
|
|
267
|
+
* contains a CR or LF character.
|
|
244
268
|
*
|
|
245
269
|
* @example
|
|
246
270
|
* ```typescript
|
package/dist/doc.js
CHANGED
|
@@ -3,6 +3,39 @@ import { formatUsage, formatUsageTerm } from "./usage.js";
|
|
|
3
3
|
|
|
4
4
|
//#region src/doc.ts
|
|
5
5
|
/**
|
|
6
|
+
* Classifies a {@link DocSection} by its content type for use in the
|
|
7
|
+
* default smart sort.
|
|
8
|
+
*
|
|
9
|
+
* @returns `0` for command-only sections, `1` for mixed sections, `2` for
|
|
10
|
+
* option/argument/passthrough-only sections.
|
|
11
|
+
*/
|
|
12
|
+
function classifySection(section) {
|
|
13
|
+
const hasCommand = section.entries.some((e) => e.term.type === "command");
|
|
14
|
+
const hasNonCommand = section.entries.some((e) => e.term.type !== "command");
|
|
15
|
+
if (hasCommand && !hasNonCommand) return 0;
|
|
16
|
+
if (hasCommand && hasNonCommand) return 1;
|
|
17
|
+
return 2;
|
|
18
|
+
}
|
|
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
|
+
/**
|
|
28
|
+
* The default section comparator: command-only sections come first, then
|
|
29
|
+
* mixed sections, then option/argument-only sections. Untitled sections
|
|
30
|
+
* receive a score bonus of -1 via {@link scoreSection} so that untitled
|
|
31
|
+
* command-only sections naturally sort before titled command-only sections.
|
|
32
|
+
* Sections with the same score preserve their original relative order
|
|
33
|
+
* (stable sort).
|
|
34
|
+
*/
|
|
35
|
+
function defaultSectionOrder(a, b) {
|
|
36
|
+
return scoreSection(a) - scoreSection(b);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
6
39
|
* Formats a documentation page into a human-readable string.
|
|
7
40
|
*
|
|
8
41
|
* This function takes a structured {@link DocPage} and converts it into
|
|
@@ -14,6 +47,8 @@ import { formatUsage, formatUsageTerm } from "./usage.js";
|
|
|
14
47
|
* @param page The documentation page to format
|
|
15
48
|
* @param options Formatting options to customize the output
|
|
16
49
|
* @returns A formatted string representation of the documentation page
|
|
50
|
+
* @throws {TypeError} If `programName` or any non-empty section's title
|
|
51
|
+
* contains a CR or LF character.
|
|
17
52
|
*
|
|
18
53
|
* @example
|
|
19
54
|
* ```typescript
|
|
@@ -34,6 +69,7 @@ import { formatUsage, formatUsageTerm } from "./usage.js";
|
|
|
34
69
|
* ```
|
|
35
70
|
*/
|
|
36
71
|
function formatDocPage(programName, page, options = {}) {
|
|
72
|
+
if (/[\r\n]/.test(programName)) throw new TypeError("Program name must not contain newlines.");
|
|
37
73
|
const termIndent = options.termIndent ?? 2;
|
|
38
74
|
const termWidth = options.termWidth ?? 26;
|
|
39
75
|
let output = "";
|
|
@@ -64,11 +100,20 @@ function formatDocPage(programName, page, options = {}) {
|
|
|
64
100
|
});
|
|
65
101
|
output += "\n";
|
|
66
102
|
}
|
|
67
|
-
const
|
|
103
|
+
const comparator = options.sectionOrder ?? defaultSectionOrder;
|
|
104
|
+
const sections = page.sections.map((s, i) => ({
|
|
105
|
+
section: s,
|
|
106
|
+
index: i
|
|
107
|
+
})).toSorted((a, b) => {
|
|
108
|
+
const cmp = comparator(a.section, b.section);
|
|
109
|
+
if (cmp !== 0) return cmp;
|
|
110
|
+
return a.index - b.index;
|
|
111
|
+
}).map(({ section }) => section);
|
|
68
112
|
for (const section of sections) {
|
|
69
113
|
if (section.entries.length < 1) continue;
|
|
70
114
|
output += "\n";
|
|
71
115
|
if (section.title != null) {
|
|
116
|
+
if (/[\r\n]/.test(section.title)) throw new TypeError("Section title must not contain newlines.");
|
|
72
117
|
const sectionLabel = options.colors ? `\x1b[1;2m${section.title}:\x1b[0m\n` : `${section.title}:\n`;
|
|
73
118
|
output += sectionLabel;
|
|
74
119
|
}
|