From f63f1a61ea603776811fcbbce241891bbc3d9776 Mon Sep 17 00:00:00 2001 From: "Soren I. Bjornstad" Date: Thu, 26 Aug 2021 11:51:11 -0500 Subject: [PATCH] create builder infrastructure --- builders.py | 35 +++++++++++++++++++++++++++++++++++ config.py | 3 ++- tzk.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 builders.py diff --git a/builders.py b/builders.py new file mode 100644 index 0000000..427fad1 --- /dev/null +++ b/builders.py @@ -0,0 +1,35 @@ +import functools + +def _lazy_evaluable(func): + """ + Decorator which makes a function lazy-evaluable: that is, when it's + initially called, it returns a zero-argument lambda with the arguments + initially passed wrapped up in it. Calling that lambda has the effect + of executing the function. + + We use this in TZK to allow the user to use function calls in her config + to define the build steps, while not requiring her to write a bunch of + ugly and confusing lambda:'s in the config. The functions that will be called + are prepared during the config and executed later. + """ + + @functools.wraps(func) + def new_func(*args, **kwargs): + my_args = args + my_kwargs = kwargs + @functools.wraps(new_func) + def inner(): + func(*my_args, **my_kwargs) + return inner + return new_func + +# Now a more descriptive name that doesn't expose inner workings +# if the user wants to write her own builder. +tzk_builder = _lazy_evaluable + +@tzk_builder +def printer(username: str): + if username == 'Maud': + raise Exception("No Mauds allowed!") + print(f"Hallelujah, {username} built a wiki!") +printer.name = "Display the user's name" diff --git a/config.py b/config.py index 7078d07..84a1d54 100644 --- a/config.py +++ b/config.py @@ -16,9 +16,10 @@ class ConfigurationManager: if child.is_file() and child.name.endswith('.py'): mod_name = child.name.rsplit('.', 1)[0] if mod_name == 'tzk_config': + sys.path.insert(0, Path("__file__").parent) sys.path.insert(0, str(self.config_path)) self.conf_mod = importlib.import_module(mod_name) - del sys.path[0] + del sys.path[0:1] break else: fail( diff --git a/tzk.py b/tzk.py index a27c014..dcb731a 100644 --- a/tzk.py +++ b/tzk.py @@ -3,6 +3,7 @@ import argparse import os import shutil import sys +import traceback from typing import Optional from config import cm @@ -146,6 +147,52 @@ class InitCommand(CliCommand): tw.install(args.wiki_name, args.tiddlywiki_version_spec, args.author) +class BuildCommand(CliCommand): + cmd = "build" + help = ("Build another wiki or derivative product, " + "such as a public version of the wiki, " + "from this TZK repository.") + + @classmethod + def setup_arguments(cls, parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "product", + metavar="PRODUCT", + help="Name of the product you want to build (defined in your config file).", + ) + + def _precheck(self, product: str) -> None: + if cm.products is None: + fail("No 'products' dictionary is defined in your config file.") + if product not in cm.products: + fail(f"No '{product}' product found in the products dictionary " + f"in your config file. (Available: {', '.join(cm.products.keys())})") + if not cm.products[product]: + fail(f"No build steps are defined in the '{product}' product " + f"in your config file.") + + def execute(self, args: argparse.Namespace) -> None: + self._precheck(args.product) + + steps = cm.products[args.product] + print(f"tzk: Starting build of product '{args.product}'.") + print(f"tzk: Found {len(steps)} build steps.") + + for idx, step in enumerate(steps, 1): + if hasattr(step, 'name'): + print(f"\ntzk: Step {idx}/{len(steps)}: {step.name}") + else: + print(f"\ntzk: Step {idx}/{len(steps)}") + try: + step() + except Exception as e: + print(f"\ntzk: Build of product '{args.product}' failed on step {failed}. " + f"The original error follows:") + traceback.print_exc() + sys.exit(1) + + print(f"\ntzk: Build of product '{args.product}' completed successfully.") + parser = argparse.ArgumentParser() subparsers = parser.add_subparsers()