Skip to content

Commit 0141510

Browse files
committed
refactor: Broke out completely generator into own file
1 parent 2b9381d commit 0141510

File tree

3 files changed

+246
-205
lines changed

3 files changed

+246
-205
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import 'dart:io' show IOSink;
2+
3+
import 'package:config/config.dart';
4+
5+
import 'usage_representation.dart';
6+
7+
/// Generates usage representation for a command in the YAML format
8+
/// of the `completely` tool.
9+
/// https://github.com/bashly-framework/completely
10+
class CompletelyYamlGenerator implements UsageRepresentationGenerator {
11+
@override
12+
void generate(
13+
final IOSink out,
14+
final CommandUsage usage,
15+
) {
16+
_innerGenerate(out, usage, const []);
17+
}
18+
19+
void _innerGenerate(
20+
final IOSink out,
21+
final CommandUsage usage,
22+
final List<OptionDefinition> inheritedOptions,
23+
) {
24+
if (usage.subcommands.isEmpty &&
25+
usage.persistentOptions.isEmpty &&
26+
usage.options.isEmpty &&
27+
inheritedOptions.isEmpty) {
28+
return;
29+
}
30+
31+
out.writeln('${usage.commandSequence.join(' ')}:');
32+
33+
for (final subcommand in usage.subcommands) {
34+
out.writeln(' - ${subcommand.command}');
35+
}
36+
37+
_generateCompletelyForOptions(out, usage, inheritedOptions);
38+
out.writeln();
39+
40+
final propagatedOptions = [...inheritedOptions, ...usage.persistentOptions];
41+
for (final subcommand in usage.subcommands) {
42+
_innerGenerate(out, subcommand, propagatedOptions);
43+
}
44+
}
45+
46+
static void _generateCompletelyForOptions(
47+
final IOSink out,
48+
final CommandUsage usage,
49+
final List<OptionDefinition> inheritedOptions,
50+
) {
51+
final allOptions = [
52+
...inheritedOptions,
53+
...usage.persistentOptions,
54+
...usage.options
55+
];
56+
57+
// options
58+
for (final option in allOptions) {
59+
if (option.option.argName != null) {
60+
out.writeln(' - --${option.option.argName}');
61+
62+
if (option.option case final FlagOption flagOption) {
63+
if (flagOption.negatable && !flagOption.hideNegatedUsage) {
64+
out.writeln(' - --no-${flagOption.argName}');
65+
}
66+
}
67+
}
68+
69+
if (option.option.argAbbrev != null) {
70+
out.writeln(' - -${option.option.argAbbrev}');
71+
}
72+
73+
if (option.option.argPos == 0) {
74+
final values = _getOptionValues(option.option);
75+
if (values.isNotEmpty) {
76+
_generateCompletelyForOptionValues(out, values);
77+
}
78+
}
79+
// can't currently complete for positional options after the first one
80+
}
81+
82+
// value completions for each option
83+
for (final option in allOptions) {
84+
_generateCompletelyForOption(out, usage.commandSequence, option.option);
85+
}
86+
}
87+
88+
static void _generateCompletelyForOption(
89+
final IOSink out,
90+
final List<String> commandSequence,
91+
final ConfigOptionBase option,
92+
) {
93+
if (option is FlagOption) {
94+
return;
95+
}
96+
97+
if (option.argName case final String argName) {
98+
_generateCompletelyForArgNameOption(
99+
out,
100+
commandSequence,
101+
option,
102+
' --$argName',
103+
);
104+
}
105+
if (option.argAbbrev case final String argAbbrev) {
106+
_generateCompletelyForArgNameOption(
107+
out,
108+
commandSequence,
109+
option,
110+
' -$argAbbrev',
111+
);
112+
}
113+
}
114+
115+
static void _generateCompletelyForArgNameOption(
116+
final IOSink out,
117+
final List<String> commandSequence,
118+
final ConfigOptionBase option,
119+
final String argName,
120+
) {
121+
final values = _getOptionValues(option);
122+
if (values.isNotEmpty) {
123+
out.writeln('${commandSequence.join(' ')}*$argName:');
124+
_generateCompletelyForOptionValues(out, values);
125+
}
126+
}
127+
128+
static List<String> _getOptionValues(
129+
final ConfigOptionBase option,
130+
) {
131+
if (option.allowedValues case final List allowedValues) {
132+
return allowedValues.map(option.valueParser.format).toList();
133+
}
134+
135+
switch (option.option) {
136+
case EnumOption():
137+
final enumParser = option.option.valueParser as EnumParser;
138+
return enumParser.enumValues.map(enumParser.format).toList();
139+
case FileOption():
140+
return ['<file>'];
141+
case DirOption():
142+
return ['<directory>'];
143+
default:
144+
return [];
145+
}
146+
}
147+
148+
static void _generateCompletelyForOptionValues(
149+
final IOSink out,
150+
final Iterable values,
151+
) {
152+
for (final value in values) {
153+
out.writeln(' - $value');
154+
}
155+
}
156+
}
Lines changed: 2 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'dart:io' show IOSink, stdout;
22

