Skip to content

Commit feb7db5

Browse files
committed
Work begins on #408 and #399
1 parent a72219b commit feb7db5

File tree

3 files changed

+230
-12
lines changed

3 files changed

+230
-12
lines changed

CONTRIBUTING.md

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,29 @@ $ cd ~/src/cmd2
200200
$ pip install -e .[dev]
201201
```
202202

203-
This will install `pytest` and `tox` for running unit tests, `pylint` for
204-
static code analysis, and `sphinx` for building the documentation.
203+
This project uses many python modules for various development tasks, including
204+
testing, rendering documentation, and building and distributing releases. These
205+
modules can be configured many different ways, which can make it difficult to
206+
learn the specific incantations required for each project you are familiar with.
207+
208+
This project uses `invoke <http://www.pyinvoke.org>`_ to provide a clean, high
209+
level interface for these development tasks. To see the full list of functions
210+
available::
211+
```bash
212+
$ invoke -l
213+
```
214+
215+
You can run multiple tasks in a single invocation, for example::
216+
```bash
217+
$ invoke clean docs sdist wheel
218+
```
219+
220+
That one command will remove all superflous cache, testing, and build
221+
files, render the documentation, and build a source distribution and a
222+
wheel distribution.
223+
224+
If you want to see the details about what `invoke` is doing under the hood,
225+
have a look at `tasks.py`.
205226

206227
Now you can check if everything is installed and working:
207228
```bash
@@ -220,20 +241,32 @@ This bit is up to you!
220241

221242
#### How to find the code in the cmd2 codebase to fix/edit?
222243

223-
The cmd2 project directory structure is pretty simple and straightforward. All actual code for cmd2
224-
is located underneath the `cmd2` directory. The code to generate the documentation is in the `docs` directory. Unit tests are in the `tests` directory. The `examples` directory contains examples of how
225-
to use cmd2. There are various other files in the root directory, but these are primarily related to
226-
continuous integration and to release deployment.
244+
The cmd2 project directory structure is pretty simple and straightforward. All
245+
actual code for cmd2 is located underneath the `cmd2` directory. The code to
246+
generate the documentation is in the `docs` directory. Unit tests are in the
247+
`tests` directory. The `examples` directory contains examples of how to use
248+
cmd2. There are various other files in the root directory, but these are
249+
primarily related to continuous integration and to release deployment.
227250

228251
#### Changes to the documentation files
229-
If you made changes to any file in the `/docs` directory, you need to build the Sphinx documentation
230-
and make sure your changes look good:
231-
```shell
232-
cd docs
233-
make clean html
252+
253+
If you made changes to any file in the `/docs` directory, you need to build the
254+
Sphinx documentation and make sure your changes look good:
255+
```bash
256+
invoke docs
234257
```
235258
In order to see the changes, use your web browser of choice to open `<cmd2>/docs/_build/html/index.html`.
236259

260+
If you would rather use a webserver to view the documentation, including
261+
automatic page refreshes as you edit the files, use:
262+
263+
```bash
264+
invoke livehtml
265+
```
266+
267+
You will be shown the IP address and port number where the documents are now
268+
served. Put that into your web browser and edit away.
269+
237270
### Static Code Analysis
238271

