"""Extension that integrates cookiecutter templates into PyScaffold.
When used via PyScaffold's Python API (instead of CLI), a ``cookiecutter_params``
keyword argument can be optionally added to :obj:`pyscaffold.api.create_project`,
this argument should be a :obj:`dict` (or a similar object that can be converted to dict
via the :obj:`dict` constructor), and is equivalent to ``extra_context`` in
:obj:`cookicookiecutter.main.cookiecutter` (PyScaffold will always add some default
values, even when no ``cookiecutter_params`` are given).
"""
# This file was transfered from the main PyScaffold repository using
# ``git filter-branch``, and therefore might have lost parts of its
# commit history.
# Please refer to ``pyscaffold`` if that is needed.
from typing import Any, Dict, List
from pyscaffold import file_system as fs
from pyscaffold.actions import Action, ActionParams, ScaffoldOpts, Structure
from pyscaffold.extensions import Extension, store_with
from pyscaffold.log import logger
UPDATE_WARNING = (
"Updating code generated using external tools is not "
"supported. The extension `cookiecutter` will be ignored, only "
"changes in PyScaffold core features will take place."
)
[docs]class Cookiecutter(Extension):
"""Additionally apply a Cookiecutter template.
Note that not all templates are suitable for PyScaffold.
Please refer to the docs for more information.
"""
persist = False
[docs] def augment_cli(self, parser):
"""Add an option to parser that enables the Cookiecutter extension
See :obj:`pyscaffold.extension.Extension.augment_cli`.
"""
parser.add_argument(
self.flag,
dest=self.name,
action=store_with(self),
metavar="TEMPLATE",
help=self.help_text,
)
parser.add_argument(
"--cookiecutter-params",
nargs="+",
required=False,
type=lambda kv: kv.split("=", 1),
help="extra parameters to be passed to cookiecutter in the form of "
"a space separated list of 'NAME=VALUE' (check the `cookiecutter.json` "
"file of the template you are using to see the available parameters). "
"Please notice PyScaffold already add some default parameters, check the "
"docs for more information.",
)
[docs] def activate(self, actions: List[Action]) -> List[Action]:
"""Register before_create hooks to generate project using Cookiecutter
Activate extension. See :obj:`pyscaffold.extension.Extension.activate`."""
actions = self.register(actions, enforce_options, before="get_default_options")
return self.register(actions, create_cookiecutter)
[docs]def enforce_options(struct: Structure, opts: ScaffoldOpts) -> ActionParams:
"""Make sure options reflect the cookiecutter usage.
See :obj:`pyscaffold.actions.Action`.
"""
opts["force"] = True
return struct, opts
[docs]def parameters(opts: ScaffoldOpts) -> Dict[str, Any]:
"""Parameters to be passed to cookiecutter as ``extra_context``"""
project_name = opts["project_path"].resolve().name
return {
"full_name": opts["author"],
"author": opts["author"],
"email": opts["email"],
"installable_name": opts["name"],
"package_name": opts["package"],
"namespace": opts.get("namespace"),
"repo_name": project_name,
"project_name": project_name,
"project_short_description": opts["description"],
"release_date": opts["release_date"],
"version": "unknown", # will be replaced later
"year": opts["year"],
**dict(opts.get("cookiecutter_params") or {}),
}
[docs]def create_cookiecutter(struct: Structure, opts: ScaffoldOpts) -> ActionParams:
"""Create a cookie cutter template
See :obj:`pyscaffold.actions.Action`.
"""
if opts.get("update"):
logger.warning(UPDATE_WARNING)
return struct, opts
try:
from cookiecutter.main import cookiecutter
except Exception as e:
raise NotInstalled from e
template = opts.get("cookiecutter")
if not template:
raise MissingTemplate
logger.report("run", "cookiecutter " + opts["cookiecutter"])
if not opts.get("pretend"):
with fs.chdir(opts["project_path"].resolve().parent):
cookiecutter(template, no_input=True, extra_context=parameters(opts))
return struct, opts
[docs]class NotInstalled(RuntimeError):
"""This extension depends on the ``cookiecutter`` package."""
DEFAULT_MESSAGE = "cookiecutter is not installed, " "run: pip install cookiecutter"
def __init__(self, message=DEFAULT_MESSAGE, *args, **kwargs):
super(NotInstalled, self).__init__(message, *args, **kwargs)
[docs]class MissingTemplate(RuntimeError):
"""A cookiecutter template (git url) is required."""
DEFAULT_MESSAGE = "missing `cookiecutter` option"
def __init__(self, message=DEFAULT_MESSAGE, *args, **kwargs):
super(MissingTemplate, self).__init__(message, *args, **kwargs)