@@ -7,10 +7,11 @@ library dartdoc.tool_runner;
77import 'dart:io' show Process, ProcessException;
88
99import 'package:analyzer/file_system/file_system.dart' ;
10+ import 'package:dartdoc/src/dartdoc_options.dart' ;
1011import 'package:dartdoc/src/io_utils.dart' ;
1112import 'package:dartdoc/src/tool_definition.dart' ;
13+ import 'package:meta/meta.dart' ;
1214import 'package:path/path.dart' as p;
13- import 'dartdoc_options.dart' ;
1415
1516typedef ToolErrorCallback = void Function (String message);
1617typedef FakeResultCallback = String Function (String tool,
@@ -20,9 +21,6 @@ typedef FakeResultCallback = String Function(String tool,
2021/// limiting both parallelization and the number of open temporary files.
2122final MultiFutureTracker <void > _toolTracker = MultiFutureTracker (4 );
2223
23- /// Can be called when the ToolRunner is no longer needed.
24- ///
25- /// This will remove any temporary files created by the tool runner.
2624class ToolTempFileTracker {
2725 final ResourceProvider resourceProvider;
2826 final Folder temporaryDirectory;
@@ -41,7 +39,7 @@ class ToolTempFileTracker {
4139
4240 int _temporaryFileCount = 0 ;
4341
44- Future < File > createTemporaryFile () async {
42+ File createTemporaryFile () {
4543 _temporaryFileCount++ ;
4644 // TODO(srawlins): Assume [temporaryDirectory]'s path is always absolute.
4745 var tempFile = resourceProvider.getFile (resourceProvider.pathContext.join (
@@ -80,22 +78,24 @@ class ToolRunner {
8078 String commandPath;
8179
8280 if (isDartSetup) {
83- commandPath = toolConfiguration. resourceProvider.resolvedExecutable;
81+ commandPath = resourceProvider.resolvedExecutable;
8482 } else {
8583 commandPath = args.removeAt (0 );
8684 }
87- await _runProcess (
88- name, '' , commandPath, args, environment, toolErrorCallback);
85+ // We do not use the stdout of the setup process.
86+ await _runProcess (name, '' , commandPath, args, environment,
87+ toolErrorCallback: toolErrorCallback);
8988 tool.setupComplete = true ;
9089 }
9190
92- Future <String > _runProcess (
93- String name,
94- String content,
95- String commandPath,
96- List <String > args,
97- Map <String , String > environment,
98- ToolErrorCallback toolErrorCallback) async {
91+ /// Runs the tool with [Process.run] , awaiting the exit code, and returning
92+ /// the stdout.
93+ ///
94+ /// If the process's exit code is not 0, or if a [ProcessException] is thrown,
95+ /// calls [toolErrorCallback] with a detailed error message, and returns `''` .
96+ Future <String > _runProcess (String name, String content, String commandPath,
97+ List <String > args, Map <String , String > environment,
98+ {@required ToolErrorCallback toolErrorCallback}) async {
9999 String commandString () => ([commandPath] + args).join (' ' );
100100 try {
101101 var result =
@@ -120,58 +120,52 @@ class ToolRunner {
120120 }
121121 }
122122
123- /// Run a tool. The name of the tool is the first argument in the [args] .
124- /// The content to be sent to to the tool is given in the optional [content] ,
125- /// and the stdout of the tool is returned.
123+ /// Run a tool.
126124 ///
127- /// The [args] must not be null, and it must have at least one member (the name
128- /// of the tool).
129- Future <String > run (List <String > args, ToolErrorCallback toolErrorCallback,
130- {String content, Map <String , String > environment}) async {
125+ /// The name of the tool is the first argument in the [args] . The content to
126+ /// be sent to to the tool is given in the optional [content] . The stdout of
127+ /// the tool is returned.
128+ Future <String > run (List <String > args,
129+ {@required String content,
130+ @required ToolErrorCallback toolErrorCallback,
131+ Map <String , String > environment}) async {
132+ assert (args != null );
133+ assert (args.isNotEmpty);
131134 Future <String > runner;
132135 // Prevent too many tools from running simultaneously.
133136 await _toolTracker.addFutureFromClosure (() {
134- runner = _run (args, toolErrorCallback,
135- content: content, environment: environment);
137+ runner = _run (args,
138+ toolErrorCallback: toolErrorCallback,
139+ content: content,
140+ environment: environment);
136141 return runner;
137142 });
138143 return runner;
139144 }
140145
141- Future <String > _run (List <String > args, ToolErrorCallback toolErrorCallback,
142- {String content, Map <String , String > environment}) async {
146+ Future <String > _run (List <String > args,
147+ {@required ToolErrorCallback toolErrorCallback,
148+ String content,
149+ Map <String , String > environment}) async {
143150 assert (args != null );
144151 assert (args.isNotEmpty);
145152 content ?? = '' ;
146153 environment ?? = < String , String > {};
147- var tool = args.removeAt (0 );
148- if (! toolConfiguration.tools.containsKey (tool )) {
154+ var toolName = args.removeAt (0 );
155+ if (! toolConfiguration.tools.containsKey (toolName )) {
149156 toolErrorCallback (
150- 'Unable to find definition for tool "$tool " in tool map. '
157+ 'Unable to find definition for tool "$toolName " in tool map. '
151158 'Did you add it to dartdoc_options.yaml?' );
152159 return '' ;
153160 }
154- var toolDefinition = toolConfiguration.tools[tool ];
161+ var toolDefinition = toolConfiguration.tools[toolName ];
155162 var toolArgs = toolDefinition.command;
156- // Ideally, we would just be able to send the input text into stdin, but
157- // there's no way to do that synchronously, and converting dartdoc to an
158- // async model of execution is a huge amount of work. Using dart:cli's
159- // waitFor feels like a hack (and requires a similar amount of work anyhow
160- // to fix order of execution issues). So, instead, we have the tool take a
161- // filename as part of its arguments, and write the input to a temporary
162- // file before running the tool synchronously.
163-
164- // Write the content to a temp file.
165- var tmpFile = await ToolTempFileTracker .createInstance (
166- toolConfiguration.resourceProvider)
167- .createTemporaryFile ();
168- tmpFile.writeAsStringSync (content);
169163
170164 // Substitute the temp filename for the "$INPUT" token, and all of the other
171165 // environment variables. Variables are allowed to either be in $(VAR) form,
172166 // or $VAR form.
173167 var envWithInput = {
174- 'INPUT' : pathContext. absolute (tmpFile.path ),
168+ 'INPUT' : _tmpFileWithContent (content ),
175169 'TOOL_COMMAND' : toolDefinition.command[0 ],
176170 ...environment,
177171 };
@@ -183,17 +177,62 @@ class ToolRunner {
183177 // find out where their script was coming from as an absolute path on the
184178 // filesystem.
185179 envWithInput['DART_SNAPSHOT_CACHE' ] = pathContext.absolute (
186- SnapshotCache .createInstance (toolConfiguration.resourceProvider)
187- .snapshotCache
188- .path);
180+ SnapshotCache .createInstance (resourceProvider).snapshotCache.path);
189181 if (toolDefinition.setupCommand != null ) {
190182 envWithInput['DART_SETUP_COMMAND' ] = toolDefinition.setupCommand[0 ];
191183 }
192184 }
185+
186+ var argsWithInput = [
187+ ...toolArgs,
188+ ..._substituteInArgs (args, envWithInput),
189+ ];
190+
191+ if (toolDefinition.setupCommand != null && ! toolDefinition.setupComplete) {
192+ await _runSetup (
193+ toolName, toolDefinition, envWithInput, toolErrorCallback);
194+ }
195+
196+ var toolStateForArgs = await toolDefinition.toolStateForArgs (argsWithInput);
197+ var commandPath = toolStateForArgs.commandPath;
198+ argsWithInput = toolStateForArgs.args;
199+ var callCompleter = toolStateForArgs.onProcessComplete;
200+ var stdout = _runProcess (
201+ toolName, content, commandPath, argsWithInput, envWithInput,
202+ toolErrorCallback: toolErrorCallback);
203+
204+ if (callCompleter == null ) {
205+ return stdout;
206+ } else {
207+ return stdout.whenComplete (callCompleter);
208+ }
209+ }
210+
211+ /// Returns the path to the temp file after [content] is written to it.
212+ String _tmpFileWithContent (String content) {
213+ // Ideally, we would just be able to send the input text into stdin, but
214+ // there's no way to do that synchronously, and converting dartdoc to an
215+ // async model of execution is a huge amount of work. Using dart:cli's
216+ // waitFor feels like a hack (and requires a similar amount of work anyhow
217+ // to fix order of execution issues). So, instead, we have the tool take a
218+ // filename as part of its arguments, and write the input to a temporary
219+ // file before running the tool synchronously.
220+
221+ // Write the content to a temp file.
222+ var tmpFile = ToolTempFileTracker .createInstance (resourceProvider)
223+ .createTemporaryFile ();
224+ tmpFile.writeAsStringSync (content);
225+ return pathContext.absolute (tmpFile.path);
226+ }
227+
228+ // TODO(srawlins): Unit tests.
229+ List <String > _substituteInArgs (
230+ List <String > args, Map <String , String > envWithInput) {
193231 var substitutions = envWithInput.map <RegExp , String >((key, value) {
194232 var escapedKey = RegExp .escape (key);
195233 return MapEntry (RegExp ('\\\$ (\\ ($escapedKey \\ )|$escapedKey \\ b)' ), value);
196234 });
235+
197236 var argsWithInput = < String > [];
198237 for (var arg in args) {
199238 var newArg = arg;
@@ -202,25 +241,10 @@ class ToolRunner {
202241 argsWithInput.add (newArg);
203242 }
204243
205- if (toolDefinition.setupCommand != null && ! toolDefinition.setupComplete) {
206- await _runSetup (tool, toolDefinition, envWithInput, toolErrorCallback);
207- }
208-
209- argsWithInput = toolArgs + argsWithInput;
210- var toolStateForArgs = await toolDefinition.toolStateForArgs (argsWithInput);
211- var commandPath = toolStateForArgs.commandPath;
212- argsWithInput = toolStateForArgs.args;
213- var callCompleter = toolStateForArgs.onProcessComplete;
214-
215- if (callCompleter != null ) {
216- return _runProcess (tool, content, commandPath, argsWithInput,
217- envWithInput, toolErrorCallback)
218- .whenComplete (callCompleter);
219- } else {
220- return _runProcess (tool, content, commandPath, argsWithInput,
221- envWithInput, toolErrorCallback);
222- }
244+ return argsWithInput;
223245 }
224246
225- p.Context get pathContext => toolConfiguration.resourceProvider.pathContext;
247+ ResourceProvider get resourceProvider => toolConfiguration.resourceProvider;
248+
249+ p.Context get pathContext => resourceProvider.pathContext;
226250}
0 commit comments