Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doc updates #11

Merged
merged 5 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/

<!-- end-badges -->

Cookie composer builds on the [cookie cutter](https://github.com/cookiecutter/cookiecutter) project to generate projects based on one or more cookiecutter templates.
Expand All @@ -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.


<img src="https://raw.githubusercontent.com/coordt/cookie-composer/master/docsrc/_static/img/sandwiches.png" alt="Templates are treated like finished sandwiches" style="zoom:50%;" />

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:

<img src="https://raw.githubusercontent.com/coordt/cookie-composer/master/docsrc/_static/img/compositions.png" alt="Sandwiches as a composition of layers" style="zoom:50%;" />

You now can manage several smaller and specialized templates that provide functionality. Each template's options will be specific to what that template needs.

<img src="https://raw.githubusercontent.com/coordt/cookie-composer/master/docsrc/_static/img/layers.png" alt="Templates broken out as layers on a sandwich" style="zoom:50%;" />

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

Expand Down
2 changes: 1 addition & 1 deletion cookie_composer/cc_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
104 changes: 99 additions & 5 deletions cookie_composer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,120 @@ 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",
required=False,
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:
Expand Down
20 changes: 19 additions & 1 deletion cookie_composer/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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}.")

Expand Down
22 changes: 20 additions & 2 deletions cookie_composer/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion cookie_composer/composition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docsrc/_static/css/custom.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.sig-prename.descclassname {
dt.sig.py > .sig-prename.descclassname {
display: none;
}
.subheading {
Expand Down
1 change: 1 addition & 0 deletions docsrc/_static/img/composer-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docsrc/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ Command-line Interface
======================

.. click:: cookie_composer.cli:cli
:prog: cookie_composer
:prog: cookie-composer
:nested: full
23 changes: 23 additions & 0 deletions docsrc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">'
'<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 '
"7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-"
".23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 "
"1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 "
"0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 "
"2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 "
"1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 "
'2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path>'
"</svg>",
"class": "",
},
],
}