239272
You should have some sort of [PEP8](https://www.python.org/dev/peps/pep-0008/)-based linting running in your editor or IDE or at the command-line before you commit code. [pylint](https://www.pylint.org) is a good Python linter which can be run at the command-line but also can integrate with many IDEs and editors.
@@ -244,7 +277,7 @@ You should have some sort of [PEP8](https://www.python.org/dev/peps/pep-0008/)-b
244277
When you're ready to share your code, run the test suite:
245278
```shell
246279
cd <cmd2>
247-
py.test
280+
invoke pytest
248281
```
249282
and ensure all tests pass.
250283

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
# install with 'pip install -e .[dev]'
7474
'dev': [
7575
'pytest', 'pytest-cov', 'tox', 'pylint', 'sphinx', 'sphinx-rtd-theme',
76+
'sphinx-autobuild','invoke',
7677
]
7778
}
7879

tasks.py

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#
2+
# coding=utf-8
3+
"""Development related tasks to be run with 'invoke'"""
4+
5+
import os
6+
import shutil
7+
8+
import invoke
9+
10+
# shared function
11+
def rmrf(dirs, verbose=True):
12+
"Silently remove a list of directories"
13+
if isinstance(dirs, str):
14+
dirs = [dirs]
15+
16+
for dir_ in dirs:
17+
if verbose:
18+
print("Removing {}".format(dir_))
19+
shutil.rmtree(dir_, ignore_errors=True)
20+
21+
22+
# create namespaces
23+
namespace = invoke.Collection()
24+
namespace_clean = invoke.Collection('clean')
25+
namespace.add_collection(namespace_clean, 'clean')
26+
27+
#####
28+
#
29+
# pytest, tox, pylint, and codecov
30+
#
31+
#####
32+
@invoke.task
33+
def pytest(context):
34+
"Run tests and code coverage using pytest"
35+
context.run("pytest --cov=cmd2 --cov-report=html")
36+
namespace.add_task(pytest)
37+
38+
@invoke.task
39+
def pytest_clean(context):
40+
"Remove pytest cache directories"
41+
#pylint: disable=unused-argument
42+
dirs = ['.pytest-cache', '.cache', 'htmlcov']
43+
rmrf(dirs)
44+
namespace_clean.add_task(pytest_clean, 'pytest')
45+
46+
@invoke.task
47+
def tox(context):
48+
"Run unit and integration tests on multiple python versions using tox"
49+
context.run("tox")
50+
namespace.add_task(tox)
51+
52+
@invoke.task
53+
def tox_clean(context):
54+
"Remove tox virtualenvs and logs"
55+
#pylint: disable=unused-argument
56+
rmrf('.tox')
57+
namespace_clean.add_task(tox_clean, 'tox')
58+
59+
@invoke.task
60+
def codecov_clean(context):
61+
"Remove code coverage reports"
62+
#pylint: disable=unused-argument
63+
dirs = set()
64+
for name in os.listdir(os.curdir):
65+
if name.startswith('.coverage'):
66+
dirs.add(name)
67+
rmrf(dirs)
68+
namespace_clean.add_task(codecov_clean, 'coverage')
69+
70+
71+
#####
72+
#
73+
# documentation
74+
#
75+
#####
76+
DOCS_SRCDIR = 'docs'
77+
DOCS_BUILDDIR = os.path.join('docs', '_build')
78+
79+
@invoke.task()
80+
def docs(context, builder='html'):
81+
"Build documentation using sphinx"
82+
cmdline = 'python -msphinx -M {} {} {}'.format(builder, DOCS_SRCDIR, DOCS_BUILDDIR)
83+
context.run(cmdline)
84+
namespace.add_task(docs)
85+
86+
@invoke.task
87+
def docs_clean(context):
88+
"Remove rendered documentation"
89+
#pylint: disable=unused-argument
90+
rmrf(DOCS_BUILDDIR)
91+
namespace_clean.add_task(docs_clean, name='docs')
92+
93+
@invoke.task
94+
def livehtml(context):
95+
"Launch webserver on http://localhost:8000 with rendered documentation"
96+
builder = 'html'
97+
outputdir = os.path.join(DOCS_BUILDDIR, builder)
98+
cmdline = 'sphinx-autobuild -b {} {} {}'.format(builder, DOCS_SRCDIR, outputdir)
99+
context.run(cmdline, pty=True)
100+
namespace.add_task(livehtml)
101+
102+
103+
#####
104+
#
105+
# build and distribute
106+
#
107+
#####
108+
BUILDDIR = 'build'
109+
DISTDIR = 'dist'
110+
111+
@invoke.task
112+
def build_clean(context):
113+
"Remove the build directory"
114+
#pylint: disable=unused-argument
115+
rmrf(BUILDDIR)
116+
namespace_clean.add_task(build_clean, 'build')
117+
118+
@invoke.task
119+
def dist_clean(context):
120+
"Remove the dist directory"
121+
#pylint: disable=unused-argument
122+
rmrf(DISTDIR)
123+
namespace_clean.add_task(dist_clean, 'dist')
124+
125+
@invoke.task
126+
def eggs_clean(context):
127+
"Remove egg directories"
128+
#pylint: disable=unused-argument
129+
dirs = set()
130+
dirs.add('.eggs')
131+
for name in os.listdir(os.curdir):
132+
if name.endswith('.egg-info'):
133+
dirs.add(name)
134+
if name.endswith('.egg'):
135+
dirs.add(name)
136+
rmrf(dirs)
137+
namespace_clean.add_task(eggs_clean, 'eggs')
138+
139+
@invoke.task
140+
def pycache_clean(context):
141+
"Remove __pycache__ directories"
142+
#pylint: disable=unused-argument
143+
dirs = set()
144+
for root, dirnames, _ in os.walk(os.curdir):
145+
if '__pycache__' in dirnames:
146+
dirs.add(os.path.join(root, '__pycache__'))
147+
print("Removing __pycache__ directories")
148+
rmrf(dirs, verbose=False)
149+
namespace_clean.add_task(pycache_clean, 'pycache')
150+
151+
#
152+
# make a dummy clean task which runs all the tasks in the clean namespace
153+
clean_tasks = list(namespace_clean.tasks.values())
154+
@invoke.task(pre=list(namespace_clean.tasks.values()), default=True)
155+
def clean_all(context):
156+
"Run all clean tasks"
157+
#pylint: disable=unused-argument
158+
pass
159+
namespace_clean.add_task(clean_all, 'all')
160+
161+
@invoke.task
162+
def sdist(context):
163+
"Create a source distribution"
164+
context.run('python setup.py sdist')
165+
namespace.add_task(sdist)
166+
167+
@invoke.task
168+
def wheel(context):
169+
"Build a wheel distribution"
170+
context.run('python setup.py bdist_wheel')
171+
namespace.add_task(wheel)
172+
173+
@invoke.task(pre=[clean_all, sdist, wheel])
174+
def pypi(context):
175+
"Build and upload a distribution to pypi"
176+
context.run('twine upload dist/*')
177+
namespace.add_task(pypi)
178+
179+
@invoke.task(pre=[clean_all, sdist, wheel])
180+
def pypi_test(context):
181+
"Build and upload a distribution to https://test.pypi.org"
182+
context.run('twine upload --repository-url https://test.pypi.org/legacy/ dist/*')
183+
namespace.add_task(pypi_test)
184+

0 commit comments

Comments
 (0)