I used to find writing command line tools tedious. Not so much the writing of the core of the tool itself, but all the peripheral stuff you had to do to actually finish one.
The Language? ¶
The first issue is to pick the language to implement it in: do I use Python, which I'm intimitely familiar with, or a Unix shell script? With shell scripts, the syntax is pretty terrible, but the tool typically fits in a single file and there's hardly any overhead running them. On the other hand, making sure the tool works under all circumstances can be tricky. Shell scripts are notorious for breaking when you feed them arguments with spaces. The burden of making sure you properly quote all the variable interpolations in the script is on the programmer. It's possible to do, just unnecessarily hard.
On the other hand, Python is so much more expressive. There are a ton of libraries out there ready to use, and Python itself includes a lot of batteries already in its standard library, of course.
Python comes with its own set of problems, though. Python runtime environments
are typically a mess, and I don't want to further pollute people's already
cluttered global Python environments. With Python, installing a package is
typically just a
pip install <pkg> away, but it requires another tedious
step: writing a
If it comes to distributing the script, a shell script may be much easier.
With shell scripts it's either a single file that needs to be copied somewhere.
Manually, or via a
make install command, which involves adding a
and dealing with subtle differences for each Unix platform, not to even mention
trying to run it on Windows machines.
Argument Parsing? ¶
Each script will at some stage require some options or arguments. How should
we do the argument parsing? Do I use
getopts? Does it even
matter? Can it take
--long-form-options? Or do I resign myself to poor
man's arg parsing again? The latter has too often become the default choice.
Standing on the Shoulders of Giants ¶
Lately, a few fantastic projects have taken away most of the tedious work surrounding the building of command line tools, and almost make it trivial now.
Click is a Python library written by Armin Ronacher that deals with all the handling of command line option and argument parsing and comes with fantastic defaults. This project is a great step towards more consistent and standard CLI interfaces. Besides solving the options and argument parsing, it also has a ton of useful features packaged, like smart colorized terminal output, file abstractions, subcommands, and rendering progress bars.
It solves the argument parsing problem.
Using pipsi (also by Armin!), users can install any Python command line script into an isolated Python runtime environment, so it solves the global cluttered Python environment problem entirely.
Cookiecutter (by the awesome Audrey Roy Greenfield) is a project generator, based on a predefined project template. It will read the template, ask the user a few questions to fill in the blanks, and generates a new project for you.
cookiecutter-python-cli is one such Cookiecutter template I wrote
that uses all of the above: it sports a predefined
setup.py, a package
structure that's extensible, and test cases and a test runner to get you
Putting it Together ¶
Let's build a new high quality CLI in Python in under 60 seconds now.
First, install pipsi and follow its instructions:
$ curl https://raw.githubusercontent.com/mitsuhiko/pipsi/master/get-pipsi.py | python
Next, using pipsi, install Cookiecutter in its own isolated runtime environment:
$ pipsi install cookiecutter
Now use Cookiecutter to create your brand new project, based on my CLI template:
$ cd ~/Desktop $ cookiecutter https://github.com/nvie/cookiecutter-python-cli.git Cloning into 'cookiecutter-python-cli'... remote: Counting objects: 64, done. remote: Total 64 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (64/64), done. Checking connectivity... done. full_name (default is "Vincent Driessen")? email (default is "email@example.com")? github_username (default is "nvie")? project_name (default is "My Tool")? repo_name (default is "python-mytool")? pypi_name (default is "mytool")? script_name (default is "my-tool")? package_name (default is "my_tool")? project_short_description (default is "My Tool does one thing, and one thing well.")? release_date (default is "2014-09-04")? year (default is "2014")? version (default is "0.1.0")?
When you're done, you'll have a project where you can run
tox to run
your test suite on all important Python versions. If you don't need the test
cases, simply remove the
$ cd python-mytool/ $ tox ... py26: commands succeeded py27: commands succeeded py33: commands succeeded py34: commands succeeded pypy: commands succeeded flake8: commands succeeded congratulations :)
Let's install and run it without further modifications:
$ pipsi install --editable . ... $ my-tool Hello, world. $ my-tool --as-cowboy Howdy, world. $ my-tool --as-cowboy Vincent Howdy, Vincent.
You can edit the
setup.py to your liking. The default provided version
should already work out of the box. When you're done implementing your tool,
you can either upload it to PyPI or just keep it to yourself locally:
$ pipsi install <pkgname> # install from PyPI $ pipsi install --editable ../path/to/project/dir # install locally
If you have any improvements for this template, please submit a pull request. Thanks!