Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 176 additions & 130 deletions lib/cli_script.dart
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,27 +64,37 @@ export 'src/temp.dart';
/// If [debug] is `true`, extra information about [Script]s' lifecycles will be
/// printed directly to stderr. As the name suggests, this is intended for use
/// only when debugging.
void wrapMain(FutureOr<void> Function() callback,
{bool chainStackTraces = true,
bool? printScriptException,
bool verboseTrace = false,
bool debug = false}) {
withConfig(() {
Chain.capture(callback, onError: (error, chain) {
if (error is! ScriptException) {
stderr.writeln(error);
stderr.writeln(terseChain(chain));
// Use the same exit code that Dart does for unhandled exceptions.
exit(254);
}
void wrapMain(
FutureOr<void> Function() callback, {
bool chainStackTraces = true,
bool? printScriptException,
bool verboseTrace = false,
bool debug = false,
}) {
withConfig(
() {
Chain.capture(
callback,
onError: (error, chain) {
if (error is! ScriptException) {
stderr.writeln(error);
stderr.writeln(terseChain(chain));
// Use the same exit code that Dart does for unhandled exceptions.
exit(254);
}

if (printScriptException ?? debug) {
stderr.writeln(error);
stderr.writeln(terseChain(chain));
}
exit(error.exitCode);
}, when: chainStackTraces);
}, verboseTrace: verboseTrace, debug: debug);
if (printScriptException ?? debug) {
stderr.writeln(error);
stderr.writeln(terseChain(chain));
}
exit(error.exitCode);
},
when: chainStackTraces,
);
},
verboseTrace: verboseTrace,
debug: debug,
);
}

/// Runs an executable for its side effects.
Expand All@@ -98,21 +108,23 @@ void wrapMain(FutureOr<void> Function() callback,
/// All other arguments are forwarded to [Process.start].
///
/// [the README]: https://github.com/google/dart_cli_script/blob/main/README.md#argument-parsing
Future<void> run(String executableAndArgs,
{Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false}) =>
Script(executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell)
.done;
Future<void> run(
String executableAndArgs, {
Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
}) => Script(
executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
).done;

/// Runs an executable and returns its stdout, with trailing newlines removed.
///
Expand All@@ -126,21 +138,23 @@ Future<void> run(String executableAndArgs,
/// [the README]: https://github.com/google/dart_cli_script/blob/main/README.md#argument-parsing
///
/// See also [Script.output].
Future<String> output(String executableAndArgs,
{Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false}) =>
Script(executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell)
.output;
Future<String> output(
String executableAndArgs, {
Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
}) => Script(
executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
).output;

/// Runs an executable and returns a stream of lines it prints to stdout.
///
Expand All@@ -155,21 +169,23 @@ Future<String> output(String executableAndArgs,
/// [the README]: https://github.com/google/dart_cli_script/blob/main/README.md#argument-parsing
///
/// See also [Script.lines].
Stream<String> lines(String executableAndArgs,
{Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false}) =>
Script(executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell)
.lines;
Stream<String> lines(
String executableAndArgs, {
Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
}) => Script(
executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
).lines;

/// Runs an executable and returns whether it returns exit code 0.
///
Expand All@@ -179,21 +195,23 @@ Stream<String> lines(String executableAndArgs,
/// All other arguments are forwarded to [Process.start].
///
/// [the README]: https://github.com/google/dart_cli_script/blob/main/README.md#argument-parsing
Future<bool> check(String executableAndArgs,
{Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false}) =>
Script(executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell)
.success;
Future<bool> check(
String executableAndArgs, {
Iterable<String>? args,
String? name,
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
}) => Script(
executableAndArgs,
args: args,
name: name,
workingDirectory: workingDirectory,
environment: environment,
includeParentEnvironment: includeParentEnvironment,
runInShell: runInShell,
).success;

/// Prints [message] to stderr and exits the current script.
///
Expand DownExpand Up@@ -225,25 +243,30 @@ Never fail(String message, {int exitCode = 1}) {
/// at the same time as [exclude].
///
/// The [caseSensitive], [unicode], and [dotAll] flags are the same as for
/// [new RegExp].
StreamTransformer<String, String> grep(String regexp,
{bool exclude = false,
bool onlyMatching = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false}) {
/// [RegExp.new].
StreamTransformer<String, String> grep(
String regexp, {
bool exclude = false,
bool onlyMatching = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false,
}) {
if (exclude && onlyMatching) {
throw ArgumentError("The exclude and onlyMatching flags can't both be set");
}

return NamedStreamTransformer.fromBind(
"grep",
(stream) => stream.grep(regexp,
exclude: exclude,
onlyMatching: onlyMatching,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll));
"grep",
(stream) => stream.grep(
regexp,
exclude: exclude,
onlyMatching: onlyMatching,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll,
),
);
}

/// Returns a transformer that replaces matches of [regexp] with [replacement].
Expand All@@ -256,19 +279,25 @@ StreamTransformer<String, String> grep(String regexp,
/// followed by a number return the character immediately following them.
///
/// The [caseSensitive], [unicode], and [dotAll] flags are the same as for
/// [new RegExp].
StreamTransformer<String, String> replace(String regexp, String replacement,
{bool all = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false}) =>
NamedStreamTransformer.fromBind(
"replace",
(stream) => stream.replace(regexp, replacement,
all: all,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll));
/// [RegExp.new].
StreamTransformer<String, String> replace(
String regexp,
String replacement, {
bool all = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false,
}) => NamedStreamTransformer.fromBind(
"replace",
(stream) => stream.replace(
regexp,
replacement,
all: all,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll,
),
);

/// Returns a transformer that replaces matches of [regexp] with the result of
/// calling [replace].
Expand All@@ -277,27 +306,34 @@ StreamTransformer<String, String> replace(String regexp, String replacement,
/// replaces all matches in each line instead.
///
/// The [caseSensitive], [unicode], and [dotAll] flags are the same as for
/// [new RegExp].
/// [RegExp.new].
StreamTransformer<String, String> replaceMapped(
String regexp, String Function(Match match) replace,
{bool all = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false}) =>
NamedStreamTransformer.fromBind(
"replaceMapped",
(stream) => stream.replaceMapped(regexp, replace,
all: all,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll));
String regexp,
String Function(Match match) replace, {
bool all = false,
bool caseSensitive = true,
bool unicode = false,
bool dotAll = false,
}) => NamedStreamTransformer.fromBind(
"replaceMapped",
(stream) => stream.replaceMapped(
regexp,
replace,
all: all,
caseSensitive: caseSensitive,
unicode: unicode,
dotAll: dotAll,
),
);

/// A transformer that emits each string exactly as it was received, but also
/// prints each string to [currentStderr].
///
/// This is primarily intended for debugging.
final teeToStderr = NamedStreamTransformer<String, String>.fromBind(
"teeToStderr", (stream) => stream.teeToStderr);
"teeToStderr",
(stream) => stream.teeToStderr,
);

/// A shorthand for opening the file at [path] as a stream.
///
Expand DownExpand Up@@ -371,7 +407,7 @@ IOSink write(String path) => File(path).openWrite();
/// });
/// }
/// ```
IOSink append(String path) => File(path).openWrite(mode: FileMode.append);
IOSink append(String path) => File(path).openWrite(mode: .append);

/// Executes [callback] with arguments from standard input.
///
Expand DownExpand Up@@ -408,18 +444,24 @@ IOSink append(String path) => File(path).openWrite(mode: FileMode.append);
///
/// See also [LineStreamExtensions.xargs], which takes arguments directly from
/// an existing string stream rather than [stdin].
Script xargs(FutureOr<void> Function(List<String> args) callback,
{int? maxArgs,
String? name,
void Function(ProcessSignal signal)? onSignal}) {
Script xargs(
FutureOr<void> Function(List<String> args) callback, {
int? maxArgs,
String? name,
void Function(ProcessSignal signal)? onSignal,
}) {
if (maxArgs != null && maxArgs < 1) {
throw RangeError.range(maxArgs, 1, null, 'maxArgs');
}

late Script script;
return Script.capture((stdin) async {
script = stdin.lines
.xargs(callback, maxArgs: maxArgs, name: name, onSignal: onSignal);
script = stdin.lines.xargs(
callback,
maxArgs: maxArgs,
name: name,
onSignal: onSignal,
);
await script.done;
}, onSignal: (signal) => script.kill(signal));
}
Expand All@@ -431,6 +473,10 @@ Script xargs(FutureOr<void> Function(List<String> args) callback,
/// If [root] is passed, it's used as the root directory for relative globs.
Stream<String> ls(String glob, {String? root}) {
var absolute = p.isAbsolute(glob);
return Glob(glob).list(root: root).map(
(entity) => absolute ? entity.path : p.relative(entity.path, from: root));
return Glob(glob)
.list(root: root)
.map(
(entity) =>
absolute ? entity.path : p.relative(entity.path, from: root),
);
}
Loading
Loading