3-
import 'package:args/command_runner.dart' show Command;
43
import 'package:config/config.dart';
54

65
import '../better_command.dart';
7-
import '../better_command_runner.dart';
6+
import 'completely_generator.dart';
7+
import 'usage_representation.dart';
88

99
enum CompletionTarget {
1010
completely,
@@ -75,206 +75,3 @@ class CompletionCommand<T> extends BetterCommand<CompletionOption, T> {
7575
return null as T;
7676
}
7777
}
78-
79-
class CommandUsage<O extends OptionDefinition> {
80-
final List<String> commandSequence;
81-
final List<O> options;
82-
final List<CommandUsage> subcommands;
83-
84-
CommandUsage({
85-
required this.commandSequence,
86-
required this.options,
87-
required this.subcommands,
88-
});
89-
90-
String get command => commandSequence.last;
91-
}
92-
93-
abstract class UsageRepresentation {
94-
/// Compiles a usage representation tree for all subcommands and options
95-
/// for a command runner.
96-
static CommandUsage compile(
97-
final BetterCommandRunner runner, {
98-
final String? execNameOverride,
99-
}) {
100-
return _generateForCommand(
101-
[execNameOverride ?? runner.executableName],
102-
_filterOptions(runner.globalOptions),
103-
runner.commands.values,
104-
const [],
105-
);
106-
}
107-
108-
static CommandUsage _generateForCommand(
109-
final List<String> commandSequence,
110-
final List<OptionDefinition> globalOptions,
111-
final Iterable<Command> subcommands,
112-
final List<OptionDefinition> options,
113-
) {
114-
final validOptions = [...globalOptions, ..._filterOptions(options)];
115-
final validSubcommands =
116-
subcommands.whereType<BetterCommand>().where((final c) => !c.hidden);
117-
118-
return CommandUsage(
119-
commandSequence: commandSequence,
120-
options: validOptions,
121-
subcommands: validSubcommands
122-
.map((final subcommand) => _generateForCommand(
123-
[...commandSequence, subcommand.name],
124-
globalOptions,
125-
subcommand.subcommands.values,
126-
subcommand.options,
127-
))
128-
.toList(),
129-
);
130-
}
131-
132-
static List<OptionDefinition> _filterOptions(
133-
final List<OptionDefinition> options,
134-
) {
135-
return options
136-
.where((final o) => !o.option.hide)
137-
.where((final o) => o.option.argName != null || o.option.argPos != null)
138-
.toList();
139-
}
140-
}
141-
142-
/// Interface for generating a usage representation for a command.
143-
abstract interface class UsageRepresentationGenerator {
144-
void generate(
145-
final IOSink out,
146-
final CommandUsage usage,
147-
);
148-
}
149-
150-
/// Generates usage representation for a command in the YAML format
151-
/// of the `completely` tool.
152-
/// https://github.com/bashly-framework/completely
153-
class CompletelyYamlGenerator implements UsageRepresentationGenerator {
154-
@override
155-
void generate(
156-
final IOSink out,
157-
final CommandUsage usage,
158-
) {
159-
if (usage.subcommands.isEmpty && usage.options.isEmpty) {
160-
return;
161-
}
162-
163-
out.writeln('${usage.commandSequence.join(' ')}:');
164-
165-
for (final subcommand in usage.subcommands) {
166-
out.writeln(' - ${subcommand.command}');
167-
}
168-
169-
_generateCompletelyForOptions(out, usage);
170-
out.writeln();
171-
172-
for (final subcommand in usage.subcommands) {
173-
generate(out, subcommand);
174-
}
175-
}
176-
177-
static void _generateCompletelyForOptions(
178-
final IOSink out,
179-
final CommandUsage usage,
180-
) {
181-
// options
182-
for (final option in usage.options) {
183-
if (option.option.argName != null) {
184-
out.writeln(' - --${option.option.argName}');
185-
186-
if (option.option case final FlagOption flagOption) {
187-
if (flagOption.negatable && !flagOption.hideNegatedUsage) {
188-
out.writeln(' - --no-${flagOption.argName}');
189-
}
190-
}
191-
}
192-
193-
if (option.option.argAbbrev != null) {
194-
out.writeln(' - -${option.option.argAbbrev}');
195-
}
196-
197-
if (option.option.argPos == 0) {
198-
final values = _getOptionValues(option.option);
199-
if (values.isNotEmpty) {
200-
_generateCompletelyForOptionValues(out, values);
201-
}
202-
}
203-
// can't currently complete for positional options after the first one
204-
}
205-
206-
// value completions for each option
207-
for (final option in usage.options) {
208-
_generateCompletelyForOption(out, usage.commandSequence, option.option);
209-
}
210-
}
211-
212-
static void _generateCompletelyForOption(
213-
final IOSink out,
214-
final List<String> commandSequence,
215-
final ConfigOptionBase option,
216-
) {
217-
if (option is FlagOption) {
218-
return;
219-
}
220-
221-
if (option.argName case final String argName) {
222-
_generateCompletelyForArgNameOption(
223-
out,
224-
commandSequence,
225-
option,
226-
' --$argName',
227-
);
228-
}
229-
if (option.argAbbrev case final String argAbbrev) {
230-
_generateCompletelyForArgNameOption(
231-
out,
232-
commandSequence,
233-
option,
234-
' -$argAbbrev',
235-
);
236-
}
237-
}
238-
239-
static void _generateCompletelyForArgNameOption(
240-
final IOSink out,
241-
final List<String> commandSequence,
242-
final ConfigOptionBase option,
243-
final String argName,
244-
) {
245-
final values = _getOptionValues(option);
246-
if (values.isNotEmpty) {
247-
out.writeln('${commandSequence.join(' ')}*$argName:');
248-
_generateCompletelyForOptionValues(out, values);
249-
}
250-
}
251-
252-
static List<String> _getOptionValues(
253-
final ConfigOptionBase option,
254-
) {
255-
if (option.allowedValues case final List allowedValues) {
256-
return allowedValues.map(option.valueParser.format).toList();
257-
}
258-
259-
switch (option.option) {
260-
case EnumOption():
261-
final enumParser = option.option.valueParser as EnumParser;
262-
return enumParser.enumValues.map(enumParser.format).toList();
263-
case FileOption():
264-
return ['<file>'];
265-
case DirOption():
266-
return ['<directory>'];
267-
default:
268-
return [];
269-
}
270-
}
271-
272-
static void _generateCompletelyForOptionValues(
273-
final IOSink out,
274-
final Iterable values,
275-
) {
276-
for (final value in values) {
277-
out.writeln(' - $value');
278-
}
279-
}
280-
}

0 commit comments

Comments
 (0)