From 047028f9f0fc5729400017b997bf0cd05898aea1 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 15 Sep 2019 22:29:09 -0400 Subject: [PATCH] tests --- bake/bakefile.py | 16 ++++-- bake/cli.py | 101 +++++++++++++++++++++++---------- bake/clint.py | 44 ++++++++++++++ bake/tests/conftest.py | 2 + bake/tests/fixtures/2.Bakefile | 5 ++ bake/tests/test_filters.py | 30 ++++++++-- setup.py | 2 +- 7 files changed, 161 insertions(+), 39 deletions(-) create mode 100644 bake/clint.py diff --git a/bake/bakefile.py b/bake/bakefile.py index 56ad39e..bc67260 100644 --- a/bake/bakefile.py +++ b/bake/bakefile.py @@ -22,12 +22,19 @@ class FilterNotAvailable(ValueError): pass -class TaskFilter: +class BaseAction: + @property + def is_filter(self): + if not hasattr(self, "_chunk_index"): + return True + + +class TaskFilter(BaseAction): def __init__(self, s): self.source = s def __str__(self): - return f"{self.source!r}" + return f"{self.source}" @property def name(self): @@ -64,6 +71,7 @@ class TaskFilter: user_value = click.prompt(f" What is {int1} times {int2}?") if int(user_value) != int1 * int2: + click.echo("Aborted!", err=True) sys.exit(1) else: @@ -74,7 +82,7 @@ class TaskFilter: self.execute_confirm(yes=yes, **self.arguments) -class TaskScript: +class TaskScript(BaseAction): def __init__(self, bashfile, chunk_index=None): self.bashfile = bashfile self._chunk_index = chunk_index @@ -86,7 +94,7 @@ class TaskScript: return f"" def __str__(self): - return f"{self.name!r}" + return f"{self.name}" @property def declaration_line(self): diff --git a/bake/cli.py b/bake/cli.py index f684294..14539fa 100644 --- a/bake/cli.py +++ b/bake/cli.py @@ -1,9 +1,15 @@ import sys import click -import crayons +import json from .bakefile import Bakefile from .config import config +from .clint import eng_join + +import pygments +import pygments.lexers +import pygments.formatters + SAFE_ENVIRONS = ["HOME", "PATH"] @@ -12,6 +18,17 @@ def indent(line): return f'{" " * 4}{line}' +def echo_json(obj): + _json = json.dumps(obj, indent=2) + + if sys.stdin.isatty(): + _json = pygments.highlight( + _json, pygments.lexers.JsonLexer(), pygments.formatters.TerminalFormatter() + ) + + click.echo(_json, err=False) + + @click.command(context_settings=dict(help_option_names=["-h", "--help"])) @click.argument( "task", @@ -69,16 +86,23 @@ def indent(line): # multiple=True, # help="task ARGV argument (can be passed multiple times).", ) -@click.option("--no-color", is_flag=True, type=click.BOOL, help="Disable colors.") @click.option("--silent", "-s", is_flag=True, type=click.BOOL, help="Reduce output.") @click.option( "--environ-json", - "-j", + "-e", nargs=1, type=click.STRING, help="environment variables, in JSON format.", ) -def task( +@click.option( + "--json", + "-j", + "_json", + is_flag=True, + type=click.BOOL, + help="Output in JSON format (stdout).", +) +def entrypoint( *, task, bakefile, @@ -90,15 +114,15 @@ def task( debug, silent, insecure, - no_color, allow, + _json, yes, ): """bake — the familiar Bash/Make hybrid.""" # Default to list behavior, when no task is provided. - if no_color: - crayons.DISABLE_COLOR = True + if _json: + silent = True SAFE_ENVIRONS.extend(allow) @@ -133,14 +157,14 @@ def task( argv.append(argument) if debug: - click.echo(f" + argv: {argv!r}") - click.echo(f" + environ: {environ!r}") + click.echo(f" + argv: {argv!r}", err=True) + click.echo(f" + environ: {environ!r}", err=True) for env in environ: key, value = env if debug: click.echo( - f" + Setting environ: {crayons.red(key)} {crayons.white('=')} {value}.", + f" + Setting environ: {click.style(key, fg='red')} {click.style('=', fg='white')} {value}.", err=True, ) bakefile.add_environ(key, value) @@ -148,29 +172,56 @@ def task( bakefile.add_args(*argv) if _list: - for task in bakefile.tasks: - print(f" - {task}") + __list_json = {"tasks": {}} + + for _task in bakefile.tasks: + depends_on = bakefile[_task].depends_on(recursive=True) + if depends_on: + deps = [] + for dep in depends_on: + if dep.is_filter: + dep = click.style(str(dep), fg="yellow") + deps.append(str(dep)) + deps = f"\n {click.style('+', fg='yellow', bold=True)} {eng_join(deps)}." + else: + deps = "" + colon = "" if not deps else "…" + + __list_json["tasks"].update( + {_task: {"depends_on": [str(d) for d in depends_on]}} + ) + + if not silent: + click.echo( + f" {click.style('-', fg='green', bold=True)} {click.style(_task, bold=True)}{colon}{deps}", + err=True, + ) + + if _json: + echo_json(__list_json) + sys.exit(0) if task: try: task = bakefile[task] except KeyError: - click.echo(crayons.red(f"Task {task} does not exist!")) + click.echo(click.style(f"Task {task} does not exist!", fg="red")) sys.exit(1) def execute_task(task, *, next_task=None, silent=False): if not silent: click.echo( - crayons.white(" + ") - + crayons.yellow(f"Executing {task}") - + crayons.white(":") + click.style(" + ", fg="white") + + click.style(f"Executing {task}", fg="yellow") + + click.style(":", fg="white"), + err=True, ) return_code = task.execute(yes=yes, next_task=next_task, silent=silent) if not _continue: if not return_code == 0: - click.echo(f"Task {task} failed!") + click.echo(f"Task {task} failed!", err=True) sys.exit(return_code) tasks = task.depends_on(recursive=True) + [task] @@ -184,21 +235,13 @@ def task( if not silent: click.echo( - crayons.white(" + ") + crayons.green("Done") + crayons.white(".") + click.style(" + ", fg="white") + + click.style("Done", fg="green") + + click.style(".", fg="white"), + err=True, ) sys.exit(0) -def entrypoint(): - try: - main() - except KeyboardInterrupt: - print("ool beans.") - - -def main(): - task() - - if __name__ == "__main__": entrypoint() diff --git a/bake/clint.py b/bake/clint.py new file mode 100644 index 0000000..1087879 --- /dev/null +++ b/bake/clint.py @@ -0,0 +1,44 @@ +COMMA = "," +CONJUNCTION = "and" +SPACE = " " + +NEWLINES = ("\n", "\r", "\r\n") +MAX_WIDTHS = [] + + +def tsplit(string, delimiters): + """Behaves str.split but supports tuples of delimiters.""" + delimiters = tuple(delimiters) + if len(delimiters) < 1: + return [string] + final_delimiter = delimiters[0] + for i in delimiters[1:]: + string = string.replace(i, final_delimiter) + return string.split(final_delimiter) + + +def eng_join(l, conj=CONJUNCTION, separator=COMMA): + """Joins lists of words. Oxford comma and all.""" + + collector = [] + left = len(l) + separator = separator + SPACE + conj = conj + SPACE + + for _l in l[:]: + + left += -1 + + collector.append(_l) + if left == 1: + if len(l) == 2: + collector.append(SPACE) + else: + collector.append(separator) + + collector.append(conj) + + elif left is not 0: + collector.append(separator) + + return str().join(collector) diff --git a/bake/tests/conftest.py b/bake/tests/conftest.py index a94658f..cc2f47f 100644 --- a/bake/tests/conftest.py +++ b/bake/tests/conftest.py @@ -1,5 +1,7 @@ import os +os.environ["PYTHONUNBUFFERED"] = "1" + import pytest import delegator diff --git a/bake/tests/fixtures/2.Bakefile b/bake/tests/fixtures/2.Bakefile index 894ba0f..6cc8e34 100644 --- a/bake/tests/fixtures/2.Bakefile +++ b/bake/tests/fixtures/2.Bakefile @@ -1,2 +1,7 @@ kinda-dangerous: @confirm + echo 'success1' really-dangerous: @confirm:secure + echo 'success2' + +mostly-harmless: kinda-dangerous +mostly-dangerous: really-dangerous diff --git a/bake/tests/test_filters.py b/bake/tests/test_filters.py index 1b4cc92..90c37a1 100644 --- a/bake/tests/test_filters.py +++ b/bake/tests/test_filters.py @@ -1,10 +1,30 @@ def test_confirm(bake): c = bake("kinda-dangerous", fixture="2", block=False) - # c.expect("?", "Y") - # c.block() - assert "kenneth" in c.out + c.expect(":", timeout=0.5) + c.send("N\n") + c.block() + assert "Aborted!" in c.out def test_confirm_secure(bake): - c = bake("kinda-dangerous", fixture="2", block=False) - c.expect + c = bake("really-dangerous", fixture="2", block=False) + c.expect(":", timeout=0.5) + c.send("4222\n") + c.block() + assert "Aborted!" in c.out + + +def test_confirm_dep(bake): + c = bake("mostly-harmless", fixture="2", block=False) + c.expect(":", timeout=0.5) + c.send("N\n") + c.block() + assert "Aborted!" in c.out + + +def test_confirm_secure(bake): + c = bake("mostly-dangerous", fixture="2", block=False) + c.expect(":", timeout=0.5) + c.send("4222\n") + c.block() + assert "Aborted!" in c.out diff --git a/setup.py b/setup.py index 1037dd4..9a0afd9 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", "appdirs"] +REQUIRED = ["click", "delegator.py", "pygments", "appdirs"] # What packages are optional? EXTRAS = {