Skip to content

Commit

Permalink
Merge pull request #96 from csdms/mcflugen/replace-cookiecutter
Browse files Browse the repository at this point in the history
Replace cookiecutter
  • Loading branch information
mcflugen authored Apr 5, 2024
2 parents 791e0b2 + cfad50e commit 8b84c83
Show file tree
Hide file tree
Showing 80 changed files with 925 additions and 1,185 deletions.
106 changes: 5 additions & 101 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,104 +1,8 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
*.py[cod]
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# dotenv
.env

# virtualenv
.venv
venv/
ENV/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/

# nox virtual envs
.nox/
__pycache__/
build/
dist/
docs/source/api/babelizer*rst
15 changes: 12 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 24.2.0
rev: 24.3.0
hooks:
- id: black
name: black
Expand Down Expand Up @@ -35,7 +35,7 @@ repos:
exclude: ^babelizer/data

- repo: https://github.com/asottile/pyupgrade
rev: v3.15.1
rev: v3.15.2
hooks:
- id: pyupgrade
args: [--py310-plus]
Expand Down Expand Up @@ -65,9 +65,18 @@ repos:
- id: end-of-file-fixer
- id: forbid-new-submodules
- id: trailing-whitespace
- id: name-tests-test
exclude: ^external
- id: file-contents-sorter
files: |
(?x)^(
requirements(-\w+)?.(in|txt)|
external/requirements(-\w+)?.(in|txt)|
.gitignore
)
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.9.0
hooks:
- id: mypy
additional_dependencies: [types-all]
Expand Down
19 changes: 0 additions & 19 deletions MANIFEST.in

This file was deleted.

92 changes: 92 additions & 0 deletions babelizer/_cookiecutter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import annotations

import os
from collections.abc import Iterable
from datetime import datetime
from typing import Any

from jinja2 import Environment
from jinja2 import FileSystemLoader
from jinja2 import StrictUndefined
from jinja2 import Template

from babelizer._datadir import get_template_dir
from babelizer._post_hook import run
from babelizer._utils import as_cwd


def cookiecutter(
template: str,
context: dict[str, Any] | None = None,
output_dir: str = ".",
) -> None:
if context is None:
context = {}
env = babelizer_environment(template)

def datetime_format(value: datetime, format_: str = "%Y-%M-%D") -> str:
return value.strftime(format_)

env.filters["datetimeformat"] = datetime_format

for dirpath, _dirnames, filenames in os.walk(template):
rel_path = os.path.relpath(dirpath, template)
target_dir = os.path.join(output_dir, render_path(rel_path, context))

if not os.path.exists(target_dir):
os.makedirs(target_dir)

for filename in filenames:
target_path = os.path.join(target_dir, render_path(filename, context))

with open(target_path, "w") as fp:
fp.write(
env.get_template(os.path.join(rel_path, filename)).render(**context)
)

with as_cwd(output_dir):
run(context)


def babelizer_environment(template: str | None = None) -> Environment:
if template is None:
template = get_template_dir()

return Environment(loader=FileSystemLoader(template), undefined=StrictUndefined)


def render_path(
path: str,
context: dict[str, Any],
remove_extension: Iterable[str] = (".jinja", ".jinja2", ".j2"),
) -> str:
"""Render a path as though it were a jinja template.
Parameters
----------
path : str
A path.
context : dict
Context to use for substitution.
remove_extension : iterable of str, optional
If the provided path ends with one of these exensions,
the extension will be removed from the rendered path.
Examples
--------
>>> from babelizer._cookiecutter import render_path
>>> render_path("{{foo}}.py", {"foo": "bar"})
'bar.py'
>>> render_path("{{foo}}.py.jinja", {"foo": "bar"})
'bar.py'
>>> render_path("bar.py.j2", {"foo": "bar"})
'bar.py'
>>> render_path("{{bar}}.py.jinja", {"foo": "bar"})
Traceback (most recent call last):
...
jinja2.exceptions.UndefinedError: 'bar' is undefined
"""
rendered_path = Template(path, undefined=StrictUndefined).render(**context)

root, ext = os.path.splitext(rendered_path)
return rendered_path if ext not in remove_extension else root
4 changes: 4 additions & 0 deletions babelizer/_datadir.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@

def get_datadir() -> str:
return str(importlib_resources.files("babelizer") / "data")


def get_template_dir() -> str:
return str(importlib_resources.files("babelizer") / "data" / "templates")
28 changes: 14 additions & 14 deletions babelizer/_files/bmi_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,30 @@
from typing import Any


