Script.capture constructor
Runs callback and captures the output of Scripts created within it.
As much as possible, callback is treated as its own isolated process. In
particular:
-
The stdout and stderr streams of any child Scripts are forwarded to this script's stdout and stderr, respectively, unless the getters are accessed within the span of a macrotask after being created (that is, before a Timer.run event fires).
-
Any unhandled ScriptExceptions within
callback, such as from child Scripts failing unexpectedly, cause this Script to fail with the same exit code. -
Any unhandled Dart exceptions within
callbackcause this Script to print the error message and stack trace to stderr and exit with code 257. -
Any errors within
callbackthat go unhandled after thisscripthas already exited are silently ignored, as is any additional output. -
Creating a Script within
callbackafter this Script has exited will throw a StateError (which will then be silently ignored unless otherwise handled).
The returned Script exits automatically as soon as the Future returned
by callback has completed and all child Scripts created within
callback have completed. It will exit early if an error goes unhandled,
including from a child Script failing unexpectedly.
The name is a human-readable name for the script, used for debugging and
error messages. It defaults to "capture".
The callback can't be interrupted by calling kill, but the onSignal
callback allows capturing those signals so the callback may react
appropriately. When no onSignal handler was set, calling kill will do
nothing and return false.
Implementation
factory Script.capture(
FutureOr<void> Function(Stream<List<int>> stdin) callback,
{String? name,
bool onSignal(ProcessSignal signal)?}) {
_checkCapture();
var scriptName = name ?? "capture";
var childScripts = FutureGroup<void>();
var stdinController = StreamController<List<int>>();
var groups = StdioGroup.entangled();
var stdoutGroup = groups.item1;
var stderrGroup = groups.item2;
var exitCodeCompleter = Completer<int>();
runZonedGuarded(
() async {
if (onSignal != null) {
onSignal = Zone.current.bindUnaryCallback(onSignal!);
}
await callback(stdinController.stream);
// Once there are no child scripts still spawning or running, mark
// this script as done.
void checkIdle() {
if (childScripts.isIdle && !exitCodeCompleter.isCompleted) {
stdoutGroup.close();
stderrGroup.close();
childScripts.close();
exitCodeCompleter.complete(0);
}
}
checkIdle();
childScripts.onIdle.listen((_) => Timer.run(checkIdle));
},
(error, stackTrace) {
if (!exitCodeCompleter.isCompleted) {
stdoutGroup.close();
stderrGroup.close();
childScripts.close();
exitCodeCompleter.completeError(error, stackTrace);
}
},
zoneValues: {
#_childScripts: childScripts,
scriptNameKey: scriptName,
stdoutKey: stdoutGroup,
stderrKey: stderrGroup
},
zoneSpecification: ZoneSpecification(print: (_, parent, zone, line) {
if (!exitCodeCompleter.isCompleted) stdoutGroup.writeln(line);
}));
return Script._(scriptName, stdinController.sink, stdoutGroup.stream,
stderrGroup.stream, exitCodeCompleter.future, (signal) {
if (onSignal == null) return false;
return onSignal!(signal);
});
}