Script.pipeline constructor

Script.pipeline(
  1. Iterable<Object> scripts, {
  2. String? name,
})

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)));
}