From f4f448ca03ee04daccf25dba711f2207561c9bd2 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 4 Sep 2022 16:49:59 -0500 Subject: [PATCH 1/4] Added raising from previous Exception !minor --- cookie_composer/cc_overrides.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookie_composer/cc_overrides.py b/cookie_composer/cc_overrides.py index df8e5ee..b664ae9 100644 --- a/cookie_composer/cc_overrides.py +++ b/cookie_composer/cc_overrides.py @@ -146,6 +146,6 @@ def prompt_for_config(prompts: dict, existing_config: Context, no_input=False) - context[key] = val except UndefinedError as err: msg = f"Unable to render variable '{key}'" - raise UndefinedVariableInTemplate(msg, err, context) + raise UndefinedVariableInTemplate(msg, err, context) from err return context From a7ead0d745aae637948661e3bb6ac7a978b0d4a3 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 4 Sep 2022 16:50:41 -0500 Subject: [PATCH 2/4] Updated README.md --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 206d591..9297722 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ [license]: https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.project_name}}/blob/main/LICENSE [coveralls]: https://coveralls.io/github/coordt/cookie-composer?branch=master +https://coordt.github.io/cookie-composer/ + Cookie composer builds on the [cookie cutter](https://github.com/cookiecutter/cookiecutter) project to generate projects based on one or more cookiecutter templates. @@ -24,6 +26,52 @@ Cookie composer builds on the [cookie cutter](https://github.com/cookiecutter/co - Add new capabilities to an existing repository by applying a template - Apply template updates to the generated project +## Introduction + +Cookie Cutter treats templates like sandwiches. There are templates for hamburgers, clubs, and any other kind of sandwich you can dream up. You might have options and defaults on a template, like `Hold the mustard?[False]:` or `Mustard type [dijon]:`, but those are decided by the template author. + + +Templates are treated like finished sandwiches + +If you look closely at the sandwiches (templates), there is usually many things in common. What if we treated the templates as compositions of other templates: + +Sandwiches as a composition of layers + +You now can manage several smaller and specialized templates that provide functionality. Each template's options will be specific to what that template needs. + +Templates broken out as layers on a sandwich + +Cookie Composer uses a composition file to describe the layers required, and even override a template's default answers. + +```yaml +template: bottom-bun +context: + toasting_level: light + buttered: False +--- +template: burger +--- +template: cheese +context: + kind: swiss +--- +template: bacon +context: + cooking_level: crispy +--- +template: ketchup +--- +template: mustard +context: + type: yellow +--- +template: top-bun +context: + toasting_level: light + buttered: False +``` + +We have created [a repo of highly composable templates](https://github.com/coordt/cookiecomposer-templates) as examples or reference. However, Cookie Composer is designed to handle any Cookie Cutter template. ## Purpose From a4c9652625f8e42df722f046f13a2f4e74f7c4bf Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 4 Sep 2022 16:51:22 -0500 Subject: [PATCH 3/4] Updated documentation configuration --- docsrc/_static/css/custom.css | 2 +- docsrc/_static/img/composer-logo.svg | 1 + docsrc/cli.rst | 2 +- docsrc/conf.py | 23 +++++++++++++++++++++++ 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 docsrc/_static/img/composer-logo.svg diff --git a/docsrc/_static/css/custom.css b/docsrc/_static/css/custom.css index ab06996..65abbe5 100644 --- a/docsrc/_static/css/custom.css +++ b/docsrc/_static/css/custom.css @@ -1,4 +1,4 @@ -.sig-prename.descclassname { +dt.sig.py > .sig-prename.descclassname { display: none; } .subheading { diff --git a/docsrc/_static/img/composer-logo.svg b/docsrc/_static/img/composer-logo.svg new file mode 100644 index 0000000..9c42e0d --- /dev/null +++ b/docsrc/_static/img/composer-logo.svg @@ -0,0 +1 @@ + diff --git a/docsrc/cli.rst b/docsrc/cli.rst index 721c227..2c27a44 100644 --- a/docsrc/cli.rst +++ b/docsrc/cli.rst @@ -2,5 +2,5 @@ Command-line Interface ====================== .. click:: cookie_composer.cli:cli - :prog: cookie_composer + :prog: cookie-composer :nested: full diff --git a/docsrc/conf.py b/docsrc/conf.py index f66015e..57dd1d8 100644 --- a/docsrc/conf.py +++ b/docsrc/conf.py @@ -65,8 +65,31 @@ # -- Options for HTML output ------------------------------------------- +html_title = "Cookie Composer" +html_logo = "_static/img/composer-logo.svg" html_theme = "furo" html_static_path = ["_static"] html_css_files = [ "css/custom.css", ] +html_theme_options = { + "top_of_page_button": "edit", + "navigation_with_keys": True, + "footer_icons": [ + { + "name": "GitHub", + "url": "https://github.com/coordt/cookie-composer", + "html": '' + '' + "", + "class": "", + }, + ], +} From a66f17a0471f7178742f9d8d80010bb8d6628cac Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 6 Sep 2022 06:41:41 -0500 Subject: [PATCH 4/4] Added subset of cookiecutter options to CLI --- cookie_composer/cli.py | 104 +++++++++++++++++++++++++++-- cookie_composer/commands/add.py | 20 +++++- cookie_composer/commands/create.py | 22 +++++- cookie_composer/composition.py | 2 +- 4 files changed, 139 insertions(+), 9 deletions(-) diff --git a/cookie_composer/cli.py b/cookie_composer/cli.py index 629ec27..f1fcd43 100644 --- a/cookie_composer/cli.py +++ b/cookie_composer/cli.py @@ -23,6 +23,40 @@ def cli(): @cli.command() @click_log.simple_verbosity_option(logger) +@click.option( + "--no-input", + is_flag=True, + help="Do not prompt for parameters and only use cookiecutter.json file content. " + "Defaults to deleting any cached resources and redownloading them.", +) +@click.option( + "-c", + "--checkout", + help="branch, tag or commit to checkout after git clone", +) +@click.option( + "--directory", + help="Directory within repo that holds cookiecutter.json file " + "for advanced repositories with multi templates in it", +) +@click.option( + "-f", + "--overwrite-if-exists", + is_flag=True, + help="Overwrite the contents of the output directory if it already exists", +) +@click.option( + "-s", + "--skip-if-file-exists", + is_flag=True, + help="Skip the files in the corresponding directories if they already exist", + default=False, +) +@click.option( + "--default-config", + is_flag=True, + help="Do not load a config file. Use the defaults instead", +) @click.argument("path_or_url", type=str, required=True) @click.argument( "output_dir", @@ -30,19 +64,79 @@ def cli(): default=lambda: Path.cwd(), type=click.Path(exists=True, dir_okay=True, file_okay=False, resolve_path=True), ) -def create(path_or_url: str, output_dir: Optional[Path]): +def create( + no_input: bool, + checkout: str, + directory: str, + overwrite_if_exists: bool, + skip_if_file_exists: bool, + default_config: bool, + path_or_url: str, + output_dir: Optional[Path], +): """Create a project from a template or configuration.""" - create_cmd(path_or_url, output_dir) + create_cmd( + path_or_url, + output_dir, + no_input, + checkout, + directory, + overwrite_if_exists, + skip_if_file_exists, + default_config, + ) @cli.command() @click_log.simple_verbosity_option(logger) -@click.option("--no-input", is_flag=True) -@click.argument("path_or_url", type=str, required=True) +@click.option( + "--no-input", + is_flag=True, + help="Do not prompt for parameters and only use cookiecutter.json file content. " + "Defaults to deleting any cached resources and redownloading them.", +) +@click.option( + "-c", + "--checkout", + help="branch, tag or commit to checkout after git clone", +) +@click.option( + "--directory", + help="Directory within repo that holds cookiecutter.json file " + "for advanced repositories with multi templates in it", +) +@click.option( + "-f", + "--overwrite-if-exists", + is_flag=True, + help="Overwrite the contents of the output directory if it already exists", +) +@click.option( + "-s", + "--skip-if-file-exists", + is_flag=True, + help="Skip the files in the corresponding directories if they already exist", + default=False, +) +@click.option( + "--default-config", + is_flag=True, + help="Do not load a config file. Use the defaults instead", +) +@click.argument("path_or_url", type=str, required=True, help="The template or composition path or URL") @click.argument( "destination", required=False, type=click.Path(exists=True, file_okay=False, writable=True, path_type=Path) ) -def add(no_input: bool, path_or_url: str, destination: Optional[Path]): +def add( + no_input: bool, + checkout: str, + directory: str, + overwrite_if_exists: bool, + skip_if_file_exists: bool, + default_config: bool, + path_or_url: str, + destination: Optional[Path], +): """Add a template or configuration to an existing project.""" destination = destination or Path(".") try: diff --git a/cookie_composer/commands/add.py b/cookie_composer/commands/add.py index 703e997..e74a617 100644 --- a/cookie_composer/commands/add.py +++ b/cookie_composer/commands/add.py @@ -29,6 +29,11 @@ def add_cmd( path_or_url: str, destination_dir: Optional[Path] = None, no_input: bool = False, + checkout: Optional[str] = None, + directory: Optional[str] = None, + overwrite_if_exists: bool = False, + skip_if_file_exists: bool = False, + default_config: bool = False, ): """ Add a template or configuration to an existing project. @@ -37,6 +42,11 @@ def add_cmd( path_or_url: A URL or string to add the template or configuration destination_dir: The project directory to add the layer to no_input: If ``True`` force each layer's ``no_input`` attribute to ``True`` + checkout: The branch, tag or commit to check out after git clone + directory: Directory within repo that holds cookiecutter.json file + overwrite_if_exists: Overwrite the contents of the output directory if it already exists + skip_if_file_exists: Skip the files in the corresponding directories if they already exist + default_config: Do not load a config file. Use the defaults instead Raises: GitError: If the destination_dir is not a git repository @@ -57,7 +67,15 @@ def add_cmd( addl_composition = read_composition(path_or_url) logger.info(f"Adding composition {path_or_url} to {output_dir}.") else: - tmpl = LayerConfig(template=path_or_url) + overwrite_rules = ["*"] if overwrite_if_exists else [] + tmpl = LayerConfig( + template=path_or_url, + directory=directory, + checkout=checkout, + no_input=no_input or default_config, + skip_if_file_exists=skip_if_file_exists, + overwrite=overwrite_rules, + ) addl_composition = Composition(layers=[tmpl]) logger.info(f"Adding template {path_or_url} to {output_dir}.") diff --git a/cookie_composer/commands/create.py b/cookie_composer/commands/create.py index e0788f4..334dbd5 100644 --- a/cookie_composer/commands/create.py +++ b/cookie_composer/commands/create.py @@ -22,6 +22,11 @@ def create_cmd( path_or_url: str, output_dir: Optional[Path] = None, no_input: bool = False, + checkout: Optional[str] = None, + directory: Optional[str] = None, + overwrite_if_exists: bool = False, + skip_if_file_exists: bool = False, + default_config: bool = False, ) -> Path: """ Generate a new project from a composition file, local template or remote template. @@ -30,6 +35,11 @@ def create_cmd( path_or_url: The path or url to the composition file or template output_dir: Where to generate the project no_input: If ``True`` force each layer's ``no_input`` attribute to ``True`` + checkout: The branch, tag or commit to check out after git clone + directory: Directory within repo that holds cookiecutter.json file + overwrite_if_exists: Overwrite the contents of the output directory if it already exists + skip_if_file_exists: Skip the files in the corresponding directories if they already exist + default_config: Do not load a config file. Use the defaults instead Returns: The path to the generated project. @@ -39,8 +49,16 @@ def create_cmd( composition = read_composition(path_or_url) logger.info(f"Rendering composition {path_or_url} to {output_dir}.") else: - tmpl = LayerConfig(template=path_or_url) - composition = Composition(layers=[tmpl], destination=output_dir) + overwrite_rules = ["*"] if overwrite_if_exists else [] + tmpl = LayerConfig( + template=path_or_url, + directory=directory, + checkout=checkout, + no_input=no_input or default_config, + skip_if_file_exists=skip_if_file_exists, + overwrite=overwrite_rules, + ) + composition = Composition(layers=[tmpl]) logger.info(f"Rendering template {path_or_url} to {output_dir}.") rendered_layers = render_layers(composition.layers, output_dir, no_input=no_input) rendered_composition = RenderedComposition( diff --git a/cookie_composer/composition.py b/cookie_composer/composition.py index 9ec9eba..99d2c46 100644 --- a/cookie_composer/composition.py +++ b/cookie_composer/composition.py @@ -249,7 +249,7 @@ def write_composition(layers: List[LayerConfig], destination: Union[str, Path]): def write_rendered_composition(composition: RenderedComposition): """ - Write the composition file using the rendered layers to the appropriate. + Write the composition file using the rendered layers to the appropriate place. Args: composition: The rendered composition object to export