def render(plugin_metadata: Mapping[str, Any]) -> str:
def render(context: Mapping[str, Any]) -> str:
"""Render _bmi.py."""
languages = {library["language"] for library in plugin_metadata["library"].values()}
languages = {library["language"] for library in context["library"].values()}
assert len(languages) == 1
language = languages.pop()

if language == "python":
return _render_bmi_py(plugin_metadata)
return _render_bmi_py(context)
else:
return _render_bmi_c(plugin_metadata)
return _render_bmi_c(context)


def _render_bmi_c(plugin_metadata: Mapping[str, Any]) -> str:
def _render_bmi_c(context: Mapping[str, Any]) -> str:
"""Render _bmi.py for a non-python library."""
languages = [library["language"] for library in plugin_metadata["library"].values()]
languages = [library["language"] for library in context["library"].values()]
language = languages[0]
assert language in ("c", "c++", "fortran")

imports = [
f"from {plugin_metadata['package']['name']}.lib import {cls}"
for cls in plugin_metadata["library"]
f"from {context['package']['name']}.lib import {cls}"
for cls in context["library"]
]

names = [f" {cls!r},".replace("'", '"') for cls in plugin_metadata["library"]]
names = [f" {cls!r},".replace("'", '"') for cls in context["library"]]

return f"""\
{os.linesep.join(sorted(imports))}
Expand All @@ -39,9 +39,9 @@ def _render_bmi_c(plugin_metadata: Mapping[str, Any]) -> str:
"""


def _render_bmi_py(plugin_metadata: Mapping[str, Any]) -> str:
def _render_bmi_py(context: Mapping[str, Any]) -> str:
"""Render _bmi.py for a python library."""
languages = [library["language"] for library in plugin_metadata["library"].values()]
languages = [library["language"] for library in context["library"].values()]
language = languages[0]
assert language == "python"

Expand All @@ -56,7 +56,7 @@ def _render_bmi_py(plugin_metadata: Mapping[str, Any]) -> str:

imports = [
f"from {component['library']} import {component['entry_point']} as {cls}"
for cls, component in plugin_metadata["library"].items()
for cls, component in context["library"].items()
]

rename = [
Expand All @@ -66,10 +66,10 @@ def _render_bmi_py(plugin_metadata: Mapping[str, Any]) -> str:
""".replace(
"'", '"'
)
for cls in plugin_metadata["library"]
for cls in context["library"]
]

names = [f" {cls!r},".replace("'", '"') for cls in plugin_metadata["library"]]
names = [f" {cls!r},".replace("'", '"') for cls in context["library"]]

return f"""\
{header}
Expand Down
8 changes: 4 additions & 4 deletions babelizer/_files/gitignore.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
from typing import Any


def render(plugin_metadata: Mapping[str, Any]) -> str:
def render(context: Mapping[str, Any]) -> str:
"""Render a .gitignore file."""
package_name = plugin_metadata["package"]["name"]
package_name = context["package"]["name"]

languages = {library["language"] for library in plugin_metadata["library"].values()}
languages = {library["language"] for library in context["library"].values()}
ignore = {
"*.egg-info/",
"*.py[cod]",
Expand All @@ -22,7 +22,7 @@ def render(plugin_metadata: Mapping[str, Any]) -> str:

if "python" not in languages:
ignore |= {"*.o", "*.so"} | {
f"{package_name}/lib/{cls.lower()}.c" for cls in plugin_metadata["library"]
f"{package_name}/lib/{cls.lower()}.c" for cls in context["library"]
}

if "fortran" in languages:
Expand Down
10 changes: 4 additions & 6 deletions babelizer/_files/init_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
from typing import Any


def render(plugin_metadata: Mapping[str, Any]) -> str:
def render(context: Mapping[str, Any]) -> str:
"""Render __init__.py."""
package_name = plugin_metadata["package"]["name"]
package_name = context["package"]["name"]

imports = [f"from {package_name}._version import __version__"]
imports += [
f"from {package_name}._bmi import {cls}" for cls in plugin_metadata["library"]
]
imports += [f"from {package_name}._bmi import {cls}" for cls in context["library"]]

names = [f" {cls!r},".replace("'", '"') for cls in plugin_metadata["library"]]
names = [f" {cls!r},".replace("'", '"') for cls in context["library"]]

return f"""\
{os.linesep.join(sorted(imports))}
Expand Down
Loading

0 comments on commit 8b84c83

Please sign in to comment.