Skip to content

Commit c7f1d4f

Browse files
committed
Add "pub run --list" command to show all available executables.
Issue #1323
1 parent a1a2470 commit c7f1d4f

File tree

4 files changed

+199
-0
lines changed

4 files changed

+199
-0
lines changed

lib/src/command/run.dart

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import 'package:path/path.dart' as p;
1010
import '../command.dart';
1111
import '../executable.dart';
1212
import '../io.dart';
13+
import '../log.dart' as log;
14+
import '../package.dart';
1315
import '../utils.dart';
1416

1517
/// Handles the `run` pub command.
@@ -22,13 +24,20 @@ class RunCommand extends PubCommand {
2224
RunCommand() {
2325
argParser.addFlag("checked", abbr: "c",
2426
help: "Enable runtime type checks and assertions.");
27+
argParser.addFlag('list', negatable: false,
28+
help: 'List all available executables.');
2529
argParser.addOption("mode",
2630
help: 'Mode to run transformers in.\n'
2731
'(defaults to "release" for dependencies, "debug" for '
2832
'entrypoint)');
2933
}
3034

3135
Future run() async {
36+
if (argResults['list']) {
37+
_listExecutables();
38+
return;
39+
}
40+
3241
if (argResults.rest.isEmpty) {
3342
usageException("Must specify an executable to run.");
3443
}
@@ -69,4 +78,58 @@ class RunCommand extends PubCommand {
6978
checked: argResults['checked'], mode: mode);
7079
await flushThenExit(exitCode);
7180
}
81+
82+
/// Lists all executables reachable from [entrypoint].
83+
void _listExecutables() {
84+
var dependencies = entrypoint.root.immediateDependencies
85+
.map((dep) => entrypoint.packageGraph.packages[dep.name]);
86+
87+
var packages = []
88+
..add(entrypoint.root)
89+
..addAll(dependencies);
90+
91+
packages.forEach((Package package) {
92+
var executables = _listExecutablesFor(package);
93+
if (executables.isNotEmpty) {
94+
log.message(_formatExecutables(package.name, executables.toList()));
95+
}
96+
});
97+
}
98+
99+
/// Lists all Dart files in the `bin` directory of the [package].
100+
///
101+
/// Returns paths without extension relative to the `bin` directory of the
102+
/// [package].
103+
List<String> _listExecutablesFor(Package package) {
104+
return package
105+
.listFiles(beneath: 'bin', recursive: false)
106+
.where((executable) => p.extension(executable) == '.dart')
107+
.map(p.basenameWithoutExtension);
108+
}
109+
110+
/// Returns formatted string that lists [executables] for the [packageName].
111+
/// Examples:
112+
///
113+
/// _formatExecutables('foo', ['foo']) // -> 'foo'
114+
/// _formatExecutables('foo', ['bar']) // -> 'foo:bar'
115+
/// _formatExecutables('foo', ['bar', 'foo']) // -> 'foo: foo, bar'
116+
///
117+
/// Note the leading space before first executable and sorting order in the
118+
/// last example.
119+
String _formatExecutables(String packageName, List<String> executables) {
120+
if (executables.length == 1) {
121+
if (executables.first == packageName) return log.bold(packageName);
122+
else return '${log.bold(packageName)}:${executables.first}';
123+
} else {
124+
// Sort executables to make executable that matches package name to be
125+
// the first in the list.
126+
executables.sort((e1, e2) {
127+
if (e1 == packageName) return -1;
128+
else if (e2 == packageName) return 1;
129+
else return e1.compareTo(e2);
130+
});
131+
132+
return '${log.bold(packageName)}: ${executables.join(', ')}';
133+
}
134+
}
72135
}

test/run/errors_if_no_executable_is_given_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Must specify an executable to run.
2020
Usage: pub run <executable> [args...]
2121
-h, --help Print this usage information.
2222
-c, --[no-]checked Enable runtime type checks and assertions.
23+
--list List all available executables.
2324
--mode Mode to run transformers in.
2425
(defaults to "release" for dependencies, "debug" for entrypoint)
2526

