Script.pipeline constructor
Pipes each script's stdout into the next script's stdin.
Each element of scripts
must be either a Script or an object that can
be converted into a script using the Script.fromByteTransformer and
Script.fromLineTransformer constructors.
Returns a new Script whose stdin comes from the first script's, and whose stdout and stderr come from the last script's.
This behaves like a Bash pipeline with set -o pipefail
: if any script
exits with a non-zero exit code, the returned script will fail, but it
will only exit once all component scripts have exited. If multiple
scripts exit with errors, the last non-zero exit code will be returned.
Similarly, the kill handler will send the event to the first script that accepts the signal.
This doesn't add any special handling for any script's stderr except for the last one.
See also operator |, which provides a syntax for creating pipelines two scripts at a time.
Implementation
factory Script.pipeline(Iterable<Object> scripts, {String? name}) {
_checkCapture();
var list = scripts.map(_toScript).toList();
if (list.isEmpty) {
throw ArgumentError.value(list, "script", "May not be empty");
} else if (list.length == 1) {
return list.first;
}
for (var i = 0; i < list.length - 1; i++) {
list[i].stdout.pipe(list[i + 1].stdin);
}
return Script._(
name ?? list.map((script) => script.name).join(" | "),
list.first.stdin,
// Wrap the final script's stdout and stderr in [SubscriptionStream]s so
// that the inner scripts will see that someone's listening and not try
// to top-level the streams' output.
SubscriptionStream(list.last.stdout.listen(null)),
SubscriptionStream(list.last.stderr.listen(null)),
Future.wait(list.map((script) => script.exitCode)).then((exitCodes) =>
exitCodes.lastWhere((code) => code != 0, orElse: () => 0)),
(signal) => list.any((script) => script.kill(signal)));
}