|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import os |
| 4 | +from collections.abc import Iterable |
| 5 | +from datetime import datetime |
| 6 | +from typing import Any |
| 7 | + |
| 8 | +from jinja2 import Environment |
| 9 | +from jinja2 import FileSystemLoader |
| 10 | +from jinja2 import StrictUndefined |
| 11 | +from jinja2 import Template |
| 12 | + |
| 13 | +from babelizer._datadir import get_template_dir |
| 14 | +from babelizer._post_hook import run |
| 15 | +from babelizer._utils import as_cwd |
| 16 | + |
| 17 | + |
| 18 | +def cookiecutter( |
| 19 | + template: str, |
| 20 | + context: dict[str, Any] | None = None, |
| 21 | + output_dir: str = ".", |
| 22 | +) -> None: |
| 23 | + if context is None: |
| 24 | + context = {} |
| 25 | + env = babelizer_environment(template) |
| 26 | + |
| 27 | + def datetime_format(value: datetime, format_: str = "%Y-%M-%D") -> str: |
| 28 | + return value.strftime(format_) |
| 29 | + |
| 30 | + env.filters["datetimeformat"] = datetime_format |
| 31 | + |
| 32 | + for dirpath, _dirnames, filenames in os.walk(template): |
| 33 | + rel_path = os.path.relpath(dirpath, template) |
| 34 | + target_dir = os.path.join(output_dir, render_path(rel_path, context)) |
| 35 | + |
| 36 | + if not os.path.exists(target_dir): |
| 37 | + os.makedirs(target_dir) |
| 38 | + |
| 39 | + for filename in filenames: |
| 40 | + target_path = os.path.join(target_dir, render_path(filename, context)) |
| 41 | + |
| 42 | + with open(target_path, "w") as fp: |
| 43 | + fp.write( |
| 44 | + env.get_template(os.path.join(rel_path, filename)).render(**context) |
| 45 | + ) |
| 46 | + |
| 47 | + with as_cwd(output_dir): |
| 48 | + run(context) |
| 49 | + |
| 50 | + |
| 51 | +def babelizer_environment(template: str | None = None) -> Environment: |
| 52 | + if template is None: |
| 53 | + template = get_template_dir() |
| 54 | + |
| 55 | + return Environment(loader=FileSystemLoader(template), undefined=StrictUndefined) |
| 56 | + |
| 57 | + |
| 58 | +def render_path( |
| 59 | + path: str, |
| 60 | + context: dict[str, Any], |
| 61 | + remove_extension: Iterable[str] = (".jinja", ".jinja2", ".j2"), |
| 62 | +) -> str: |
| 63 | + """Render a path as though it were a jinja template. |
| 64 | +
|
| 65 | + Parameters |
| 66 | + ---------- |
| 67 | + path : str |
| 68 | + A path. |
| 69 | + context : dict |
| 70 | + Context to use for substitution. |
| 71 | + remove_extension : iterable of str, optional |
| 72 | + If the provided path ends with one of these exensions, |
| 73 | + the extension will be removed from the rendered path. |
| 74 | +
|
| 75 | + Examples |
| 76 | + -------- |
| 77 | + >>> from babelizer._cookiecutter import render_path |
| 78 | + >>> render_path("{{foo}}.py", {"foo": "bar"}) |
| 79 | + 'bar.py' |
| 80 | + >>> render_path("{{foo}}.py.jinja", {"foo": "bar"}) |
| 81 | + 'bar.py' |
| 82 | + >>> render_path("bar.py.j2", {"foo": "bar"}) |
| 83 | + 'bar.py' |
| 84 | + >>> render_path("{{bar}}.py.jinja", {"foo": "bar"}) |
| 85 | + Traceback (most recent call last): |
| 86 | + ... |
| 87 | + jinja2.exceptions.UndefinedError: 'bar' is undefined |
| 88 | + """ |
| 89 | + rendered_path = Template(path, undefined=StrictUndefined).render(**context) |
| 90 | + |
| 91 | + root, ext = os.path.splitext(rendered_path) |
| 92 | + return rendered_path if ext not in remove_extension else root |
0 commit comments