Skip to content

Commit

Permalink
Add skeleton govcookiecutter package
Browse files Browse the repository at this point in the history
Create a Python package with the minimum folders and files required to
create a project from `govcookiecutter` using a CLI.

By executing:

```
python setup.py build_package
```

a universal source and wheel distribution of this skeleton package is
created. `pip` install the package, and then run `govcookiecutter` in
your terminal.

Should fix issue #23.
  • Loading branch information
ESKYoung committed Jul 6, 2021
1 parent 5d559b6 commit bb087ad
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -865,3 +865,6 @@ example/*

# Ignore Sphinx documentation link checking folder
docs/_linkcheck/

# Ignore everything inside the `src/govcookiecutter/govcookiecutter` folder
src/govcookiecutter/govcookiecutter/*
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include README.md LICENSE
graft src/govcookiecutter/
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ pytest-mock
pytest-xdist
Sphinx
toml
wheel
124 changes: 124 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from pathlib import Path
from setuptools import Command, find_packages, setup
from shutil import copy, copytree, rmtree
from src.govcookiecutter import __version__
import subprocess
import sys

# Define a description for this package
PACKAGE_DESCRIPTION = (
"A cookiecutter template for analytical, Python-, or Python and R-based projects "
"within Her Majesty's Government, and wider public sector."
)

# Define the path to the parent directory of this file
dir_parent = Path(__file__).resolve().parent

# Try to parse the `README.md` file to generate the long description of this package
try:
with open(dir_parent.joinpath("README.md"), encoding="utf-8") as f:
long_description = f.read()
except FileNotFoundError:
long_description = PACKAGE_DESCRIPTION

# Define paths to the template folder, and the `src` directories
dir_template = dir_parent.joinpath("{{ cookiecutter.repo_name }}")
dir_govcookiecutter = dir_parent.joinpath("src", "govcookiecutter", "govcookiecutter")
dir_govcookiecutter_template = dir_govcookiecutter.joinpath(
"{{ cookiecutter.repo_name }}"
)

# Define a dictionary of files and folders to copy to a skeleton `govcookiecutter`
# Python package, where the keys are the current locations, and values are the
# locations within the skeleton package
copy_folders_and_files = {
dir_parent.joinpath("hooks"): dir_govcookiecutter.joinpath("hooks"),
dir_template: dir_govcookiecutter_template,
dir_parent.joinpath("cookiecutter.json"): dir_govcookiecutter.joinpath(
"cookiecutter.json"
),
}


class BuildPackage(Command):
"""Build a skeleton ``govcookiecutter`` package."""

description = "Build a skeleton `govcookiecutter` package."
user_options = []

def initialize_options(self) -> None:
pass

def finalize_options(self) -> None:
pass

@staticmethod
def run() -> None:
"""Build a skeleton ``govcookiecutter`` package.
The skeleton package contains only the necessary files and folders to generate
a project template. It also contains a CLI interface, where invoking the
``govcookiecutter`` command in your terminal creates the project.
Returns:
A universal source and wheel distribution of the skeleton `govcookiecutter`
package.
"""

# If they already exist, delete certain folders related to the package build
rmtree(dir_parent.joinpath("build"), ignore_errors=True)
rmtree(dir_parent.joinpath("dist"), ignore_errors=True)
rmtree(dir_govcookiecutter, ignore_errors=True)

# Copy files and folders required by `cookiecutter` to create the project
# templates
for k, v in copy_folders_and_files.items():
if k.is_file():
copy(k, v)
elif k.is_dir():
copytree(k, v)
else:
raise RuntimeError(f"Invalid file/folder to copy: {k}")

# Create the universal source and wheel distributions
subprocess.run(
[sys.executable, "setup.py", "sdist", "bdist_wheel", "--universal"]
)


# Set up the package
setup(
name="govcookiecutter",
version=__version__,
description=PACKAGE_DESCRIPTION,
long_description=long_description,
long_description_content_type="text/markdown",
author="ukgovdatascience",
author_email="gds-data-science@digital.cabinet-office.gov.uk",
python_requires=">=3.6.1",
url="https://github.com/ukgovdatascience/govcookiecutter",
install_requires=["click", "cookiecutter"],
package_dir={"": "src"},
packages=find_packages(where="src"),
include_package_data=True,
license="MIT",
classifiers=[
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
],
entry_points={
"console_scripts": ["govcookiecutter=govcookiecutter.__main__:main"],
},
cmdclass={
"build_package": BuildPackage,
},
)
2 changes: 2 additions & 0 deletions src/govcookiecutter/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Define the version number of the package
__version__ = "1.2.0"
4 changes: 4 additions & 0 deletions src/govcookiecutter/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from govcookiecutter.cli import main

if __name__ == "__main__":
main(prog_name="govcookiecutter")
79 changes: 79 additions & 0 deletions src/govcookiecutter/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from pathlib import Path
from cookiecutter.main import cookiecutter
from govcookiecutter import __version__
import click

# Define the file path to the project template
dir_template = Path(__file__).resolve().parent.joinpath("govcookiecutter")


# Relevant `click` options taken from `cookiecutter` version 1.7.3
@click.command(context_settings=dict(help_option_names=["-h", "--help"]))
@click.version_option(__version__, u"-V", u"--version")
@click.option(
"--no-input",
is_flag=True,
help="Do not prompt for parameters and only use `cookiecutter.json` file content",
)
@click.option(
"--replay",
is_flag=True,
help="Do not prompt for parameters and only use information entered previously",
)
@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(
"-o",
"--output-dir",
default=".",
type=click.Path(),
help="Where to output the generated project dir into",
)
@click.option(
"--config-file", type=click.Path(), default=None, help="User configuration file"
)
@click.option(
"--default-config",
is_flag=True,
help="Do not load a config file. Use the defaults instead",
)
def main(
no_input,
replay,
overwrite_if_exists,
output_dir,
config_file,
default_config,
skip_if_file_exists,
) -> None:
"""Generate a ``govcookiecutter`` template using ``cookiecutter``.
Options are those available from ``cookiecutter`` v1.7.3 that are compatible with
``govcookiecutter``.
"""
cookiecutter(
template=str(dir_template),
no_input=no_input,
replay=replay,
overwrite_if_exists=overwrite_if_exists,
output_dir=output_dir,
config_file=config_file,
default_config=default_config,
skip_if_file_exists=skip_if_file_exists,
)


if __name__ == "__main__":
main()

0 comments on commit bb087ad

Please sign in to comment.