cookiecutter
¶
Keywords: Cookiecutter, Template, Project, Git Repo.
Summary¶
cookiecutter 是一个开源 Python 项目. 可以从项目模板中生成项目. 这个项目模板本质上是一堆文件和文件夹. 特殊的是这些文件的名字以及内容都是用 jinja2 模版语言写成的. 这就使得你只要填写一些参数, 就可以生成一整个目录.
Templaterize¶
既然我们可以从模板生成项目, 那我们能不能从项目生成模板呢? 答案是可行的. 我们就自己实现了一个工具.
# -*- coding: utf-8 -*-
"""
This script can convert a concrete project into a cookiecutter template project.
"""
import typing as T
from pathlib_mate import Path
from rich import print as rprint
dir_here = Path.dir_here(__file__)
dir_tmp = dir_here / "tmp"
def mirror_dir(
dir_before: Path,
dir_src: Path,
dir_dst: Path,
mapper: dict,
debug: bool = False,
) -> Path:
dir_after = dir_dst.joinpath(dir_src.basename, dir_before.relative_to(dir_src))
abspath = dir_after.abspath
for k, v in mapper.items():
abspath = abspath.replace(k, f"{{{{ cookiecutter.{v} }}}}")
dir_new = Path(abspath)
if debug:
rprint(f"{str(dir_before.relative_to(dir_src.parent))!r} ->")
rprint(f" {str(dir_new.relative_to(dir_dst))!r}")
dir_new.mkdir_if_not_exists()
return dir_new
def mirror_file(
path_before: Path,
dir_src: Path,
dir_dst: Path,
mapper: dict,
debug: bool = False,
) -> Path:
path_after = dir_dst.joinpath(dir_src.basename, path_before.relative_to(dir_src))
abspath = path_after.abspath
for k, v in mapper.items():
abspath = abspath.replace(k, f"{{{{ cookiecutter.{v} }}}}")
path_new = Path(abspath)
if debug:
rprint(f"{str(path_before.relative_to(dir_src.parent))!r} ->")
rprint(f" {str(path_new.relative_to(dir_dst))!r}")
content = path_before.read_bytes()
try:
text = content.decode("utf-8", errors="strict")
text = text.replace("{{", "{% raw %}{{{% endraw %}").replace("}}", "{% raw %}}}{% endraw %}")
for k, v in mapper.items():
text = text.replace(k, f"{{{{ cookiecutter.{v} }}}}")
path_new.write_text(text)
except UnicodeDecodeError:
path_before.copyto(path_new)
return path_new
def templaterize(
dir_src: Path,
dir_dst: Path,
mapper: dict,
ignore_dirs: T.List[str],
ignore_files: T.List[str],
debug: bool = False,
_is_root: bool = True,
):
"""
Example::
- dir_src: ``/tmp/my_project/README.md``
- dir_dst: ``/GitHub/``
- mapper: ``{"my_project": "project_name"}``
Then it creates ``/GitHub/{{ cookiecutter.project_name }}/README.md``.
"""
if _is_root:
dir_dst.remove_if_exists()
dir_dst.mkdir_if_not_exists()
mirror_dir(dir_src, dir_src, dir_dst, mapper, debug)
for p in dir_src.iterdir():
if p.is_dir():
if p.basename not in ignore_dirs:
dir_new = mirror_dir(p, dir_src, dir_dst, mapper, debug)
templaterize(
dir_src=p,
dir_dst=dir_new.parent,
mapper=mapper,
ignore_dirs=ignore_dirs,
ignore_files=ignore_files,
debug=debug,
_is_root=False,
)
elif p.is_file():
if p.basename not in ignore_files:
mirror_file(p, dir_src, dir_dst, mapper, debug)
else:
pass
if __name__ == "__main__":
templaterize(
dir_src=Path("/Users/sanhehu/Documents/CodeCommit/aws_python-project"),
dir_dst=dir_tmp,
mapper={
"aws_python": "package_name",
"Sanhe Hu": "author_name",
"husanhe@gmail.com": "author_email",
},
ignore_dirs=[
".idea",
".git",
".venv",
".pytest_cache",
"build",
"dist",
"htmlcov",
],
ignore_files=[
".coverage",
".current-env-name.json",
".poetry-lock-hash.json",
"requirements-main.txt",
"requirements-dev.txt",
"requirements-test.txt",
"requirements-doc.txt",
],
debug=True,
)