diff --git a/lib/src/command/run.dart b/lib/src/command/run.dart index ec26fbfc1..26ca3026d 100644 --- a/lib/src/command/run.dart +++ b/lib/src/command/run.dart @@ -10,6 +10,8 @@ import 'package:path/path.dart' as p; import '../command.dart'; import '../executable.dart'; import '../io.dart'; +import '../log.dart' as log; +import '../package.dart'; import '../utils.dart'; /// Handles the `run` pub command. @@ -22,6 +24,8 @@ class RunCommand extends PubCommand { RunCommand() { argParser.addFlag("checked", abbr: "c", help: "Enable runtime type checks and assertions."); + argParser.addFlag('list', negatable: false, + help: 'List all available executables.'); argParser.addOption("mode", help: 'Mode to run transformers in.\n' '(defaults to "release" for dependencies, "debug" for ' @@ -29,6 +33,11 @@ class RunCommand extends PubCommand { } Future run() async { + if (argResults['list']) { + _listExecutables(); + return; + } + if (argResults.rest.isEmpty) { usageException("Must specify an executable to run."); } @@ -69,4 +78,58 @@ class RunCommand extends PubCommand { checked: argResults['checked'], mode: mode); await flushThenExit(exitCode); } + + /// Lists all executables reachable from [entrypoint]. + void _listExecutables() { + var packages = [] + ..add(entrypoint.root) + ..addAll(entrypoint.root.immediateDependencies + .map((dep) => entrypoint.packageGraph.packages[dep.name])); + + packages.forEach((Package package) { + var executables = _listExecutablesFor(package); + if (executables.isNotEmpty) { + log.message(_formatExecutables(package.name, executables.toList())); + } + }); + } + + /// Lists all Dart files in the `bin` directory of the [package]. + /// + /// Returns file names without extensions. + List _listExecutablesFor(Package package) { + return package + .listFiles(beneath: 'bin', recursive: false) + .where((executable) => p.extension(executable) == '.dart') + .map(p.basenameWithoutExtension); + } + + /// Returns formatted string that lists [executables] for the [packageName]. + /// Examples: + /// + /// _formatExecutables('foo', ['foo']) // -> 'foo' + /// _formatExecutables('foo', ['bar']) // -> 'foo:bar' + /// _formatExecutables('foo', ['bar', 'foo']) // -> 'foo: foo, bar' + /// + /// Note the leading space before first executable and sorting order in the + /// last example. + String _formatExecutables(String packageName, List executables) { + if (executables.length == 1) { + // If executable matches the package name omit the name of executable in + // the output. + return executables.first != packageName + ? '${log.bold(packageName)}:${executables.first}' + : log.bold(packageName); + } else { + // Sort executables to make executable that matches the package name to be + // the first in the list. + executables.sort((e1, e2) { + if (e1 == packageName) return -1; + else if (e2 == packageName) return 1; + else return e1.compareTo(e2); + }); + + return '${log.bold(packageName)}: ${executables.join(', ')}'; + } + } } diff --git a/test/run/errors_if_no_executable_is_given_test.dart b/test/run/errors_if_no_executable_is_given_test.dart index 661009052..694d99567 100644 --- a/test/run/errors_if_no_executable_is_given_test.dart +++ b/test/run/errors_if_no_executable_is_given_test.dart @@ -20,6 +20,7 @@ Must specify an executable to run. Usage: pub run [args...] -h, --help Print this usage information. -c, --[no-]checked Enable runtime type checks and assertions. + --list List all available executables. --mode Mode to run transformers in. (defaults to "release" for dependencies, "debug" for entrypoint) diff --git a/test/run/errors_if_path_in_dependency_test.dart b/test/run/errors_if_path_in_dependency_test.dart index 55f8cd7fd..860b50165 100644 --- a/test/run/errors_if_path_in_dependency_test.dart +++ b/test/run/errors_if_path_in_dependency_test.dart @@ -27,6 +27,7 @@ Cannot run an executable in a subdirectory of a dependency. Usage: pub run [args...] -h, --help Print this usage information. -c, --[no-]checked Enable runtime type checks and assertions. + --list List all available executables. --mode Mode to run transformers in. (defaults to "release" for dependencies, "debug" for entrypoint) diff --git a/test/run/list_test.dart b/test/run/list_test.dart new file mode 100644 index 000000000..3de918747 --- /dev/null +++ b/test/run/list_test.dart @@ -0,0 +1,134 @@ +// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import '../descriptor.dart' as d; +import '../test_pub.dart'; + +main() { + integration("lists executables in entrypoint's bin", () { + d.dir(appPath, [ + d.appPubspec(), + d.dir('bin', [ + d.file('foo.dart'), + d.file('bar.dart') + ]) + ]).create(); + + schedulePub(args: ['run', '--list'], output: 'myapp: bar, foo'); + }); + + integration("doesn't list executables in bin's sub directories", () { + d.dir(appPath, [ + d.appPubspec(), + d.dir('bin', [ + d.file('foo.dart'), + d.dir('sub', [ + d.file('bar.dart') + ]) + ]) + ]).create(); + + schedulePub(args: ['run', '--list'], output: 'myapp:foo'); + }); + + integration('lists only Dart files', () { + d.dir(appPath, [ + d.appPubspec(), + d.dir('bin', [ + d.file('foo.dart'), + d.file('bar.sh') + ]) + ]).create(); + + schedulePub(args: ['run', '--list'], output: 'myapp:foo'); + }); + + integration('lists executables from a dependency', () { + d.dir('foo', [ + d.libPubspec('foo', '1.0.0'), + d.dir('bin', [ + d.file('bar.dart') + ]) + ]).create(); + + d.dir(appPath, [ + d.appPubspec({ + 'foo': {'path': '../foo'} + }) + ]).create(); + + pubGet(); + schedulePub(args: ['run', '--list'], output: 'foo:bar'); + }); + + integration('lists executables only from immediate dependencies', () { + d.dir(appPath, [ + d.appPubspec({ + 'foo': {'path': '../foo'} + }) + ]).create(); + + d.dir('foo', [ + d.libPubspec('foo', '1.0.0', deps: { + 'baz': {'path': '../baz'} + }), + d.dir('bin', [ + d.file('bar.dart') + ]) + ]).create(); + + d.dir('baz', [ + d.libPubspec('baz', '1.0.0'), + d.dir('bin', [ + d.file('qux.dart') + ]) + ]).create(); + + + pubGet(); + schedulePub(args: ['run', '--list'], output: 'foo:bar'); + }); + + integration('applies formatting before printing executables', () { + d.dir(appPath, [ + d.appPubspec({ + 'foo': {'path': '../foo'}, + 'bar': {'path': '../bar'} + }), + d.dir('bin', [ + d.file('myapp.dart') + ]) + ]).create(); + + d.dir('foo', [ + d.libPubspec('foo', '1.0.0'), + d.dir('bin', [ + d.file('baz.dart'), + d.file('foo.dart') + ]) + ]).create(); + + d.dir('bar', [ + d.libPubspec('bar', '1.0.0'), + d.dir('bin', [ + d.file('qux.dart') + ]) + ]).create(); + + pubGet(); + schedulePub(args: ['run', '--list'], output: ''' +myapp +bar:qux +foo: foo, baz +'''); + }); + + integration('prints blank line when no executables found', () { + d.dir(appPath, [ + d.appPubspec() + ]).create(); + + schedulePub(args: ['run', '--list'], output: '\n'); + }); +}