Skip to content

Commit 87f58bb

Browse files
committed
Add support for emitting usage as a JSON schema
- Vendor a copy of a library defining a JSON schema API which can output as a `Map<String, Object?>`. - Add a `jsonSchema` getter on `ArgParser` which is similar to `usage` but outputs a schema instead of help text. Clients can manually support a `--json-help` or similar argument in the same way they support `--help`.
1 parent dc97530 commit 87f58bb

File tree

4 files changed

+743
-0
lines changed

4 files changed

+743
-0
lines changed

pkgs/args/lib/src/allow_anything_parser.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class AllowAnythingParser implements ArgParser {
2222
@override
2323
int? get usageLineLength => null;
2424

25+
@override
26+
Map<String, Object?> get jsonSchema =>
27+
const {'type': 'object', 'properties': {}};
28+
2529
@override
2630
ArgParser addCommand(String name, [ArgParser? parser]) {
2731
throw UnsupportedError(

pkgs/args/lib/src/arg_parser.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:collection';
66

77
import 'allow_anything_parser.dart';
88
import 'arg_results.dart';
9+
import 'json_schema.dart';
910
import 'option.dart';
1011
import 'parser.dart';
1112
import 'usage.dart';
@@ -392,4 +393,48 @@ class ArgParser {
392393
/// Finds the option whose name or alias matches [name], or `null` if no
393394
/// option has that name or alias.
394395
Option? findByNameOrAlias(String name) => options[_aliases[name] ?? name];
396+
397+
Map<String, Object?> get jsonSchema {
398+
final properties = <String, Schema>{};
399+
final required = <String>[];
400+
for (final option in _options.values) {
401+
if (option.hide) continue;
402+
var help = option.help;
403+
final extras = <String>[];
404+
if (option.defaultsTo != null) {
405+
extras.add('defaults to "${option.defaultsTo}"');
406+
}
407+
if (option.allowed?.isNotEmpty ?? false) {
408+
extras.add('allowed values: ${option.allowed?.join(', ')}');
409+
}
410+
if (extras.isNotEmpty) {
411+
help = [
412+
if (help != null) help,
413+
...extras,
414+
].join('\n');
415+
}
416+
final schema = switch (option.type) {
417+
OptionType.flag => Schema.bool(
418+
description: help,
419+
),
420+
OptionType.single => Schema.string(
421+
description: help,
422+
),
423+
OptionType.multiple => Schema.list(
424+
description: help,
425+
items: Schema.string(),
426+
),
427+
_ => throw StateError('Unhandled Option Type: ${option.type.name}')
428+
};
429+
430+
if (option.mandatory) {
431+
required.add(option.name);
432+
}
433+
properties[option.name] = schema;
434+
}
435+
return Schema.object(
436+
properties: properties,
437+
required: required,
438+
).asMap();
439+
}
395440
}

0 commit comments

Comments
 (0)