@@ -10,6 +10,8 @@ import 'package:path/path.dart' as p;
10
10
import '../command.dart' ;
11
11
import '../executable.dart' ;
12
12
import '../io.dart' ;
13
+ import '../log.dart' as log;
14
+ import '../package.dart' ;
13
15
import '../utils.dart' ;
14
16
15
17
/// Handles the `run` pub command.
@@ -20,15 +22,21 @@ class RunCommand extends PubCommand {
20
22
bool get allowTrailingOptions => false ;
21
23
22
24
RunCommand () {
23
- argParser.addFlag ("checked" , abbr: "c" ,
24
- help: "Enable runtime type checks and assertions." );
25
+ argParser.addFlag ("checked" ,
26
+ abbr: "c" , help: "Enable runtime type checks and assertions." );
27
+ argParser.addFlag ('list' ,
28
+ help: 'List all available executables.' , negatable: false );
25
29
argParser.addOption ("mode" ,
26
30
help: 'Mode to run transformers in.\n '
27
- '(defaults to "release" for dependencies, "debug" for '
28
- 'entrypoint)' );
31
+ '(defaults to "release" for dependencies, "debug" for entrypoint)' );
29
32
}
30
33
31
34
Future run () async {
35
+ if (argResults['list' ]) {
36
+ _listExecutables ();
37
+ return ;
38
+ }
39
+
32
40
if (argResults.rest.isEmpty) {
33
41
usageException ("Must specify an executable to run." );
34
42
}
@@ -45,10 +53,10 @@ class RunCommand extends PubCommand {
45
53
executable = components[1 ];
46
54
47
55
if (p.split (executable).length > 1 ) {
48
- // TODO(nweiz): Use adjacent strings when the new async/await compiler
49
- // lands.
50
- usageException ("Cannot run an executable in a subdirectory of a " +
51
- "dependency." );
56
+ // TODO(nweiz): Use adjacent strings when the new async/await compiler
57
+ // lands.
58
+ usageException (
59
+ "Cannot run an executable in a subdirectory of a dependency." );
52
60
}
53
61
} else if (onlyIdentifierRegExp.hasMatch (executable)) {
54
62
// "pub run foo" means the same thing as "pub run foo:foo" as long as
@@ -69,4 +77,48 @@ class RunCommand extends PubCommand {
69
77
checked: argResults['checked' ], mode: mode);
70
78
await flushThenExit (exitCode);
71
79
}
80
+
81
+ /// Lists all executables reachable from [entrypoint] .
82
+ void _listExecutables () {
83
+ var ownExecutables = _listExecutablesFor (entrypoint.root)
84
+ .map ((executable) => p.join ('bin' , executable));
85
+
86
+ var packageExecutables = entrypoint.root.immediateDependencies
87
+ .map ((dep) => entrypoint.packageGraph.packages[dep.name])
88
+ .expand ((Package package) => _listExecutablesFor (package).map (
89
+ (executable) => _normalizeExecutable (package.name, executable)));
90
+
91
+ var allExecutables = []
92
+ ..addAll (ownExecutables)
93
+ ..addAll (packageExecutables)
94
+ ..sort ();
95
+
96
+ log.message (log.bold (allExecutables.join ('\n ' )));
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: true )
106
+ .where ((executable) => p.extension (executable) == '.dart' )
107
+ .map ((executable) => p.relative (executable, from: package.path ('bin' )))
108
+ .map ((executable) => p.withoutExtension (executable));
109
+ }
110
+
111
+ /// If [executable] is the same as [package] name, returns unmodified
112
+ /// [executable] .
113
+ ///
114
+ /// _normalizeExecutable('foo', 'foo') // -> 'foo'
115
+ ///
116
+ /// Otherwise prepends [executable] with [package] name and separates them
117
+ /// with the colon.
118
+ ///
119
+ /// _normalizeExecutable('foo', 'bar') // -> 'foo:bar'
120
+ /// _normalizeExecutable('foo', 'bar/baz') // -> 'foo:bar/baz'
121
+ String _normalizeExecutable (String package, String executable) {
122
+ return package == executable ? package : '$package :$executable ' ;
123
+ }
72
124
}
0 commit comments