Skip to content

Commit 8ed5f2f

Browse files
committed
Create a cookiecutter template
1 parent aec574a commit 8ed5f2f

File tree

6 files changed

+648
-0
lines changed

6 files changed

+648
-0
lines changed

cookiecutter.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"project_name": "my_cli_tool",
3+
"project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}",
4+
"project_description": "CLI tool for developers",
5+
"project_short_description": "CLI tool for developers",
6+
"package_name": "{{ cookiecutter.project_slug }}",
7+
"author_name": "Your Name",
8+
"author_email": "[email protected]",
9+
"github_username": "username",
10+
"github_repo": "{{ cookiecutter.project_slug }}",
11+
"version": "0.0.1",
12+
"python_version": "3.9",
13+
"license": ["MIT", "BSD-3", "GPL-3.0", "Apache-2.0"],
14+
"include_docs": ["y", "n"],
15+
"include_github_actions": ["y", "n"],
16+
"include_tests": ["y", "n"],
17+
"supported_vcs": ["git", "svn", "hg"],
18+
"create_author_file": ["y", "n"]
19+
}

hooks/post_gen_project.py

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
#!/usr/bin/env python
2+
"""Post-generation script for cookiecutter."""
3+
4+
import os
5+
import datetime
6+
7+
license_type = "{{cookiecutter.license}}"
8+
author = "{{cookiecutter.author_name}}"
9+
year = datetime.datetime.now().year
10+
11+
12+
def generate_mit_license():
13+
"""Generate MIT license file."""
14+
mit_license = f"""MIT License
15+
16+
Copyright (c) {year} {author}
17+
18+
Permission is hereby granted, free of charge, to any person obtaining a copy
19+
of this software and associated documentation files (the "Software"), to deal
20+
in the Software without restriction, including without limitation the rights
21+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22+
copies of the Software, and to permit persons to whom the Software is
23+
furnished to do so, subject to the following conditions:
24+
25+
The above copyright notice and this permission notice shall be included in all
26+
copies or substantial portions of the Software.
27+
28+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
34+
SOFTWARE.
35+
"""
36+
with open("LICENSE", "w") as f:
37+
f.write(mit_license)
38+
39+
40+
def generate_bsd3_license():
41+
"""Generate BSD-3 license file."""
42+
bsd3_license = f"""BSD 3-Clause License
43+
44+
Copyright (c) {year}, {author}
45+
All rights reserved.
46+
47+
Redistribution and use in source and binary forms, with or without
48+
modification, are permitted provided that the following conditions are met:
49+
50+
1. Redistributions of source code must retain the above copyright notice, this
51+
list of conditions and the following disclaimer.
52+
53+
2. Redistributions in binary form must reproduce the above copyright notice,
54+
this list of conditions and the following disclaimer in the documentation
55+
and/or other materials provided with the distribution.
56+
57+
3. Neither the name of the copyright holder nor the names of its
58+
contributors may be used to endorse or promote products derived from
59+
this software without specific prior written permission.
60+
61+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
62+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
63+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
64+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
65+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
66+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
67+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
68+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
69+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
70+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
71+
"""
72+
with open("LICENSE", "w") as f:
73+
f.write(bsd3_license)
74+
75+
76+
def generate_gpl3_license():
77+
"""Generate GPL-3.0 license file."""
78+
# This would be the full GPL-3.0 license, but it's very long
79+
# Here we'll just write a reference to the standard license
80+
gpl3_license = f"""Copyright (C) {year} {author}
81+
82+
This program is free software: you can redistribute it and/or modify
83+
it under the terms of the GNU General Public License as published by
84+
the Free Software Foundation, either version 3 of the License, or
85+
(at your option) any later version.
86+
87+
This program is distributed in the hope that it will be useful,
88+
but WITHOUT ANY WARRANTY; without even the implied warranty of
89+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
90+
GNU General Public License for more details.
91+
92+
You should have received a copy of the GNU General Public License
93+
along with this program. If not, see <https://www.gnu.org/licenses/>.
94+
"""
95+
with open("LICENSE", "w") as f:
96+
f.write(gpl3_license)
97+
98+
99+
def generate_apache2_license():
100+
"""Generate Apache-2.0 license file."""
101+
apache2_license = f""" Apache License
102+
Version 2.0, January 2004
103+
http://www.apache.org/licenses/
104+
105+
Copyright {year} {author}
106+
107+
Licensed under the Apache License, Version 2.0 (the "License");
108+
you may not use this file except in compliance with the License.
109+
You may obtain a copy of the License at
110+
111+
http://www.apache.org/licenses/LICENSE-2.0
112+
113+
Unless required by applicable law or agreed to in writing, software
114+
distributed under the License is distributed on an "AS IS" BASIS,
115+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
116+
See the License for the specific language governing permissions and
117+
limitations under the License.
118+
"""
119+
with open("LICENSE", "w") as f:
120+
f.write(apache2_license)
121+
122+
123+
if __name__ == "__main__":
124+
if license_type == "MIT":
125+
generate_mit_license()
126+
elif license_type == "BSD-3":
127+
generate_bsd3_license()
128+
elif license_type == "GPL-3.0":
129+
generate_gpl3_license()
130+
elif license_type == "Apache-2.0":
131+
generate_apache2_license()
132+
else:
133+
print(f"Unsupported license type: {license_type}")
134+
135+
# Create test directory if tests are included
136+
if "{{cookiecutter.include_tests}}" == "y":
137+
if not os.path.exists("tests"):
138+
os.makedirs("tests")
139+
with open("tests/__init__.py", "w") as f:
140+
f.write("""Test package for {{cookiecutter.package_name}}.""")
141+
142+
# Create a basic test file
143+
with open("tests/test_cli.py", "w") as f:
144+
f.write("""#!/usr/bin/env python
145+
"""Test CLI for {{cookiecutter.package_name}}."""
146+
147+
from __future__ import annotations
148+
149+
import os
150+
import pathlib
151+
import subprocess
152+
import sys
153+
154+
import pytest
155+
156+
import {{cookiecutter.package_name}}
157+
158+
159+
def test_run():
160+
"""Test run."""
161+
# Test that the function doesn't error
162+
proc = {{cookiecutter.package_name}}.run(cmd="echo", cmd_args=["hello"])
163+
assert proc is None
164+
165+
# Test when G_IS_TEST is set, it returns the proc
166+
os.environ["{{cookiecutter.package_name.upper()}}_IS_TEST"] = "1"
167+
proc = {{cookiecutter.package_name}}.run(cmd="echo", cmd_args=["hello"])
168+
assert isinstance(proc, subprocess.Popen)
169+
assert proc.returncode == 0
170+
del os.environ["{{cookiecutter.package_name.upper()}}_IS_TEST"]
171+
""")
172+
173+
# Create docs directory if docs are included
174+
if "{{cookiecutter.include_docs}}" == "y":
175+
if not os.path.exists("docs"):
176+
os.makedirs("docs")
177+
with open("docs/index.md", "w") as f:
178+
f.write("""# {{cookiecutter.project_name}}
179+
180+
{{cookiecutter.project_description}}
181+
182+
## Installation
183+
184+
```bash
185+
pip install {{cookiecutter.package_name}}
186+
```
187+
188+
## Usage
189+
190+
```bash
191+
{{cookiecutter.package_name}}
192+
```
193+
194+
This will detect the type of repository in your current directory and run the appropriate VCS command.
195+
""")
196+
197+
# Create GitHub Actions workflows if included
198+
if "{{cookiecutter.include_github_actions}}" == "y":
199+
if not os.path.exists(".github/workflows"):
200+
os.makedirs(".github/workflows")
201+
with open(".github/workflows/tests.yml", "w") as f:
202+
f.write("""name: tests
203+
204+
on:
205+
push:
206+
branches: [main]
207+
pull_request:
208+
branches: [main]
209+
210+
jobs:
211+
build:
212+
runs-on: ubuntu-latest
213+
strategy:
214+
matrix:
215+
python-version: ['3.9', '3.10', '3.11']
216+
217+
steps:
218+
- uses: actions/checkout@v3
219+
- name: Set up Python ${{ matrix.python-version }}
220+
uses: actions/setup-python@v4
221+
with:
222+
python-version: ${{ matrix.python-version }}
223+
- name: Install dependencies
224+
run: |
225+
python -m pip install --upgrade pip
226+
pip install uv
227+
uv pip install -e .
228+
uv pip install pytest pytest-cov
229+
- name: Test with pytest
230+
run: |
231+
uv pip install pytest
232+
pytest
233+
""")
234+
235+
print("Project generated successfully!")
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# `$ {{cookiecutter.package_name}}`
2+
3+
{{cookiecutter.project_description}}
4+
5+
[![Python Package](https://img.shields.io/pypi/v/{{cookiecutter.package_name}}.svg)](https://pypi.org/project/{{cookiecutter.package_name}}/)
6+
{% if cookiecutter.include_docs == "y" %}
7+
[![Docs](https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/workflows/docs/badge.svg)](https://{{cookiecutter.package_name}}.git-pull.com)
8+
{% endif %}
9+
{% if cookiecutter.include_github_actions == "y" %}
10+
[![Build Status](https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/workflows/tests/badge.svg)](https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/actions?query=workflow%3A%22tests%22)
11+
[![Code Coverage](https://codecov.io/gh/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/branch/master/graph/badge.svg)](https://codecov.io/gh/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}})
12+
{% endif %}
13+
[![License](https://img.shields.io/github/license/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}.svg)](https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/blob/master/LICENSE)
14+
15+
Shortcut / powertool for developers to access current repos' VCS, whether it's
16+
{% for vcs in cookiecutter.supported_vcs.split(',') %}
17+
{% if loop.first %}{{vcs.strip()}}{% elif loop.last %} or {{vcs.strip()}}{% else %}, {{vcs.strip()}}{% endif %}{% endfor %}.
18+
19+
```console
20+
$ pip install --user {{cookiecutter.package_name}}
21+
```
22+
23+
```console
24+
$ {{cookiecutter.package_name}}
25+
```
26+
27+
### Developmental releases
28+
29+
You can test the unpublished version of {{cookiecutter.package_name}} before its released.
30+
31+
- [pip](https://pip.pypa.io/en/stable/):
32+
33+
```console
34+
$ pip install --user --upgrade --pre {{cookiecutter.package_name}}
35+
```
36+
37+
- [pipx](https://pypa.github.io/pipx/docs/):
38+
39+
```console
40+
$ pipx install --suffix=@next {{cookiecutter.package_name}} --pip-args '\--pre' --force
41+
```
42+
43+
Then use `{{cookiecutter.package_name}}@next --help`.
44+
45+
# More information
46+
47+
- Python support: >= {{cookiecutter.python_version}}, pypy
48+
- VCS supported: {% for vcs in cookiecutter.supported_vcs.split(',') %}{{vcs.strip()}}(1){% if not loop.last %}, {% endif %}{% endfor %}
49+
- Source: <https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}>
50+
{% if cookiecutter.include_docs == "y" %}
51+
- Docs: <https://{{cookiecutter.package_name}}.git-pull.com>
52+
- Changelog: <https://{{cookiecutter.package_name}}.git-pull.com/history.html>
53+
- API: <https://{{cookiecutter.package_name}}.git-pull.com/api.html>
54+
{% endif %}
55+
- Issues: <https://github.com/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}/issues>
56+
{% if cookiecutter.include_github_actions == "y" %}
57+
- Test Coverage: <https://codecov.io/gh/{{cookiecutter.github_username}}/{{cookiecutter.github_repo}}>
58+
{% endif %}
59+
- pypi: <https://pypi.python.org/pypi/{{cookiecutter.package_name}}>
60+
- License: [{{cookiecutter.license}}](https://opensource.org/licenses/{{cookiecutter.license}})

0 commit comments

Comments
 (0)