diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c7c5a..e605d07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.4.0 + +* Command suggestions will now also suggest based on aliases of a command. +* Introduce getter `Command.suggestionAliases` for names that cannot be used as + aliases, but will trigger suggestions. + ## 2.3.2 * Require Dart 2.18 diff --git a/lib/command_runner.dart b/lib/command_runner.dart index e2c9192..183264d 100644 --- a/lib/command_runner.dart +++ b/lib/command_runner.dart @@ -220,10 +220,17 @@ class CommandRunner { SplayTreeSet>((a, b) => distances[a]! - distances[b]!); for (var command in commands) { if (command.hidden) continue; - var distance = _editDistance(name, command.name); - if (distance <= suggestionDistanceLimit) { - distances[command] = distance; - candidates.add(command); + for (var alias in [ + command.name, + ...command.aliases, + ...command.suggestionAliases + ]) { + var distance = _editDistance(name, alias); + if (distance <= suggestionDistanceLimit) { + distances[command] = + math.min(distances[command] ?? distance, distance); + candidates.add(command); + } } } if (candidates.isEmpty) return ''; @@ -412,6 +419,17 @@ abstract class Command { /// This is intended to be overridden. List get aliases => const []; + /// Alternate non-functional names for this command. + /// + /// These names won't be used in the documentation, and also they won't work + /// when invoked on the command line. But if an unknown command is used it + /// will be matched against this when creating suggestions. + /// + /// A name does not have to be repeated both here and in [aliases]. + /// + /// This is intended to be overridden. + List get suggestionAliases => const []; + Command() { if (!argParser.allowsAnything) { argParser.addFlag('help', diff --git a/pubspec.yaml b/pubspec.yaml index 589a067..c3b52f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: args -version: 2.3.2 +version: 2.4.0 description: >- Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options. diff --git a/test/command_runner_test.dart b/test/command_runner_test.dart index cbf35cc..9207c3b 100644 --- a/test/command_runner_test.dart +++ b/test/command_runner_test.dart @@ -481,6 +481,28 @@ Did you mean one of these? throwsUsageException( 'Could not find a command named "hidde".', anything)); }); + + test('Suggests based on aliases', () { + var command = AliasedCommand(); + runner.addCommand(command); + expect(() => runner.run(['rename']), throwsUsageException(''' +Could not find a command named "rename". + +Did you mean one of these? + aliased +''', anything)); + }); + + test('Suggests based on suggestedAliases', () { + var command = SuggestionAliasedCommand(); + runner.addCommand(command); + expect(() => runner.run(['renamed']), throwsUsageException(''' +Could not find a command named "renamed". + +Did you mean one of these? + aliased +''', anything)); + }); }); group('with --help', () { diff --git a/test/test_utils.dart b/test/test_utils.dart index ab51146..5cfb74a 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -266,7 +266,28 @@ class AliasedCommand extends Command { final takesArguments = false; @override - final aliases = const ['alias', 'als']; + final aliases = const ['alias', 'als', 'renamed']; + + @override + void run() { + hasRun = true; + } +} + +class SuggestionAliasedCommand extends Command { + bool hasRun = false; + + @override + final name = 'aliased'; + + @override + final description = 'Set a value.'; + + @override + final takesArguments = false; + + @override + final suggestionAliases = const ['renamed']; @override void run() {