diff --git a/README.md b/README.md index c6014f5..c9138e9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ -# Bashfile: Like Make + Bash, Combined. +# `bashf`: Kinda like Make & Bash, combined. I love using `Makefile`s for one-off tasks in projects. The problem with doing this, is you can't use familiar bash–isms when doing so, as GNU Make doesn't use the familiar Bash sytnax. This project seeks to bridge these works. +## Installation + +Install `bashf` via: + + $ brew install kennethreitz/-/bashf --head diff --git a/bashf/bash.py b/bashf/bash.py index 5ad5499..fe5fbdf 100644 --- a/bashf/bash.py +++ b/bashf/bash.py @@ -4,6 +4,9 @@ bash.py module import re import time import json as json_lib +import os +import stat +from tempfile import mkstemp from shlex import quote as shlex_quote import delegator @@ -100,9 +103,37 @@ class Bash: """execute the bash process as a child of this process""" return BashProcess(parent=self, args=args, **kwargs) - def command(self, script: str, **kwargs) -> BashProcess: + def command(self, script: str, debug=False, **kwargs) -> BashProcess: """form up the command with shlex and execute""" - return self._exec(f"-c {shlex_quote(script)}", **kwargs) + + tf = mkstemp(suffix='.sh', prefix='bashf-')[1] + + with open(tf, 'w') as f: + f.write(script) + + # Mark the temporary file as executable. + st = os.stat(tf) + os.chmod(tf, st.st_mode | stat.S_IEXEC) + + stdlib_path = os.path.join( + os.path.dirname(__file__), 'scripts', 'stdlib.sh' + ) + # print(stdlib_path) + + # cmd = f"bash -c {(script)}" + script = shlex_quote(f"unbuffer {tf} 2>&1 | bashf-indent") + cmd = f'bash --init-file {shlex_quote(stdlib_path)} -i -c {script} ' + + if debug: + print(cmd) + + return_code = os.system(cmd) + + if not debug: + # Cleanup temporary file. + os.remove(tf) + + return return_code def run(script=None, **kwargs): diff --git a/bashf/bashfile.py b/bashf/bashfile.py index 60e280e..ccb989d 100644 --- a/bashf/bashfile.py +++ b/bashf/bashfile.py @@ -48,9 +48,6 @@ class TaskScript: if t.name not in [task.name for task in tasks]: tasks.insert(i + 1, t) - # if reverse: - # tasks = list(reversed(tasks)) - return tasks @classmethod diff --git a/bashf/cli.py b/bashf/cli.py index 98db96b..757a58b 100644 --- a/bashf/cli.py +++ b/bashf/cli.py @@ -4,6 +4,10 @@ import crayons from .bashfile import Bashfile +def indent(line): + return f'{" " * 4}{line}' + + @click.command() @click.argument( 'task', @@ -31,6 +35,13 @@ from .bashfile import Bashfile multiple=True, help='task environment variable (can be passed multiple times).', ) +@click.option( + '--fail', + '-x', + is_flag=True, + type=click.BOOL, + help='Fail immediately, if any task fails.', +) @click.option( '--arg', '-a', @@ -39,6 +50,9 @@ from .bashfile import Bashfile multiple=True, help='task ARGV arguments (can be passed multiple times).', ) +@click.option( + '--quiet', '-q', is_flag=True, type=click.BOOL, help='Reduce output.' +) @click.option( '--environ-json', '-j', @@ -47,7 +61,17 @@ from .bashfile import Bashfile help='environment variables, in JSON format.', ) def task( - *, task, bashfile, arg, _list, environ, environ_json, shellcheck, debug + *, + task, + bashfile, + arg, + _list, + environ, + fail, + environ_json, + shellcheck, + debug, + quiet, ): """bashf — Bashfile runner (the familiar Bash/Make hybrid).""" # Default to list behavior, when no task is provided. @@ -80,22 +104,22 @@ def task( try: task = bashfile[task] except KeyError: - click.echo(f'Task {task!r} does not exist!') + click.echo(crayons.red(f'Task {task!r} does not exist!')) sys.exit(1) # print(task) for task in task.depends_on(recursive=True): - cmd = task.execute() + if not quiet: + click.echo(crayons.yellow(f'Executing task {task.name!r}…')) + return_code = task.execute() - for line in cmd.output: - click.echo(line, nl=False, err=False) + if fail: + if not return_code == 0: + click.echo(f'Task {task.name!r} failed!') + sys.exit(return_code) - # if cmd.err: - # click.echo(cmd.err, nl=False, err=True) - - if not cmd.ok: - click.echo(f'Task {task.name!r} failed!') - sys.exit(cmd.return_code) + click.echo('Done!') + sys.exit(0) def entrypoint(): diff --git a/bashf/scripts/stdlib.sh b/bashf/scripts/stdlib.sh new file mode 100644 index 0000000..1e82d39 --- /dev/null +++ b/bashf/scripts/stdlib.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +if [ "$(uname)" == Darwin ]; then + bashf-sed() { command sed -l "$@"; } +else + bashf-sed() { command sed -u "$@"; } +fi + +# Syntax sugar. +bashf-indent() { + bashf-sed "s/^/ /" +} diff --git a/setup.py b/setup.py index e1d292a..5e39deb 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ REQUIRES_PYTHON = '>=3.6.0' VERSION = '0.1.0' # What packages are required for this module to be executed? -REQUIRED = ['click', 'delegator.py', 'crayons', 'bash.py'] +REQUIRED = ['click', 'delegator.py', 'crayons'] # What packages are optional? EXTRAS = {