From 702bf94014ad5acd6bc82a0d50982db266359f42 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 16 Sep 2019 00:00:03 -0400 Subject: [PATCH] shellcheck --- Bakefile | 3 +++ bake/bakefile.py | 42 ++++++++++++++++++++++++++++---------- bake/cli.py | 52 +++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 77 insertions(+), 20 deletions(-) diff --git a/Bakefile b/Bakefile index e79d299..4482aa1 100644 --- a/Bakefile +++ b/Bakefile @@ -11,3 +11,6 @@ system-install: @confirm pypi-upload: python-dev-deps pipenv run setup.py upload + +test: + echo $@ diff --git a/bake/bakefile.py b/bake/bakefile.py index bc67260..242ffff 100644 --- a/bake/bakefile.py +++ b/bake/bakefile.py @@ -1,8 +1,12 @@ -import sys -import os import json +import os +import stat +import sys from random import randint +from shlex import quote as shlex_quote +from tempfile import mkstemp +import delegator import click from .bash import Bash @@ -140,11 +144,7 @@ class TaskScript(BaseAction): if line.startswith(indent_style): return line[len(indent_style) :] - def execute(self, *, blocking=False, debug=False, silent=False, **kwargs): - from tempfile import mkstemp - import stat - from shlex import quote as shlex_quote - + def temp_source(self): tf = mkstemp(suffix=".sh", prefix="bashf-")[1] with open(tf, "w") as f: @@ -154,6 +154,12 @@ class TaskScript(BaseAction): st = os.stat(tf) os.chmod(tf, st.st_mode | stat.S_IEXEC) + return tf + + def execute(self, *, blocking=False, debug=False, silent=False, **kwargs): + + tf = self.temp_source() + stdlib_path = os.path.join(os.path.dirname(__file__), "scripts", "stdlib.sh") args = [shlex_quote(a) for a in self.bashfile.args] @@ -163,11 +169,19 @@ class TaskScript(BaseAction): else: script = shlex_quote(f"{tf} {args} 2>&1 | bake-indent") cmd = f"bash --init-file {shlex_quote(stdlib_path)} -i -c {script}" + if debug: - print(cmd) + click.echo(f"$ {cmd}", err=True) return os.system(cmd) + def shellcheck(self, *, silent=False, debug=False, **kwargs): + tf = self.temp_source() + cmd = f"shellcheck {shlex_quote(tf)} --external-sources --format=json" + if debug: + click.echo(f"$ {cmd}", err=True) + return delegator.run(cmd) + @property def name(self): return self.chunk[0].split(":")[0].strip() @@ -177,6 +191,14 @@ class TaskScript(BaseAction): return self.bashfile.chunks[self._chunk_index] def _iter_source(self): + try: + has_shebang = self.chunk[2].startswith("#!") + except IndexError: + has_shebang = False + + if not has_shebang: + yield "#!/usr/bin/env bash" + for line in self.chunk[1:]: line = self._transform_line(line) if line: @@ -188,9 +210,7 @@ class TaskScript(BaseAction): @property def source_lines(self): - def gen(): - for line in self.bashfile.source_lines: - pass + return [s for s in self._iter_source()] class Bakefile: diff --git a/bake/cli.py b/bake/cli.py index e29cf67..9bb59c8 100644 --- a/bake/cli.py +++ b/bake/cli.py @@ -11,7 +11,7 @@ import pygments.lexers import pygments.formatters -SAFE_ENVIRONS = ["HOME", "PATH"] +SAFE_ENVIRONS = ["HOME", "PATH", "LANG", "LOCALE", "TERM", "VIRTUAL_ENV"] def indent(line): @@ -171,7 +171,7 @@ def entrypoint( bakefile.add_args(*argv) - if _list: + if _list and not shellcheck: __list_json = {"tasks": {}} for _task in bakefile.tasks: @@ -202,6 +202,45 @@ def entrypoint( sys.exit(0) + if shellcheck: + __shellcheck_statuses = [] + for _task in bakefile.tasks: + _task = bakefile[_task] + c = _task.shellcheck(silent=silent, debug=debug) + __shellcheck_statuses.append(c.return_code) + if not c.ok: + # click.echo(click.style("Shellcheck failed!", fg="red"), err=True) + if _json: + echo_json(json.loads(c.out)) + + for report in json.loads(c.out): + level = report["level"] + code = report["code"] + message = report["message"] + line = report["line"] + column = (report["column"], report["endColumn"]) + colored_line_n = click.style( + str(line).zfill(3), bg="black", fg="cyan", bold=True + ) + colored_task = click.style(str(_task), fg="yellow", bold=True) + actual_line = _task.source_lines[line - 1] + + click.echo(f"In {colored_task} line {line}:", err=True) + click.echo(f"{colored_line_n} {actual_line}", err=True) + + length = column[1] - column[0] + if not length: + length = len(actual_line) + + underline = (" " * 4) + (" " * column[0]) + "^" + "-" * (length - 1) + underline += f"SC{code}: {message}" + underline = click.style(level, fg="magenta") + click.style( + underline[len(level) :], fg="red", bold=True + ) + + click.echo(underline, err=True) + + sys.exit(max(__shellcheck_statuses)) if task: try: task = bakefile[task] @@ -209,7 +248,7 @@ def entrypoint( click.echo(click.style(f"Task {task} does not exist!", fg="red")) sys.exit(1) - def execute_task(task, *, next_task=None, silent=False): + def execute_task(task, *, silent=False): if not silent: click.echo( click.style(" + ", fg="white") @@ -217,7 +256,7 @@ def entrypoint( + click.style(":", fg="white"), err=True, ) - return_code = task.execute(yes=yes, next_task=next_task, silent=silent) + return_code = task.execute(yes=yes, silent=silent) if not _continue: if not return_code == 0: @@ -226,11 +265,6 @@ def entrypoint( tasks = task.depends_on(recursive=True) + [task] for task in tasks: - try: - next_task = tasks[tasks.index(task) + 1] - except IndexError: - next_task = None - execute_task(task, next_task=next_task, silent=silent) if not silent: