diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..855eaf1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + - package-ecosystem: "pip" + directory: "{{cookiecutter.project_slug}}/requirements" + schedule: + interval: "weekly" diff --git a/README.md b/README.md index 710e48d..26d5d7b 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,34 @@ -# Cookiecutter for Python Click CLI :cookie: +# Important note +A newer version of this Cookiecutter (using the amazing [Typer](https://typer.tiangolo.com/)!) is currently work in progress and available [here](https://github.com/ltpitt/python-cookiecutter-typer-cli). + +# A cookiecutter to bake tasty Python Click CLI tools :cookie: + +![Come to the dark side... We have cookies!](https://github.com/ltpitt/cookiecutter-python-cli/blob/master/darth_vader_cookies.jpg?raw=true) ## Comes with: + - [x] Containerization - [x] Pre-commit hooks - - [x] mypy + - [x] black + - [x] click - [x] flake8 - [x] mkdocs-material + - [x] mypy - [x] packaging - - [x] Containerization + ## Usage Install cookiecutter. ```bash -$ pip install --user cookiecutter +pip install --user cookiecutter ``` Generate your project template using cookiecutter. ```bash -$ cookiecutter gh:yankeexe/cookiecutter-python-cli +cookiecutter gh:ltpitt/cookiecutter-python-cli ``` ## Project Setup @@ -30,35 +38,47 @@ $ cookiecutter gh:yankeexe/cookiecutter-python-cli 2. Create a virtual environment. ```bash -$ make venv +python -m venv venv +``` + +3. Linux / Mac - Activate it. + +```bash +source venv/Scripts/activate +``` + +3. Windows - Activate it. + +```bash +source venv/Scripts/Activate.ps1 ``` -3. Activate it. +4. Linux / Mac - Install development dependencies with editable mode to test the CLI. ```bash -$ source venv/bin/activate +make install ``` -4. Install development dependencies with editable mode to test the CLI. +4. Windows - Install development dependencies with editable mode to test the CLI. ```bash -$ make install +pip install -e . -r requirements/dev.txt ``` ## Take your CLI for a spin -This Cookiecutter comes with two generic CLI commands, namely, `init` and `show`. +This Cookiecutter comes with two generic CLI commands, namely, `init` and `hello`. > **NOTE** > > `<>` is the executable command you choose for your CLI during project setup. ```bash -$ <> init +<> init ``` ```bash -$ <> show +<> hello ``` ### Test with Docker @@ -67,33 +87,45 @@ CLI commands can be tested with Docker. 1. Build an image for the CLI. - Image is tagged with the same name as the `cli_command`. + Image is tagged <> name. ```bash -$ make docker-image +make docker-image ``` 2. Run the command inside the container. ```bash -$ docker-run --rm <> init +docker-run --rm <> init ``` ## Documentation -1. Install documentation-related dependencies. +1. Linux / Mac - Install documentation-related dependencies. + +```bash +make docs +``` + +1. Windows - Install development dependencies with editable mode to test the CLI. + +```bash +pip install -r requirements/docs.txt +``` + +2. Linux / Mac - Serve the docs locally. ```bash -$ make docs +make serve-docs ``` -2. Serve the docs locally. +2. Windows - Serve the docs locally. ```bash -$ make serve-docs +mkdocs serve -f docs/mkdocs.yml ``` -## Distribution +## Linux / Mac - Distribution > **NOTE** > @@ -102,19 +134,19 @@ $ make serve-docs To publish you CLI to PyPI, run: ```bash -$ make distributions +make distributions ``` `dist` directory will be created inside your project directory. Upload it to PyPI using: ```bash -$ twine dist/* +twine dist/* ``` -## Help +## Linux / Mac - Help For help related to make commands. ```bash -$ make help +make help ``` diff --git a/cookiecutter.json b/cookiecutter.json index 0436eb2..369ca48 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,7 +1,7 @@ { "project_name": "Awesome CLI", "project_slug": "{{cookiecutter.project_name.strip().lower().replace(' ', '_').replace('-', '_')}}", - "cli_command": "yocli", + "cli_command": "acli", "author": "Yankee Maharjan", - "description": "This CLI tool does awesome things." + "description": "This CLI tool does one thing and does it awesomely." } diff --git a/darth_vader_cookies.jpg b/darth_vader_cookies.jpg new file mode 100644 index 0000000..f19e6ab Binary files /dev/null and b/darth_vader_cookies.jpg differ diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index 70acbdf..08abe77 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -18,7 +18,7 @@ test: # run pytest in verbose mode @pytest -vvv venv: # create a virtual env - @python3 -m venv venv + @python -m venv venv format: # format all files using black @black . @@ -41,7 +41,7 @@ serve-docs: # serve docs on localhost distributions: # create distribution wheel and zip for PyPI @pip install -r requirements/publish.txt - @python3 setup.py sdist bdist_wheel + @python setup.py sdist bdist_wheel @echo "Use `twine upload dist/*` to upload to PyPI" docker-image: diff --git a/{{cookiecutter.project_slug}}/README.md b/{{cookiecutter.project_slug}}/README.md index 99e6187..22255b1 100644 --- a/{{cookiecutter.project_slug}}/README.md +++ b/{{cookiecutter.project_slug}}/README.md @@ -24,7 +24,7 @@ $ make install ## Take your CLI for a spin -This Cookiecutter comes with two generic CLI commands, namely, `init` and `show`. +This Cookiecutter comes with two generic CLI commands, namely, `init` and `hello`. ```bash $ {{cookiecutter.cli_command.strip().lower().replace(' ', '_').replace('-', '_')}} init diff --git a/{{cookiecutter.project_slug}}/docs/docs/commands/index.md b/{{cookiecutter.project_slug}}/docs/docs/commands/index.md deleted file mode 100644 index e0a11a1..0000000 --- a/{{cookiecutter.project_slug}}/docs/docs/commands/index.md +++ /dev/null @@ -1,20 +0,0 @@ -## Commands for the CLI. - -!!! note - `<>` is the one you chose during the project creation. - -### init - -Shows a welcome message as a CLI initialization. - -```bash -$ <> init -``` - -### show - -Shows a generic message. - -```bash -$ <> show -``` diff --git a/{{cookiecutter.project_slug}}/docs/docs/index.md b/{{cookiecutter.project_slug}}/docs/docs/index.md index 9dbc05b..b61e00a 100644 --- a/{{cookiecutter.project_slug}}/docs/docs/index.md +++ b/{{cookiecutter.project_slug}}/docs/docs/index.md @@ -1 +1,7 @@ # Welcome to {{cookiecutter.project_name}} + +::: mkdocs-click + :module: {{cookiecutter.project_slug}}.app + :command: {{cookiecutter.cli_command}} + :depth: 1 + diff --git a/{{cookiecutter.project_slug}}/docs/mkdocs.yml b/{{cookiecutter.project_slug}}/docs/mkdocs.yml index 17b9696..073f8c8 100644 --- a/{{cookiecutter.project_slug}}/docs/mkdocs.yml +++ b/{{cookiecutter.project_slug}}/docs/mkdocs.yml @@ -5,13 +5,6 @@ theme: features: - navigation.instant -nav: - - Overview: - - Introduction: index.md - - Commands: - - Overview: ./commands/index.md - -# Extensions markdown_extensions: - markdown.extensions.admonition - markdown.extensions.codehilite: @@ -37,3 +30,12 @@ markdown_extensions: - pymdownx.tasklist: custom_checkbox: true - pymdownx.tilde + - mkdocs-click + +plugins: + - search + - markdownextradata: {} + + +extra: + cli_command: "{{cookiecutter.cli_command}}" diff --git a/{{cookiecutter.project_slug}}/requirements/dev.in b/{{cookiecutter.project_slug}}/requirements/dev.in index cc7ed97..e6d601a 100644 --- a/{{cookiecutter.project_slug}}/requirements/dev.in +++ b/{{cookiecutter.project_slug}}/requirements/dev.in @@ -3,8 +3,11 @@ pip-tools pre-commit tox -mypy flake8 -pytest black -pre-commit +click +typing-extensions +rich +pyyaml>=6.0 +py>=1.11.0 + diff --git a/{{cookiecutter.project_slug}}/requirements/dev.txt b/{{cookiecutter.project_slug}}/requirements/dev.txt index 2bf70c4..97a617a 100644 --- a/{{cookiecutter.project_slug}}/requirements/dev.txt +++ b/{{cookiecutter.project_slug}}/requirements/dev.txt @@ -1,26 +1,44 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile requirements/dev.in # appdirs==1.4.4 # via virtualenv +atomicwrites==1.4.1 + # via pytest attrs==20.3.0 # via pytest +black==22.8.0 + # via -r requirements/dev.in +build==0.8.0 + # via pip-tools cfgv==3.2.0 # via pre-commit -click==7.1.2 +click==8.1.3 # via + # -r requirements/dev.in + # black # mkdocs # nltk # pip-tools +colorama==0.4.5 + # via + # build + # click + # pytest + # tox +commonmark==0.9.1 + # via rich distlib==0.3.1 # via virtualenv filelock==3.0.12 # via # tox # virtualenv +flake8==5.0.4 + # via -r requirements/dev.in future==0.18.2 # via lunr identify==1.5.13 @@ -42,28 +60,39 @@ markdown==3.3.3 # pymdown-extensions markupsafe==1.1.1 # via jinja2 -mkdocs-material-extensions==1.0.1 +mccabe==0.7.0 + # via flake8 +mkdocs==1.1.2 # via mkdocs-material mkdocs-material==6.2.8 # via - # -r requirements/docs.in + # -r requirements\docs.in # mkdocs-material-extensions -mkdocs==1.1.2 +mkdocs-material-extensions==1.0.1 # via mkdocs-material -mypy-extensions==0.4.3 - # via mypy mypy==0.800 # via -r requirements/dev.in +mypy-extensions==0.4.3 + # via + # black + # mypy nltk==3.5 # via lunr nodeenv==1.5.0 # via pre-commit packaging==20.9 # via + # build # pytest # tox -pip-tools==5.5.0 +pathspec==0.10.1 + # via black +pep517==0.13.0 + # via build +pip-tools==6.8.0 # via -r requirements/dev.in +platformdirs==2.5.2 + # via black pluggy==0.13.1 # via # pytest @@ -74,20 +103,28 @@ py==1.10.0 # via # pytest # tox +pycodestyle==2.9.1 + # via flake8 +pyflakes==2.5.0 + # via flake8 pygments==2.7.4 - # via mkdocs-material + # via + # mkdocs-material + # rich pymdown-extensions==8.1.1 # via mkdocs-material pyparsing==2.4.7 # via packaging pytest==6.2.2 - # via -r requirements/tests.in + # via -r requirements\tests.in pyyaml==5.4.1 # via # mkdocs # pre-commit regex==2020.11.13 # via nltk +rich==12.5.1 + # via -r requirements/dev.in six==1.15.0 # via # livereload @@ -99,6 +136,11 @@ toml==0.10.2 # pre-commit # pytest # tox +tomli==2.0.1 + # via + # black + # build + # pep517 tornado==6.1 # via # livereload @@ -109,12 +151,18 @@ tqdm==4.56.0 # via nltk typed-ast==1.4.2 # via mypy -typing-extensions==3.7.4.3 - # via mypy +typing-extensions==4.3.0 + # via + # -r requirements/dev.in + # black + # mypy virtualenv==20.4.2 # via # pre-commit # tox +wheel==0.37.1 + # via pip-tools # The following packages are considered to be unsafe in a requirements file: # pip +# setuptools diff --git a/{{cookiecutter.project_slug}}/requirements/docs.in b/{{cookiecutter.project_slug}}/requirements/docs.in index 4c8f017..0f170bd 100644 --- a/{{cookiecutter.project_slug}}/requirements/docs.in +++ b/{{cookiecutter.project_slug}}/requirements/docs.in @@ -1 +1,3 @@ mkdocs-material +mkdocs-markdownextradata-plugin +mkdocs-click diff --git a/{{cookiecutter.project_slug}}/requirements/docs.txt b/{{cookiecutter.project_slug}}/requirements/docs.txt index 4713a98..6e871c0 100644 --- a/{{cookiecutter.project_slug}}/requirements/docs.txt +++ b/{{cookiecutter.project_slug}}/requirements/docs.txt @@ -1,13 +1,16 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile requirements/docs.in # -click==7.1.2 +click==8.1.3 # via # mkdocs + # mkdocs-click # nltk +colorama==0.4.5 + # via click future==0.18.2 # via lunr jinja2==2.11.3 @@ -21,17 +24,24 @@ lunr[languages]==0.5.8 markdown==3.3.3 # via # mkdocs + # mkdocs-click # mkdocs-material # pymdown-extensions markupsafe==1.1.1 # via jinja2 -mkdocs-material-extensions==1.0.1 - # via mkdocs-material +mkdocs==1.1.2 + # via + # mkdocs-markdownextradata-plugin + # mkdocs-material +mkdocs-click==0.8.0 + # via -r requirements/docs.in +mkdocs-markdownextradata-plugin==0.2.5 + # via -r requirements/docs.in mkdocs-material==6.2.8 # via # -r requirements/docs.in # mkdocs-material-extensions -mkdocs==1.1.2 +mkdocs-material-extensions==1.0.1 # via mkdocs-material nltk==3.5 # via lunr @@ -40,7 +50,9 @@ pygments==2.7.4 pymdown-extensions==8.1.1 # via mkdocs-material pyyaml==5.4.1 - # via mkdocs + # via + # mkdocs + # mkdocs-markdownextradata-plugin regex==2020.11.13 # via nltk six==1.15.0 diff --git a/{{cookiecutter.project_slug}}/requirements/publish.txt b/{{cookiecutter.project_slug}}/requirements/publish.txt index 106e4b9..ff4b3bf 100644 --- a/{{cookiecutter.project_slug}}/requirements/publish.txt +++ b/{{cookiecutter.project_slug}}/requirements/publish.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile requirements/publish.in @@ -8,46 +8,36 @@ bleach==3.3.0 # via readme-renderer certifi==2020.12.5 # via requests -cffi==1.14.5 - # via cryptography chardet==4.0.0 # via requests colorama==0.4.4 # via twine -cryptography==3.4.4 - # via secretstorage docutils==0.16 # via readme-renderer idna==2.10 # via requests -jeepney==0.6.0 - # via - # keyring - # secretstorage keyring==22.0.1 # via twine packaging==20.9 # via bleach pkginfo==1.7.0 # via twine -pycparser==2.20 - # via cffi pygments==2.7.4 # via readme-renderer pyparsing==2.4.7 # via packaging +pywin32-ctypes==0.2.0 + # via keyring readme-renderer==28.0 # via twine -requests-toolbelt==0.9.1 - # via twine requests==2.25.1 # via # requests-toolbelt # twine +requests-toolbelt==0.9.1 + # via twine rfc3986==1.4.0 # via twine -secretstorage==3.3.1 - # via keyring six==1.15.0 # via # bleach diff --git a/{{cookiecutter.project_slug}}/requirements/tests.txt b/{{cookiecutter.project_slug}}/requirements/tests.txt index 43d6aba..c569a49 100644 --- a/{{cookiecutter.project_slug}}/requirements/tests.txt +++ b/{{cookiecutter.project_slug}}/requirements/tests.txt @@ -1,11 +1,15 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile requirements/tests.in # +atomicwrites==1.4.1 + # via pytest attrs==20.3.0 # via pytest +colorama==0.4.5 + # via pytest iniconfig==1.1.1 # via pytest packaging==20.9 diff --git a/{{cookiecutter.project_slug}}/setup.py b/{{cookiecutter.project_slug}}/setup.py index 0cdb9dc..76c0b3f 100644 --- a/{{cookiecutter.project_slug}}/setup.py +++ b/{{cookiecutter.project_slug}}/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r") as f: long_description = f.read() -requirements = ["click", "rich<=7.1.0", "simple-term-menu", "requests"] +requirements = ["click", "rich", "simple-term-menu", "requests"] setuptools.setup( @@ -21,7 +21,7 @@ install_requires=requirements, entry_points={ "console_scripts": [ - "{{cookiecutter.cli_command.strip().lower().replace(' ', '_').replace('-', '_')}} = {{cookiecutter.project_slug}}.app:cli" + "{{cookiecutter.cli_command.strip().lower().replace(' ', '_').replace('-', '_')}} = {{cookiecutter.project_slug}}.app:{{cookiecutter.cli_command}}" ] }, classifiers=[ diff --git a/{{cookiecutter.project_slug}}/tests/test_help.py b/{{cookiecutter.project_slug}}/tests/test_help.py new file mode 100644 index 0000000..027ce19 --- /dev/null +++ b/{{cookiecutter.project_slug}}/tests/test_help.py @@ -0,0 +1,9 @@ +from click.testing import CliRunner +from {{cookiecutter.project_slug}} import app + + +def test_help(): + runner = CliRunner() + result = runner.invoke(app.{{cookiecutter.cli_command}}) + assert result.exit_code == 0 + assert "Usage: " in result.output diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/app.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/app.py index 3a024df..3fb0114 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/app.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/app.py @@ -3,13 +3,13 @@ """ import click -from {{cookiecutter.project_slug}}.commands import init, show +from {{cookiecutter.project_slug}}.commands import init, hello @click.group() -def cli(): +def {{cookiecutter.cli_command}}(): pass -cli.add_command(init) -cli.add_command(show) +{{cookiecutter.cli_command}}.add_command(init) +{{cookiecutter.cli_command}}.add_command(hello) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/__init__.py index 6ea6bb2..02a3281 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/__init__.py @@ -2,4 +2,4 @@ Exports for CLI commands. """ from {{cookiecutter.project_slug}}.commands.init import init -from {{cookiecutter.project_slug}}.commands.show import show +from {{cookiecutter.project_slug}}.commands.hello import hello diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/hello.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/hello.py new file mode 100644 index 0000000..5fcafad --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/hello.py @@ -0,0 +1,16 @@ +""" +Show command for the CLI. +""" +import click + +from {{cookiecutter.project_slug}} import console + + +@click.command() +@click.option('--count', default=1, help='Number of greetings.') +@click.option('--name', prompt='Your name', + help='The person to greet.') +def hello(count, name): + """Simple program that greets NAME for a total of COUNT times.""" + for x in range(count): + console.print(f"Hello, [italic red]{name}[/italic red] :)") diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/init.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/init.py index e910247..49f4adc 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/init.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/init.py @@ -2,7 +2,6 @@ CLI initialization command. """ import click -from rich.prompt import Prompt from {{cookiecutter.project_slug}} import console from {{cookiecutter.project_slug}}.constants import WELCOME_MESSAGE diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/show.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/show.py deleted file mode 100644 index d587fbf..0000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/commands/show.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Show command for the CLI. -""" -import click - -from {{cookiecutter.project_slug}} import console - - -@click.command() -def show(): - """ - Generic sub-command to show a message. - """ - console.print("Get started with your CLI in no time!")