Merge pull request #12 from kennethreitz/git-config

Git config
This commit is contained in:
2019-09-22 21:13:23 -04:00
committed by GitHub
6 changed files with 184 additions and 82 deletions
+1
View File
@@ -0,0 +1 @@
from .bakefile import Bakefile
+49 -42
View File
@@ -11,7 +11,8 @@ import networkx
from . import utils
from .bash import Bash
from .constants import INDENT_STYLES
from .cache import Cache
from .constants import INDENT_STYLES, DEFAULT_BAKEFILE_NAME
from .exceptions import FilterNotAvailable, NoBakefileFound, TaskNotInBashfile
@@ -35,6 +36,10 @@ class Bakefile:
self._tasks = None
self._graph = None
@property
def cache(self):
return Cache(bf=self)
@property
def graph(self):
if self._graph:
@@ -131,9 +136,14 @@ class Bakefile:
@classmethod
def find(
Class, *, filename="Bashfile", root=os.getcwd(), max_depth=4, topdown=False
Class,
*,
filename=DEFAULT_BAKEFILE_NAME,
root=os.getcwd(),
max_depth=4,
topdown=False,
):
"""Returns the path of a Pipfile in parent directories."""
"""Returns the path of a Bakefile in parent directories."""
i = 0
for c, d, f in utils.walk_up(root):
@@ -197,7 +207,7 @@ class Bakefile:
tasks = {}
for i, chunk in enumerate(self.chunks):
script = TaskScript._from_chunk_index(bashfile=self, i=i)
script = TaskScript._from_chunk_index(bf=self, i=i)
tasks[script.name] = script
self._tasks = tasks
@@ -251,9 +261,9 @@ class BaseAction:
class TaskFilter(BaseAction):
"""A filter, which can be applied to a task."""
def __init__(self, s, bashfile):
def __init__(self, s, *, bf):
self.source = s
self.bashfile = bashfile
self.bf = bf
self.__uuid = uuid4().hex
self.do_skip = None
@@ -271,7 +281,7 @@ class TaskFilter(BaseAction):
def __hash__(self):
"""Important for (networkx) graph traversal."""
return hash((self.bashfile, self.source, self.__uuid))
return hash((self.bf, self.source, self.__uuid))
@property
def name(self):
@@ -339,39 +349,33 @@ class TaskFilter(BaseAction):
return ("confirmed", True)
def execute_skip_if(self, *, key, cache=None, **kwargs):
def execute_skip_if(self, *, key, **kwargs):
"""Determines if it is appropriate to skip the dependent TaskScript."""
if cache is None:
# I'm cheating here, and shoving stuff into the git folder (which I assume is there).
# TODO: Improve this — look into $ git config --local (shell) use instead.
cache = f".git/bake-hash-{sha256(key.encode('utf-8')).hexdigest()}"
# Ensure the provided filekey exists, and if it doesn't, abort mission.
key_path = os.path.abspath(key)
cache_path = os.path.abspath(cache)
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
if not os.path.exists(key_path):
self.do_skip = False
return ("skip", False)
# return ("skip", False)
return
if os.path.exists(cache_path):
with open(cache_path, "r") as f:
old_hash = f.read().strip()
else:
old_hash = "NOPE"
key = sha256(key.encode("utf-8")).hexdigest()
old_hash = str(self.bf.cache[key])
# Get the current filestate hashsum.
with open(key_path, "r") as f:
current_hash = sha256(f.read().encode("utf-8")).hexdigest()
with open(cache_path, "w") as f:
f.write(current_hash)
self.bf.cache[key] = current_hash
if old_hash == current_hash:
self.do_skip = True
return ("skip", True)
# return ("skip", True)
return
self.do_skip = False
return ("skip", False)
# return ("skip", False)
return
def execute(self, yes=False, **kwargs):
"""This should probably be two different classes…
@@ -390,9 +394,9 @@ class FakeTaskScript(BaseAction):
Ussually typos. They display red in the terminal. Neat.
"""
def __init__(self, s, bashfile):
def __init__(self, s, *, bf):
self.source = s
self.bashfile = bashfile
self.bf = bf
def __str__(self):
"""The color red, as mentioned above."""
@@ -405,8 +409,8 @@ class TaskScript(BaseAction):
You're pretty witty & intelligent — you can infer what this class is for, based on its name.
"""
def __init__(self, bashfile, chunk_index=None):
self.bashfile = bashfile
def __init__(self, *, bf, chunk_index=None):
self.bf = bf
self._chunk_index = chunk_index
if self._chunk_index is None:
@@ -419,7 +423,7 @@ class TaskScript(BaseAction):
return f"{self.name}"
def __hash__(self):
return hash((self.bashfile, self._chunk_index))
return hash((self.bf, self._chunk_index))
def __eq__(self, other):
if hasattr(other, "_chunk_index"):
@@ -434,20 +438,20 @@ class TaskScript(BaseAction):
task_strings = self.declaration_line.split(":", 1)[1].split()
task_name_index_tuples = [
(self.bashfile.find_chunk(task_name=s), s) for s in task_strings
(self.bf.find_chunk(task_name=s), s) for s in task_strings
]
for i, task_string in task_name_index_tuples:
if task_string.startswith("@"):
if include_filters:
yield TaskFilter(task_string, bashfile=self.bashfile)
yield TaskFilter(task_string, bf=self.bf)
elif i is None:
if include_fakes:
yield FakeTaskScript(task_string, bashfile=self.bashfile)
yield FakeTaskScript(task_string, bf=self.bf)
else:
# Otherwise, create the task.
yield TaskScript(chunk_index=i, bashfile=self.bashfile)
yield TaskScript(chunk_index=i, bf=self.bf)
actions = [t for t in gen_actions()]
@@ -455,9 +459,7 @@ class TaskScript(BaseAction):
graph = {}
actions = []
edge_view = networkx.edge_dfs(
self.bashfile.graph, self, orientation="original"
)
edge_view = networkx.edge_dfs(self.bf.graph, self, orientation="original")
for parent, child, _ in edge_view:
if parent not in graph:
@@ -491,9 +493,9 @@ class TaskScript(BaseAction):
return actions
@classmethod
def _from_chunk_index(Class, bashfile, *, i):
def _from_chunk_index(Class, bf, *, i):
return Class(bashfile=bashfile, chunk_index=i)
return Class(bf=bf, chunk_index=i)
@staticmethod
def _transform_line(line, *, indent_styles=INDENT_STYLES):
@@ -543,7 +545,7 @@ class TaskScript(BaseAction):
self, *, blocking=False, debug=False, interactive=False, silent=False, **kwargs
):
args = " ".join([shlex_quote(a) for a in self.bashfile.args])
args = " ".join([shlex_quote(a) for a in self.bf.args])
args = args if args else "\b"
sed_magic = (
"2>&1 | sed >&2 's/^/ | /'" if not (silent or interactive) else "\b"
@@ -561,7 +563,12 @@ class TaskScript(BaseAction):
if debug:
click.echo(f" {click.style('$', fg='green')} {script}", err=True)
bash = Bash(interactive=interactive)
if silent:
bash_interactive = True
else:
bash_interactive = interactive
bash = Bash(interactive=bash_interactive)
return bash.command(script, quote=False)
@property
@@ -570,7 +577,7 @@ class TaskScript(BaseAction):
@property
def chunk(self):
return self.bashfile.chunks[self._chunk_index]
return self.bf.chunks[self._chunk_index]
def _iter_source(self):
try:
+101
View File
@@ -0,0 +1,101 @@
import click
import delegator
PREFIX = "bake"
SEPERATOR = "."
__all__ = ['Cache']
class Cache:
prefix = PREFIX
seperator = SEPERATOR
def __init__(self, *, bf, debug=False, enabled=True):
self.bf = bf
self.enabled = enabled
self.debug = debug
try:
# Assert git exists, and appears functioning.
c = delegator.run("git --version")
assert c.ok
if self.debug:
click.echo(" + cache.git.ok: true", err=True)
# Record the top-level directory of the git repository.
c = delegator.run("git rev-parse --show-toplevel")
self.git_root = c.out.strip()
if self.debug:
click.echo(f" + cache.git.root: {self.git_root!r}", err=True)
# Assert Bakefile exists within it.
assert self.bf.path.startswith(self.git_root)
if self.debug:
click.echo(f" + cache.git.contains_bakefile: true", err=True)
except AssertionError:
# Cache is disabled.
self.enabled = False
if debug:
click.echo(f" + cache.enabled: true", err=True)
def __repr__(self):
return f"<Cache enabled={self.enabled}>"
def _key_for_hashes(self, key):
return self.seperator.join((self.prefix, "hashes", key))
def clear(self):
for key in self:
del self[key]
if self.debug:
click.echo(" + cache.git.cleared: true", err=True)
def __iter__(self):
# TODO: Print these.
cmd = "git config --local --list"
if self.debug:
click.echo(f" {click.style('$', fg='green')} {cmd}", err=True)
c = delegator.run(cmd)
for result in c.out.split("\n"):
if result.startswith(self.prefix):
print(result.split("=", -1)[0])
yield result.split("=", -1)[0]
def __getitem__(self, k):
key = self._key_for_hashes(k)
cmd = f"git config --local --get {key}"
if self.debug:
click.echo(f" {click.style('$', fg='green')} {cmd}", err=True)
c = delegator.run()
if c.ok:
return c.out.strip()
else:
return None
def __setitem__(self, k, v):
key = self._key_for_hashes(k)
cmd = f"git config --local {key} {v}"
if self.debug:
click.echo(f" {click.style('$', fg='green')} {cmd}", err=True)
c = delegator.run(cmd)
return c.ok
def __delitem__(self, k):
cmd = f"git config --local --unset {k}"
if self.debug:
click.echo(f" {click.style('$', fg='green')} {cmd}", err=True)
c = delegator.run(cmd)
return c.ok
+31 -39
View File
@@ -75,7 +75,7 @@ def echo_json(obj):
type=click.STRING,
default="__LIST_ALL__",
envvar="BAKE_TASK",
# required=False,
required=False,
)
@click.option(
"--bakefile",
@@ -93,13 +93,16 @@ def echo_json(obj):
is_flag=True,
help="Lists available tasks (and their dependencies).",
)
@click.option(
"--clear", default=False, is_flag=True, help="Clears the cache (e.g. @skip)."
)
@click.option(
"--levels",
"-l",
default=None,
nargs=1,
type=click.INT,
help="List only a given number of '/' levels of tasks.",
help="The number of '/' levels to list.",
)
@click.option(
"--help", "-h", default=False, is_flag=True, help="Show this message and exit."
@@ -185,6 +188,7 @@ def entrypoint(
allow,
_json,
no_deps,
clear,
interactive,
yes,
help,
@@ -209,10 +213,11 @@ def entrypoint(
# Establish the Bakefile.
try:
if bakefile == "__BAKEFILE__":
bakefile = Bakefile.find(root=".", filename="Bakefile")
else:
bakefile = Bakefile(path=bakefile)
bf = (
Bakefile.find(root=".", filename="Bakefile")
if bakefile == "__BAKEFILE__"
else Bakefile(path=bakefile)
)
except NoBakefileFound:
click.echo(click.style("No Bakefile found!", fg="red"), err=True)
@@ -220,42 +225,31 @@ def entrypoint(
sys.exit(0)
if debug:
click.echo(f" + Bakefile: {bakefile.path}", err=True)
click.echo(f" + Bakefile: {bf.path}", err=True)
# Clear the cache, if asked to do so.
if clear:
bf.cache.clear()
# --source (internal API)
if source:
def echo_generator(g):
for g in g:
click.echo(g)
if source == "__init__":
source = random.choice(list(bakefile.tasks.keys()))
task = bakefile.tasks[source]
source = task.gen_source(
sources=[task.bashfile.funcs_source, task.bashfile.root_source]
)
else:
task = bakefile.tasks[source]
source = task.gen_source(
sources=[
task.bashfile.funcs_source,
task.bashfile.root_source,
task.source,
]
)
task = bf.tasks[source]
source = task.gen_source(
sources=[task.bf.funcs_source, task.bf.root_source, task.source]
)
for source_line in source:
click.echo(source_line)
sys.exit(0)
if not insecure:
for key in bakefile.environ:
for key in bf.environ:
if key not in SAFE_ENVIRONS:
del bakefile.environ[key]
del bf.environ[key]
if environ_json:
bakefile.add_environ_json(environ_json)
bf.add_environ_json(environ_json)
argv = []
environ = []
@@ -278,9 +272,9 @@ def entrypoint(
f" + Setting environ: {click.style(key, fg='red')} {click.style('=', fg='white')} {value}.",
err=True,
)
bakefile.add_environ(key, value)
bf.add_environ(key, value)
bakefile.add_args(*argv)
bf.add_args(*argv)
if _list:
__list_json = {"tasks": {}}
@@ -288,19 +282,17 @@ def entrypoint(
# Enable level filtering.
if levels is not None:
task_list = []
for _task in bakefile.tasks:
for _task in bf.tasks:
if len(_task.split("/")) <= levels:
task_list.append(_task)
else:
task_list = bakefile.tasks
task_list = bf.tasks
if sort:
task_list = sorted(task_list)
for _task in task_list:
depends_on = bakefile[_task].depends_on(
include_filters=False, recursive=True
)
depends_on = bf[_task].depends_on(include_filters=False, recursive=True)
if no_deps:
depends_on = ()
@@ -323,7 +315,7 @@ def entrypoint(
)
if not silent:
tasks_unechoed = len(bakefile.tasks) - len(task_list)
tasks_unechoed = len(bf.tasks) - len(task_list)
if tasks_unechoed:
bake_command = str(click.style(f"bake --levels {levels + 1}", fg="red"))
@@ -340,14 +332,14 @@ def entrypoint(
if task:
try:
task = bakefile[task]
task = bf[task]
except KeyError:
click.echo(click.style(f"Task {task} does not exist!", fg="red"))
sys.exit(1)
def execute_task(task, *, silent=False):
try:
edges = list(bakefile.graph.out_edges(task))[0]
edges = list(bf.graph.out_edges(task))[0]
except IndexError:
edges = list()
+1
View File
@@ -14,3 +14,4 @@ SAFE_ENVIRONS = [
"PYTHONDONTWRITEBYTECODE",
"BAKE_SILENT",
]
DEFAULT_BAKEFILE_NAME = "Bakefile"
+1 -1
View File
@@ -18,7 +18,7 @@ URL = "https://github.com/kennethreitz/bake"
EMAIL = "me@kennethreitz.org"
AUTHOR = "Kenneth Reitz"
REQUIRES_PYTHON = ">=3.6.0"
VERSION = "0.9.0"
VERSION = "0.10.0"
# What packages are required for this module to be executed?
REQUIRED = ["click", "delegator.py", "pygments", "networkx"]