|
1 | 1 | import 'dart:io' show IOSink, stdout; |
2 | 2 |
|
3 | | -import 'package:args/command_runner.dart' show Command; |
4 | 3 | import 'package:config/config.dart'; |
5 | 4 |
|
6 | 5 | import '../better_command.dart'; |
7 | | -import '../better_command_runner.dart'; |
| 6 | +import 'completely_generator.dart'; |
| 7 | +import 'usage_representation.dart'; |
8 | 8 |
|
9 | 9 | enum CompletionTarget { |
10 | 10 | completely, |
@@ -75,206 +75,3 @@ class CompletionCommand<T> extends BetterCommand<CompletionOption, T> { |
75 | 75 | return null as T; |
76 | 76 | } |
77 | 77 | } |
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