test/run/errors_if_path_in_dependency_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Cannot run an executable in a subdirectory of a dependency.
2727
Usage: pub run <executable> [args...]
2828
-h, --help Print this usage information.
2929
-c, --[no-]checked Enable runtime type checks and assertions.
30+
--list List all available executables.
3031
--mode Mode to run transformers in.
3132
(defaults to "release" for dependencies, "debug" for entrypoint)
3233

test/run/list_test.dart

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import '../descriptor.dart' as d;
6+
import '../test_pub.dart';
7+
8+
main() {
9+
integration("lists executables in entrypoint's bin", () {
10+
d.dir(appPath, [
11+
d.appPubspec(),
12+
d.dir('bin', [
13+
d.file('foo.dart'),
14+
d.file('bar.dart')
15+
])
16+
]).create();
17+
18+
schedulePub(args: ['run', '--list'], output: 'myapp: bar, foo');
19+
});
20+
21+
integration("doesn't list executables in bin's sub directories", () {
22+
d.dir(appPath, [
23+
d.appPubspec(),
24+
d.dir('bin', [
25+
d.file('foo.dart'),
26+
d.dir('sub', [
27+
d.file('bar.dart')
28+
])
29+
])
30+
]).create();
31+
32+
schedulePub(args: ['run', '--list'], output: 'myapp:foo');
33+
});
34+
35+
integration('lists only Dart files', () {
36+
d.dir(appPath, [
37+
d.appPubspec(),
38+
d.dir('bin', [
39+
d.file('foo.dart'),
40+
d.file('bar.sh')
41+
])
42+
]).create();
43+
44+
schedulePub(args: ['run', '--list'], output: 'myapp:foo');
45+
});
46+
47+
integration('lists executables from a dependency', () {
48+
d.dir('foo', [
49+
d.libPubspec('foo', '1.0.0'),
50+
d.dir('bin', [
51+
d.file('bar.dart')
52+
])
53+
]).create();
54+
55+
d.dir(appPath, [
56+
d.appPubspec({
57+
'foo': {'path': '../foo'}
58+
})
59+
]).create();
60+
61+
pubGet();
62+
schedulePub(args: ['run', '--list'], output: 'foo:bar');
63+
});
64+
65+
integration('lists executables only from immediate dependencies', () {
66+
d.dir(appPath, [
67+
d.appPubspec({
68+
'foo': {'path': '../foo'}
69+
})
70+
]).create();
71+
72+
d.dir('foo', [
73+
d.libPubspec('foo', '1.0.0', deps: {
74+
'baz': {'path': '../baz'}
75+
}),
76+
d.dir('bin', [
77+
d.file('bar.dart')
78+
])
79+
]).create();
80+
81+
d.dir('baz', [
82+
d.libPubspec('baz', '1.0.0'),
83+
d.dir('bin', [
84+
d.file('qux.dart')
85+
])
86+
]).create();
87+
88+
89+
pubGet();
90+
schedulePub(args: ['run', '--list'], output: 'foo:bar');
91+
});
92+
93+
integration('applies formatting before printing executables', () {
94+
d.dir(appPath, [
95+
d.appPubspec({
96+
'foo': {'path': '../foo'},
97+
'bar': {'path': '../bar'}
98+
}),
99+
d.dir('bin', [
100+
d.file('myapp.dart')
101+
])
102+
]).create();
103+
104+
d.dir('foo', [
105+
d.libPubspec('foo', '1.0.0'),
106+
d.dir('bin', [
107+
d.file('baz.dart'),
108+
d.file('foo.dart')
109+
])
110+
]).create();
111+
112+
d.dir('bar', [
113+
d.libPubspec('bar', '1.0.0'),
114+
d.dir('bin', [
115+
d.file('qux.dart')
116+
])
117+
]).create();
118+
119+
pubGet();
120+
schedulePub(args: ['run', '--list'], output: '''
121+
myapp
122+
bar:qux
123+
foo: foo, baz
124+
''');
125+
});
126+
127+
integration('prints blank line when no executables found', () {
128+
d.dir(appPath, [
129+
d.appPubspec()
130+
]).create();
131+
132+
schedulePub(args: ['run', '--list'], output: '\n');
133+
});
134+
}

0 commit comments

Comments
 (0)