How to generate beautiful technical documentation
In the previous post I gave some motivational tips to inspire you to document your coding project. In this post I will illustrate how you can convert documentation written as plain text files into beautiful HTML documentation using a tool called Sphinx.
Installing Sphinx
Sphinx is a documentation generation tool written in Python and it can be
installed using pip
. If you do not yet have pip
installed on your
system please have a look at the
pip installation notes.
Let us install Sphinx.
$ sudo pip install -U Sphinx
Generating boilerplate files for the documentation
Suppose that we are at the early stages of our project. All we have is
a README
file with the content below.
README
======
This project aims to inspire people to write more and better documentation.
However, we know that we want to store more extensive documentation in
a subdirectory named docs
. Let us create that directory and add
some Sphinx boilerplate files to it.
$ mkdir docs
$ cd docs
$ sphinx-quickstart
The last command will prompt you for answers to a bunch of questions on how you want to setup your documentation and what extensions you want to enable. I tend to accept the defaults for everything except the question on whether or not I want to separate the source and build directories.
> Separate source and build directories (y/n) [n]: y
The input fields for project name, author name(s) and project version require you to provide some information. Below are the answers that I gave to these questions in this instance.
> Project name: Better documentation
> Author name(s): Tjelvar Olsson
> Project version: 0.0.1
Let’s see what was generated.
$ tree
.
├── Makefile
├── build
├── make.bat
└── source
├── _static
├── _templates
├── conf.py
└── index.rst
4 directories, 4 files
Let us go through the files one by one. The Makefile
allows us to build the
documentation using make
. The make.bat
file allows us to build the documentation
on Windows based systems. The source/conf.py
file contains configurations for building
the documentation (we will edit this later). The index.rst
file is the root file of
the documentation we are about to write.
Let’s build some documentation
Before we do anything else let us see what we get when we build the documentation.
$ make html
This will create output in the directory build/html
, open the build/html/index.html
file
in your browser of choice. You should see something along the lines of the below.
Now have a look at the content of the source/index.rst
file.
.. Better documentation documentation master file, created by
sphinx-quickstart on Mon Jun 29 11:00:21 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Better documentation's documentation!
================================================
Contents:
.. toctree::
:maxdepth: 2
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
The first section is a comment (the section starting with ..
). This is
followed by a header (denoted by the =
underline). The .. toctree::
section is Sphinx’s way of denoting that a list of other files should be included
(at the moment we have none). Finally, in the Indices and tables
section
there are links to index, module and search pages. If you are documenting a
Python package the module page will contain links to the modules in your
package.
Adding some more content
Let us add some more content. Create the file source/intro.rst
and copy and
paste the text below into it.
Introduction
============
The purpose of this project is to help scientists write better documentation.
Now add a link to it in source/index.rst
.
Contents:
.. toctree::
:maxdepth: 2
intro
Note that the reference of the file to be included does not need the .rst
extension.
Furthermore it needs to be indented to the same level as :maxdepth:
(by
default this is three spaces). The latter has caught me out many times as I tend to
indent four spaces.
If you rebuild the documentation using make html
you will see the content
of the source/intro.rst
file included in the documentation.
reStructuredText markup
You may have noticed that we can create headers by underlining them with special characters. Sphinx uses reStructuredText as a markup language. For a quick introduction to the reStructuredText syntax have a look at A ReStructuredText Primer followed by Quick reStructuredText. Another good source is Sphinx’s ReStructuredText Primer.
Including code snippets in the documentation
Sphinx has taken advantage of the fact that reStructuredText is extensible and
has added directives of its own. We have already seen one of these: the toctree
directive.
Let us have a look at the code-block
directive, which can be used to include
code snippets. Create the file source/code_example.rst
and add the text
below to it.
Code example
============
Here is a Python function.
.. code-block:: python
def greet(name):
print("Hello {}".format(name))
Here is a C function.
.. code-block:: C
int add(int a, int b) {
return a + b;
}
Remember to include the file into the toctree
of source/index.rst
.
.. toctree::
:maxdepth: 2
intro
code_example
Use make html
to build the documentation and behold the beautifully
generated code snippets included in your documentation.
It is also possible to include whole files of source code in your
documentation. Copy and paste the text below into a file named
source/example_script.py
.
"""This is an example script."""
import sys
def greet(name):
"""Return greeting."""
return "Hello {}!".format(name)
if __name__ == "__main__":
name = sys.argv[1]
print(greet(name))
Now we will use Sphinx’s include
directive to include the content of this script
into the “Code example” page. Add the lines below to the end of the
source/code_example.rst
file.
Below is the content of a Python sample script.
.. literalinclude:: example_script.py
:language: python
Sphinx also has several options for styling the display of your code snippets. For example you can add line numbers and emphasize particular lines. For more inspiration on how to include code snippets in your documentation have a look at Showing code examples in the Sphinx documentation.
Generating API documentaiton for Python projects
Sphinx has got particularly good support for documenting Python projects.
Let us create a module named chemistry
for us to document at the root level
of the project .
$ cd ../
$ mkdir chemistry
$ ls
README
docs
chemistry
Create the file chemistry/__init__.py
and add the code below to it.
"""Basic chemistry module.
The :mod:`chemistry` module contains three classes:
- :class:`chemistry.Atom`
- :class:`chemistry.Bond`
- :class:`chemistry.Molecule`
One can use the :func:`chemistry.Molecule.add_atom` and
:func:`chemsitry.Molecule.add_bond` functions to build up a molecule.
Example illustrating how to create a methane molecule.
>>> from chemistry import Molecule
>>> mol = Molecule('Methane')
>>> carbon_index = mol.add_atom(atomic_number=6)
>>> hydrogen1_index = mol.add_atom(atomic_number=1)
>>> hydrogen2_index = mol.add_atom(atomic_number=1)
>>> hydrogen3_index = mol.add_atom(atomic_number=1)
>>> hydrogen4_index = mol.add_atom(atomic_number=1)
>>> bond1_index = mol.add_bond(carbon_index, hydrogen1_index)
>>> bond2_index = mol.add_bond(carbon_index, hydrogen2_index)
>>> bond3_index = mol.add_bond(carbon_index, hydrogen3_index)
>>> bond4_index = mol.add_bond(carbon_index, hydrogen4_index)
"""
class Atom(object):
"""Class representing an atom."""
def __init__(self, atomic_number):
self.atomic_number = atomic_number
self.bonds = []
def bond_to(self, other_atom):
"""Return the :class:`chemistry.Bond` formed between the two atoms.
:param other_atom: :class:`chemistry.Atom` to form :class:`chemistry.Bond` to
:returns: :class:`chemistry.Bond`
"""
bond = Bond(self, other_atom)
self.bonds.append(bond)
other_atom.bonds.append(bond)
return bond
class Bond(object):
"""Class representing a bond between two atoms."""
def __init__(self, atom1, atom2):
self.atoms = (atom1, atom2)
class Molecule(object):
"""Class representing a molecule consisting of atoms and bonds."""
def __init__(self, identifier):
self.identifier = identifier
self.atoms = []
self.bonds = []
def add_atom(self, atomic_number):
"""Return the list index of the atom added to the molecule.
:param atomic_number: atomic number of the atom to be added
:returns: index of the atom in the molecule
"""
atom = Atom(atomic_number)
self.atoms.append(atom)
return len(self.atoms) - 1
def add_bond(self, atom1_index, atom2_index):
"""Return the list index of the bond added to the molecule.
:param atom1_index: atom's index in molecule
:param atom2_index: atom's index in molecule
:returns: index of the bond in the molecule
"""
atom1 = self.atoms[atom1_index]
atom2 = self.atoms[atom2_index]
bond = atom1.bond_to(atom2)
self.bonds.append(bond)
return len(self.bonds) - 1
We will now use Sphinx’s autodoc
functionality to generate API
documentation for this module. First of all we need to add the
sphinx.ext.autodoc
extension to the docs/source/conf.py
file.
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc']
In the same file we also need to specify the path to the module that we want to generate documentation for.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('../../'))
Now create the file docs/source/api.rst
and copy and paste the text below
into it.
API documentaiton
=================
.. automodule:: chemistry
:members:
We also need to remember to include the api.rst
file in the toctree
.
Edit the docs/source/index.rst
file to match the below.
.. toctree::
:maxdepth: 2
intro
code_example
api
Finally, regenerate the documentation by running make html
in the docs
directory and behold the beautifully generated API documentation.
If you interact with the generated HTML documentation you will note that the constructs following the pattern below have been converted into hyperlinks.
:mod:`chemistry`
:class:`chemistry.Molecule`
:func:`chemistry.Molecule.add_atom`
These directives can be used anywhere in your documentation to link to the relevant section in the API documentation. Having descriptive documentation that contains links to the more technical API documentation is very pleasant and these directives make it very easy to do so.
It is also worth commenting on the :param:
and :returns:
directives
used in the docstrings. These are part of a larger set of description directives
that are formatted nicely by Sphinx. For more information have a look at the
info field list section
in the Sphinx documentation.
What about the original README file?
Let us finish off by including the content of the original README
file
into the generated HTML documenation.
Create the file docs/source/README.rst
and copy and paste the text below into it.
.. include:: ../../README
This will include the content of the top level README
file into the documentation.
Styling the documentaiton
The default theme of Sphinx is currently Alabaster. It is very beautiful. However, personally I prefer the Sphinx ReadTheDocs theme. In particular because of its left hand side navigation bar. Let’s check it out.
First of all we install the theme using pip
.
$ pip install sphinx_rtd_theme
Now we need to edit theme section in docs/source/conf.py
to look like the
below.
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#html_theme = 'alabaster'
# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
Note that the code above includes some logic for handling the cases where one hosts the documentation on readthedocs.
Regenerate the documentation by running make html
in the docs
directory
and explore the look and feel of this new theme. Note in particular the
behaviour of the left hand side navigation bar and the clear “next” and
“previous” buttons at the bottom of each page.
For more information on the ReadTheDocs theme have a look here.
ReadTheDocs
Whilst on the subject it is worth mentioning the ability to host your documentation on readthedocs. Simply sign-up for an account, link your GitHub/BitBucket account and then you can select the projects that you want to host on readthedocs. It is great!
It is worth noting that if your project documentation includes links to packages
such as numpy
and scipy
you will need to mock these out in the
conf.py
file. For more information have a look at this
readthedocs faq.
For a real life example have a look at
this conf.py.
Further reading
I hope this post has inspired you to try out Sphinx. It is a wonderful tool for generating beautiful documentation!
Below are a couple links to other resources on how to use Sphinx.