mirror of
https://github.com/chenasraf/dart_script_runner.git
synced 2026-05-17 17:48:03 +00:00
feat: colorize help output
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
## 0.4.0
|
||||
|
||||
- Colorized help output
|
||||
|
||||
## 0.3.2
|
||||
|
||||
- Improve I/O pass-through to commands
|
||||
|
||||
@@ -9,7 +9,7 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:yaml/yaml.dart' as yaml;
|
||||
|
||||
import 'runnable_script.dart';
|
||||
import 'utils.dart' as utils;
|
||||
import 'utils.dart';
|
||||
|
||||
/// The configuration for a script runner. See each field's documentation for more information.
|
||||
class ScriptRunnerConfig {
|
||||
@@ -76,8 +76,7 @@ class ScriptRunnerConfig {
|
||||
final sourceMap = await _tryFindConfig(fs, startDir);
|
||||
|
||||
if (sourceMap.isEmpty) {
|
||||
throw StateError(
|
||||
'Must provide scripts in either pubspec.yaml or script_runner.yaml');
|
||||
throw StateError('Must provide scripts in either pubspec.yaml or script_runner.yaml');
|
||||
}
|
||||
|
||||
final source = sourceMap.values.first;
|
||||
@@ -98,8 +97,7 @@ class ScriptRunnerConfig {
|
||||
);
|
||||
}
|
||||
|
||||
static Future<yaml.YamlMap?> _getPubspecConfig(
|
||||
FileSystem fileSystem, String folderPath) async {
|
||||
static Future<yaml.YamlMap?> _getPubspecConfig(FileSystem fileSystem, String folderPath) async {
|
||||
final filePath = path.join(folderPath, 'pubspec.yaml');
|
||||
final file = fileSystem.file(filePath);
|
||||
if (!file.existsSync()) {
|
||||
@@ -116,8 +114,7 @@ class ScriptRunnerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<yaml.YamlMap?>? _getCustomConfig(
|
||||
FileSystem fileSystem, String folderPath) async {
|
||||
static Future<yaml.YamlMap?>? _getCustomConfig(FileSystem fileSystem, String folderPath) async {
|
||||
final filePath = path.join(folderPath, 'script_runner.yaml');
|
||||
final file = fileSystem.file(filePath);
|
||||
if (!file.existsSync()) {
|
||||
@@ -137,36 +134,66 @@ class ScriptRunnerConfig {
|
||||
yaml.YamlList scriptsRaw, {
|
||||
FileSystem? fileSystem,
|
||||
}) {
|
||||
final scripts = scriptsRaw
|
||||
.map((script) =>
|
||||
RunnableScript.fromYamlMap(script, fileSystem: fileSystem))
|
||||
.toList();
|
||||
final scripts = scriptsRaw.map((script) => RunnableScript.fromYamlMap(script, fileSystem: fileSystem)).toList();
|
||||
return scripts.map((s) => s..preloadScripts = scripts).toList();
|
||||
}
|
||||
|
||||
/// Prints usage help text for this config
|
||||
void printUsage() {
|
||||
print('Dart Script Runner');
|
||||
print(' Usage: scr script_name ...args');
|
||||
print('');
|
||||
var maxLen = 0;
|
||||
print(
|
||||
[
|
||||
colorize('Usage:', [TerminalColor.bold]),
|
||||
colorize('scr', [TerminalColor.yellow]),
|
||||
colorize('<script_name>', [TerminalColor.brightWhite]),
|
||||
colorize('[...args]', [TerminalColor.gray]),
|
||||
].join(' '),
|
||||
);
|
||||
print(
|
||||
[
|
||||
' ' * 'Usage:'.length,
|
||||
colorize('scr', [TerminalColor.yellow]),
|
||||
colorize('-h', [TerminalColor.brightWhite]),
|
||||
].join(' '),
|
||||
);
|
||||
print('');
|
||||
final titleStyle = [TerminalColor.bold, TerminalColor.brightWhite];
|
||||
printColor('Built-in flags:', titleStyle);
|
||||
print('');
|
||||
var maxLen = '-h, --help'.length;
|
||||
for (final scr in scripts) {
|
||||
maxLen = math.max(maxLen, scr.name.length);
|
||||
}
|
||||
final padLen = maxLen + 6;
|
||||
print(' ${'-h, --help'.padRight(padLen, ' ')} Print this help message\n');
|
||||
print(' ${colorize('-h, --help'.padRight(padLen, ' '), [
|
||||
TerminalColor.yellow
|
||||
])} ${colorize('Print this help message', [TerminalColor.gray])}');
|
||||
print('');
|
||||
|
||||
print(
|
||||
'Available scripts'
|
||||
'${configSource?.isNotEmpty == true ? ' on $configSource:' : ':'}',
|
||||
[
|
||||
colorize('Available scripts', [
|
||||
TerminalColor.bold,
|
||||
TerminalColor.brightWhite,
|
||||
]),
|
||||
(configSource?.isNotEmpty == true
|
||||
? [
|
||||
colorize(' on ', titleStyle),
|
||||
colorize(configSource!, [...titleStyle, TerminalColor.underline]),
|
||||
colorize(':', titleStyle)
|
||||
].join('')
|
||||
: ':'),
|
||||
].join(''),
|
||||
);
|
||||
print('');
|
||||
for (final scr in scripts) {
|
||||
final lines = utils.chunks(
|
||||
final lines = chunks(
|
||||
scr.description ?? '\$ ${[scr.cmd, ...scr.args].join(' ')}',
|
||||
80 - padLen,
|
||||
lineLength - padLen,
|
||||
stripColors: true,
|
||||
wrapLine: (line) => colorize(line, [TerminalColor.gray]),
|
||||
);
|
||||
print(' ${scr.name.padRight(padLen, ' ')} ${lines.first}');
|
||||
printColor(' ${scr.name.padRight(padLen, ' ')} ${lines.first}', [TerminalColor.yellow]);
|
||||
for (final line in lines.sublist(1)) {
|
||||
print(' ${''.padRight(padLen, ' ')} $line');
|
||||
}
|
||||
@@ -174,8 +201,7 @@ class ScriptRunnerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, yaml.YamlMap>> _tryFindConfig(
|
||||
FileSystem fs, String startDir) async {
|
||||
static Future<Map<String, yaml.YamlMap>> _tryFindConfig(FileSystem fs, String startDir) async {
|
||||
var dir = fs.directory(startDir);
|
||||
String sourceFile;
|
||||
yaml.YamlMap? source;
|
||||
@@ -291,7 +317,7 @@ class ScriptRunnerShellConfig {
|
||||
case OS.linux:
|
||||
case OS.macos:
|
||||
try {
|
||||
final envShell = utils.firstNonNull([
|
||||
final envShell = firstNonNull([
|
||||
Platform.environment['SHELL'],
|
||||
Platform.environment['TERM'],
|
||||
]);
|
||||
@@ -309,4 +335,3 @@ enum OS {
|
||||
linux,
|
||||
// other
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,7 @@ class RunnableScript {
|
||||
}) : _fileSystem = fileSystem ?? LocalFileSystem();
|
||||
|
||||
/// Generate a runnable script from a yaml loaded map as defined in the config.
|
||||
factory RunnableScript.fromYamlMap(yaml.YamlMap map,
|
||||
{FileSystem? fileSystem}) {
|
||||
factory RunnableScript.fromYamlMap(yaml.YamlMap map, {FileSystem? fileSystem}) {
|
||||
final out = <String, dynamic>{};
|
||||
|
||||
if (map['name'] == null && map.keys.length == 1) {
|
||||
@@ -74,15 +73,13 @@ class RunnableScript {
|
||||
out['cmd'] = map.values.first;
|
||||
} else {
|
||||
out.addAll(map.cast<String, dynamic>());
|
||||
out['args'] =
|
||||
(map['args'] as yaml.YamlList?)?.map((e) => e.toString()).toList();
|
||||
out['args'] = (map['args'] as yaml.YamlList?)?.map((e) => e.toString()).toList();
|
||||
out['env'] = (map['env'] as yaml.YamlMap?)?.cast<String, String>();
|
||||
}
|
||||
try {
|
||||
return RunnableScript.fromMap(out, fileSystem: fileSystem);
|
||||
} catch (e) {
|
||||
throw StateError(
|
||||
'Failed to parse script, arguments: $map, $fileSystem. Error: $e');
|
||||
throw StateError('Failed to parse script, arguments: $map, $fileSystem. Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,8 +109,7 @@ class RunnableScript {
|
||||
appendNewline: appendNewline,
|
||||
);
|
||||
} catch (e) {
|
||||
throw StateError(
|
||||
'Failed to parse script, arguments: $map, $fileSystem. Error: $e');
|
||||
throw StateError('Failed to parse script, arguments: $map, $fileSystem. Error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +128,7 @@ class RunnableScript {
|
||||
if (result.exitCode != 0) throw Exception(result.stderr);
|
||||
}
|
||||
|
||||
final origCmd = [cmd, ...effectiveArgs.map(_utils.wrap)].join(' ');
|
||||
final origCmd = [cmd, ...effectiveArgs.map(_utils.quoteWrap)].join(' ');
|
||||
|
||||
if (!suppressHeaderOutput) {
|
||||
print('\$ $origCmd');
|
||||
@@ -173,14 +169,13 @@ class RunnableScript {
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
String _getScriptPath() => _fileSystem.path
|
||||
.join(_fileSystem.systemTempDirectory.path, 'script_runner_$name.sh');
|
||||
String _getScriptPath() => _fileSystem.path.join(_fileSystem.systemTempDirectory.path, 'script_runner_$name.sh');
|
||||
|
||||
String _getScriptContents(
|
||||
ScriptRunnerConfig config, {
|
||||
List<String> extraArgs = const [],
|
||||
}) {
|
||||
final script = "$cmd ${(args + extraArgs).map(_utils.wrap).join(' ')}";
|
||||
final script = "$cmd ${(args + extraArgs).map(_utils.quoteWrap).join(' ')}";
|
||||
switch (config.shell.os) {
|
||||
case OS.windows:
|
||||
return [
|
||||
@@ -190,12 +185,8 @@ class RunnableScript {
|
||||
].join('\n');
|
||||
case OS.linux:
|
||||
case OS.macos:
|
||||
return [
|
||||
...preloadScripts.map((e) =>
|
||||
"[[ ! \$(which ${e.name}) ]] && alias ${e.name}='scr ${e.name}'"),
|
||||
script
|
||||
].join('\n');
|
||||
return [...preloadScripts.map((e) => "[[ ! \$(which ${e.name}) ]] && alias ${e.name}='scr ${e.name}'"), script]
|
||||
.join('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,26 +42,39 @@ List<String> splitArgs(String string) {
|
||||
return out.where((e) => e.isNotEmpty).toList();
|
||||
}
|
||||
|
||||
String stripColor(String str) {
|
||||
return str.replaceAll(RegExp(r'\x1B\[\d+m'), '');
|
||||
}
|
||||
|
||||
T noop<T>(T arg) => arg;
|
||||
|
||||
/// Split string into chunks of [maxLen] characters.
|
||||
// @internal
|
||||
List<String> chunks(String str, int maxLen) {
|
||||
List<String> chunks(
|
||||
String str,
|
||||
int maxLen, {
|
||||
bool stripColors = false,
|
||||
String Function(String) wrapLine = noop,
|
||||
}) {
|
||||
final words = str.split(' ');
|
||||
final chunks = <String>[];
|
||||
var chunk = '';
|
||||
for (final word in words) {
|
||||
if (chunk.length + word.length > maxLen) {
|
||||
chunks.add(chunk);
|
||||
final chunkLength = stripColors ? stripColor(chunk).length : chunk.length;
|
||||
final wordLength = stripColors ? stripColor(word).length : word.length;
|
||||
if (chunkLength + wordLength > maxLen) {
|
||||
chunks.add(wrapLine(chunk));
|
||||
chunk = '';
|
||||
}
|
||||
chunk += '$word ';
|
||||
}
|
||||
chunks.add(chunk);
|
||||
chunks.add(wrapLine(chunk));
|
||||
return chunks;
|
||||
}
|
||||
|
||||
/// wrap args with quotes if necessary
|
||||
// @internal
|
||||
String wrap(String arg) {
|
||||
String quoteWrap(String arg) {
|
||||
if (arg.contains(' ')) {
|
||||
return '"$arg"';
|
||||
}
|
||||
@@ -78,3 +91,39 @@ T? firstNonNull<T>(Iterable<T?> list) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String colorize(String text, [Iterable<TerminalColor> colors = const []]) {
|
||||
for (final color in colors) {
|
||||
text = '\x1B[${color.index}m$text';
|
||||
}
|
||||
return '$text\x1B[0m';
|
||||
}
|
||||
|
||||
void printColor(String text, [Iterable<TerminalColor> colors = const []]) {
|
||||
print(colorize(text, colors));
|
||||
}
|
||||
|
||||
class TerminalColor {
|
||||
const TerminalColor._(this.index);
|
||||
final int index;
|
||||
|
||||
static const TerminalColor none = TerminalColor._(-1);
|
||||
static const TerminalColor red = TerminalColor._(31);
|
||||
static const TerminalColor green = TerminalColor._(32);
|
||||
static const TerminalColor yellow = TerminalColor._(33);
|
||||
static const TerminalColor blue = TerminalColor._(34);
|
||||
static const TerminalColor magenta = TerminalColor._(35);
|
||||
static const TerminalColor cyan = TerminalColor._(36);
|
||||
static const TerminalColor white = TerminalColor._(37);
|
||||
static const TerminalColor gray = TerminalColor._(90);
|
||||
static const TerminalColor brightRed = TerminalColor._(91);
|
||||
static const TerminalColor brightGreen = TerminalColor._(92);
|
||||
static const TerminalColor brightYellow = TerminalColor._(93);
|
||||
static const TerminalColor brightBlue = TerminalColor._(94);
|
||||
static const TerminalColor brightMagenta = TerminalColor._(95);
|
||||
static const TerminalColor brightCyan = TerminalColor._(96);
|
||||
static const TerminalColor brightWhite = TerminalColor._(97);
|
||||
|
||||
static const TerminalColor bold = TerminalColor._(1);
|
||||
static const TerminalColor underline = TerminalColor._(4);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: script_runner
|
||||
description: Run all your project-related scripts in a portable, simple config.
|
||||
version: 0.3.2
|
||||
version: 0.3.3
|
||||
homepage: https://casraf.dev/
|
||||
repository: https://github.com/chenasraf/dart_script_runner
|
||||
license: MIT
|
||||
@@ -17,13 +17,12 @@ dev_dependencies:
|
||||
test:
|
||||
btool:
|
||||
|
||||
|
||||
script_runner:
|
||||
# line_length: 100
|
||||
scripts:
|
||||
# Real
|
||||
- auto-fix: dart fix --apply
|
||||
- publish: dart pub publish --force
|
||||
- publish: dart format .; dart pub publish; format
|
||||
- publish:dry: dart pub publish --dry-run
|
||||
- doc: dart doc
|
||||
- name: version
|
||||
@@ -32,7 +31,7 @@ script_runner:
|
||||
- name: 'version:set'
|
||||
cmd: dart run btool set packageVersion
|
||||
suppress_header_output: true
|
||||
- format: dart format .
|
||||
- format: dart format --line-length 120 .
|
||||
|
||||
# Examples
|
||||
- name: echo1
|
||||
|
||||
@@ -144,8 +144,7 @@ void main() {
|
||||
}
|
||||
|
||||
Future<void> _writeCustomConf(FileSystem fs, [String? contents]) async {
|
||||
final pubFile =
|
||||
fs.file(path.join(fs.currentDirectory.path, 'script_runner.yaml'));
|
||||
final pubFile = fs.file(path.join(fs.currentDirectory.path, 'script_runner.yaml'));
|
||||
pubFile.create(recursive: true);
|
||||
await pubFile.writeAsString(
|
||||
contents ??
|
||||
|
||||
Reference in New Issue
Block a user