Auto-Generating Documentation Using Mercurial, ReST, and Sphinx
I often find myself taking notes about various aspects of my job that I feel I would forget as soon as I moved onto another project. I've gotten into the habit of taking my notes using reStructured Text, which shouldn't come as any surprise to any of my regular visitors. On several occasions, I had some of the other guys in the company ask me for some clarification on some things I had taken notes on. Lucky for me, I had taken some nice notes!
However, these individuals probably wouldn't appreciate reading ReST markup as much as I do, so I decided to do something nice for them. I setup Sphinx to prettify my documentation. I then wrote a small Web server using Python, so people within the company network could access the latest version of my notes without much hassle.
Just like I take notes to remind myself of stuff at work, I want to do that again for this automated ReST->HTML magic--I want to be able to do this in the future! I figured I would make my notes even more public this time, so you all can enjoy similar bliss.
I am writing this article with UNIX-like operating systems in mind. Please forgive me if you're a Windows user and some of this is not consistent with what you're seeing. Perhaps one day I'll try to set this sort of thing up on Windows.
The first step that we want to take is installing Sphinx. This is the project that Python itself uses to generate its online documentation. It's pretty dang awesome. Feel free to skip this section if you have already installed Sphinx.
Depending on your environment of choice, you may or may not have a package manager that offers python-sphinx or something along those lines. I personally prefer to install it using pip or easy_install:
$ sudo pip install sphinx
Running that command will likely respond with a bunch of output about downloading Sphinx and various dependencies. When I ran it in my sandbox VM, I saw it install the following packages:
It should be a pretty speedy installation.
We'll be using Mercurial to keep track of changes to our ReST documentation. Mercurial is a distributed version control system that is built using Python. It's wonderful! Just like with Sphinx, if you have already installed Mercurial, feel free to skip to the next section.
I personally prefer to install Mercurial using pip or easy_install--it's usually more up-to-date than what you would have in your package repositories. To do that, simply run a command such as the following:
$ sudo pip install mercurial
This will go out and download and install the latest stable Mercurial. You may need python-dev or something like that for your platform in order for that command to work. However, if you're on Windows, I highly recommend TortoiseHg. The installer for TortoiseHg will install a graphical Mercurial client along with the command line tools.
Create A Repository
Now let's create a brand new Mercurial repository to house our notes/documentation. Open a terminal/console/command prompt to the location of your choice on your computer and execute the following commands:
$ hg init mydox $ cd mydox
The next step is to configure Sphinx for our project. Sphinx makes this very simple:
This is a wizard that will walk you through the configuration process for your project. It's pretty safe to accept the defaults, in my opinion. Here's the output of my wizard:
$ sphinx-quickstart Welcome to the Sphinx quickstart utility. Please enter values for the following settings (just press Enter to accept a default value, if one is given in brackets). Enter the root path for documentation. > Root path for the documentation [.]: You have two options for placing the build directory for Sphinx output. Either, you use a directory "_build" within the root path, or you separate "source" and "build" directories within the root path. > Separate source and build directories (y/N) [n]: y Inside the root directory, two more directories will be created; "_templates" for custom HTML templates and "_static" for custom stylesheets and other static files. You can enter another prefix (such as ".") to replace the underscore. > Name prefix for templates and static dir [_]: The project name will occur in several places in the built documentation. > Project name: My Dox > Author name(s): Josh VanderLinden Sphinx has the notion of a "version" and a "release" for the software. Each version can have multiple releases. For example, for Python the version is something like 2.5 or 3.0, while the release is something like 2.5.1 or 3.0a1. If you don't need this dual structure, just set both to the same value. > Project version: 0.0.1 > Project release [0.0.1]: The file name suffix for source files. Commonly, this is either ".txt" or ".rst". Only files with this suffix are considered documents. > Source file suffix [.rst]: One document is special in that it is considered the top node of the "contents tree", that is, it is the root of the hierarchical structure of the documents. Normally, this is "index", but if your "index" document is a custom template, you can also set this to another filename. > Name of your master document (without suffix) [index]: Please indicate if you want to use one of the following Sphinx extensions: > autodoc: automatically insert docstrings from modules (y/N) [n]: > doctest: automatically test code snippets in doctest blocks (y/N) [n]: > intersphinx: link between Sphinx documentation of different projects (y/N) [n]: > todo: write "todo" entries that can be shown or hidden on build (y/N) [n]: > coverage: checks for documentation coverage (y/N) [n]: > pngmath: include math, rendered as PNG images (y/N) [n]: > jsmath: include math, rendered in the browser by JSMath (y/N) [n]: > ifconfig: conditional inclusion of content based on config values (y/N) [n]: A Makefile and a Windows command file can be generated for you so that you only have to run e.g. `make html' instead of invoking sphinx-build directly. > Create Makefile? (Y/n) [y]: > Create Windows command file? (Y/n) [y]: n Finished: An initial directory structure has been created. You should now populate your master file ./source/index.rst and create other documentation source files. Use the Makefile to build the docs, like so: make builder where "builder" is one of the supported builders, e.g. html, latex or linkcheck.
If you followed the same steps I did (I separated the source and build directories), you should see three new files in your mydox repository:
We'll do our work in the source directory.
Get Some ReST
Now is the time when we start writing some ReST that we want to turn into HTML using Sphinx. Open some file, like first_doc.rst and put some ReST in it. If nothing comes to mind, or you're not familiar with ReST syntax, try the following:
========================= This Is My First Document ========================= Yes, this is my first document. It's lame. Deal with it.
Save the file (keep in mind that it should be within the source directory if you used the same settings I did). Now it's time to add it to the list of files that Mercurial will pay attention to. While we're at it, let's add the other files that were created by the Sphinx configuration wizard:
$ hg add adding ../Makefile adding conf.py adding first_doc.rst adding index.rst $ hg st A Makefile A source/conf.py A source/first_doc.py A source/index.rst
Don't worry that we don't see all of the directories in the output of hg st--Mercurial tracks files, not directories.
Here comes the magic in automating the conversion from ReST to HTML: Mercurial hooks. We will use the precommit hook to fire off a command that tells Sphinx to translate our ReST markup into HTML.
Edit your mydox/.hg/hgrc file. If the file does not yet exist, go ahead and create it. Add the following content to it:
[hooks] precommit.sphinxify = ~/bin/sphinxify_docs.sh
I've opted to call a Bash script instead of using an inline Python call. Now let's create the Bash script, ~/bin/sphinxify_docs.sh:
#!/bin/bash cd $HOME/mydox sphinx-build source/ docs/
Notice that I used the $HOME environment variable. This means that I created the mydox directory at /home/myusername/mydox. Adjust that line according to your setup. You'll probably also want to make that script executable:
$ chmod +x ~/bin/sphinxify_docs.sh
Three, Two, One...
You should now be at a stage where you can safely commit changes to your repository and have Sphinx build your HTML documentation. Execute the following command somewhere under your mydox repository:
$ hg ci -m "Initial commit"
If your setup is anything like mine, you should see some output similar to this:
$ hg ci -m "Initial commit" Making output directory... Running Sphinx v0.6.4 No builder selected, using default: html loading pickled environment... not found building [html]: targets for 2 source files that are out of date updating environment: 2 added, 0 changed, 0 removed reading sources... [100%] index looking for now-outdated files... none found pickling environment... done checking consistency... /home/jvanderlinden/mydox/source/first_doc.rst:: WARNING: document isn't included in any toctree done preparing documents... done writing output... [100%] index writing additional files... genindex search copying static files... done dumping search index... done dumping object inventory... done build succeeded, 1 warning. $ hg st ? docs/.buildinfo ? docs/.doctrees/environment.pickle ? docs/.doctrees/first_doc.doctree ? docs/.doctrees/index.doctree ? docs/_sources/first_doc.txt ? docs/_sources/index.txt ? docs/_static/basic.css ? docs/_static/default.css ? docs/_static/doctools.js ? docs/_static/file.png ? docs/_static/jquery.js ? docs/_static/minus.png ? docs/_static/plus.png ? docs/_static/pygments.css ? docs/_static/searchtools.js ? docs/first_doc.html ? docs/genindex.html ? docs/index.html ? docs/objects.inv ? docs/search.html ? docs/searchindex.js
If you see something like that, you're in good shape. Go ahead and take a look at your new mydox/docs/index.html file in the Web browser of your choosing.
Not very exciting, is it? Notice how your first_doc.rst doesn't appear anywhere on that page? That's because we didn't tell Sphinx to put it there. Let's do that now.
Edit the mydox/source/index.rst file that was created during Sphinx configuration. In the section that starts with .. toctree::, let's tell Sphinx to include everything we ReST-ify:
.. toctree:: :maxdepth: 2 :glob: *
That should do it. Now, I don't know about you, but I don't really want to include the output HTML, images, CSS, JS, or anything in my documentation repository. It would just take up more space each time we change an .rst file. Let's tell Mercurial to not pay attention to the output HTML--it'll just be static and always up-to-date on our filesystem.
Create a new file called mydox/.hgignore. In this file, put the following content:
syntax: glob docs/
Save the file, and you should now see something like the following when running hg st:
$ hg st M source/index.rst ? .hgignore
Let's include the .hgignore file in the list of files that Mercurial will track:
$ hg add .hgignore $ hg st M source/index.rst A .hgignore
Finally, let's commit one more time:
$ hg ci -m "Updating the index to include our .rst files" Running Sphinx v0.6.4 No builder selected, using default: html loading pickled environment... done building [html]: targets for 1 source files that are out of date updating environment: 0 added, 1 changed, 0 removed reading sources... [100%] index looking for now-outdated files... none found pickling environment... done checking consistency... done preparing documents... done writing output... [100%] index writing additional files... genindex search copying static files... done dumping search index... done dumping object inventory... done build succeeded.
Tada!! The first_doc.rst should now appear on the index page.
Serving Your Documentation
Who seriously wants to have HTML files that are hard to get to? How can we make it easier to access those HTML files? Perhaps we can create a simple static file Web server? That might sound difficult, but it's really not--not when you have access to Python!
#!/usr/bin/env python # -*- coding: utf-8 -*- from BaseHTTPServer import HTTPServer from SimpleHTTPServer import SimpleHTTPRequestHandler def main(): try: server = HTTPServer(('', 80), SimpleHTTPRequestHandler) server.serve_forever() except KeyboardInterrupt: server.socket.close() if __name__ == '__main__': main()
I created this simple script and put it in my ~/bin/ directory, also making it executable. Once that's done, you can navigate to your mydox/docs/ directory and run the script. Since I called the script webserver.py, I just do this:
$ cd ~/mydox/docs $ sudo webserver.py
This makes it possible for you to visit http://localhost/ on your own computer, or to use your computer's IP in place of localhost to access your documentation from a different computer on your network. Pretty slick, if you ask me.
I suppose there's more I could add, but that's all I have time for tonight. Enjoy!