Tuesday, June 2, 2009

Using Sphinx and git as a simple CMS

Suppose you want to be able to easily publish content to a website without using a CMS like Plone or Drupal. Let’s say in order to minimize the attack surface of your website you don’t want any dynamic HTML generation to take place. In this post I’ll demonstrate one way this can be done using existing tools. The main building blocks are Sphinx and git. First, I’ll set up a self-contained installation using virtualenv:
$ mkdir sphinx_playground
$ virtualenv --no-site-packages sphinx_playground
New python executable in sphinx_playground/bin/python
Installing setuptools............done.
$ cd sphinx_playground
$ source bin/activate
(sphinx_playground)$ easy_install -U Sphinx
Searching for Sphinx
Reading http://pypi.python.org/simple/Sphinx/
Reading http://sphinx.pocoo.org/
Best match: Sphinx 0.6.1
Downloading http://pypi.python.org/packages/2.6/S/Sphinx/Sphinx-0.6.1-py2.6.egg#md5=0c5baac650e48792124f71eabb0eb029
[...]
Finished processing dependencies for Sphinx
(sphinx_playground)$
  
Installing Sphinx with easy_install installs all dependencies, including docutils and Pygments. The next step is to create directories for the git repository containing files in restructuredText and for the HTML that Sphinx will produce from these files. Sphinx includes a script named sphinx-quickstart, that will do this, as well as create some initial files to get started.
(sphinx_playground)$ sphinx-quickstart
[...]
(sphinx_playground)$ cd source
(sphinx_playground)$ git init
Initialized empty Git repository in /some/path/sphinx_playground/source/.git/
(sphinx_playground)$ git add conf.py index.rst
(sphinx_playground)$ git commit -m "initial commit"
Created initial commit ce317ec: initial commit
 2 files changed, 214 insertions(+), 0 deletions(-)
 create mode 100644 conf.py
 create mode 100644 index.rst
(sphinx_playground)$
  
sphinx-quickstart asks a few questions regarding the configuration. I chose seperate directories for source and build and accepted the default for most of the other choices. The script created a makefile, which I can use to generate the HTML by typing
(sphinx_playground)$ make html
  
The HTML files will end up in build/html. By default, the directory containing the stylesheets used in these files is build/html/_static. You probably want to change how the resulting HTML looks. Luckily, Sphinx has the concept of themes. I won’t go into detail on how to use this feature, since I don’t have the slightest clue. It’s probably not very hard, though. Check out the Sphinx documentation on how to use HTML themes. What’s left is adding a post commit hook in the git repository. Creating hooks in git is just a matter of putting a file named post-commit (or pre-commit, etc.) in /path/to/repo/.git/hooks. Here’s an example for a simple post-commit script that calls make to generate the HTML:
(sphinx_playground)$ cat source/.git/hooks/post-commit
#!/bin/sh

PROJECT_DIR=/some/path/sphinx_playground

cd $PROJECT_DIR
make html
(sphinx_playground)$ chmod 744 source/.git/hooks/post-commit
  
Make sure the script is executable and then modify the file source/index.rst and commit the changes.
(sphinx_playground)$ cat index.rst
.. sphinx-playground documentation master file, created by
sphinx-quickstart on Sun May 31 23:01:49 2009.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

Welcome to sphinx-playground's documentation!
=============================================

I'm a headline! Look at me!
---------------------------

Contents:

.. toctree::
:maxdepth: 2

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

(sphinx_playground)$ git add index.rst
(sphinx_playground)$ git commit -m "added silly headline"
sphinx-build -b html -d build/doctrees   source build/html
Making output directory...
Running Sphinx v0.6.1
loading pickled environment... not found
building [html]: targets for 1 source files that are out of date
updating environment: 1 added, 0 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.

Build finished. The HTML pages are in build/html.
Created commit 43a506c: added silly headline
1 files changed, 3 insertions(+), 0 deletions(-)
(sphinx_playground)$
  
If you point your browser to /some/path/build/html/index.html you should see the new headline on the page. Updates work as well, just change the file and commit again:
(sphinx_playground)$ cat index.rst
.. sphinx-playground documentation master file, created by
sphinx-quickstart on Sun May 31 23:01:49 2009.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

Welcome to sphinx-playground's documentation!
=============================================

I'm a different headline! Look at me!
-------------------------------------

Contents:

.. toctree::
:maxdepth: 2

Indices and tables
==================

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

(sphinx_playground)$ git add index.rst
(sphinx_playground)$ git commit -m "changed silly headline"
sphinx-build -b html -d build/doctrees   source build/html
Running Sphinx v0.6.1
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.

Build finished. The HTML pages are in build/html.
Created commit 0d6ef5a: changed silly headline
1 files changed, 2 insertions(+), 2 deletions(-)
(sphinx_playground)$
  
You might wonder whether Sphinx regenerates the HTML for all source files, regardless of them being out of date or not. Luckily, Sphinx is smarter than that, as you can see below:
(sphinx_playground)$ cat newfile.rst
I'm just a new file
===================

Move along, there is nothing to see here.

(sphinx_playground)$ git add newfile.rst
(sphinx_playground)$ git commit -m "added new file"
sphinx-build -b html -d build/doctrees   source build/html
Running Sphinx v0.6.1
loading pickled environment... done
building [html]: targets for 0 source files that are out of date
updating environment: 1 added, 0 changed, 0 removed
reading sources... [100%] newfile
looking for now-outdated files... none found
pickling environment... done
checking consistency... /some/path/sphinx_playground/source/newfile.rst:: WARNING: document isn't included in any toctree
done
preparing documents... done
writing output... [100%] newfile
writing additional files... genindex search
copying static files... done
dumping search index... done
dumping object inventory... done
build succeeded, 1 warning.

Build finished. The HTML pages are in build/html.
Created commit 5a287cd: added new file
 1 files changed, 5 insertions(+), 0 deletions(-)
 create mode 100644 newfile.rst
(sphinx_playground)$
  
As you can see in the output, Sphinx correctly determined, that existing source files have not changed. Also note, that Sphinx emits a warning, saying that the new file is not mentioned in a table of contents. Well, that’s pretty much it. Obviously you can enhance the post commit hook in any way you see fit. Like uploading the generated HTML to a web server via FTP or ssh, for example.

2 comments:

  1. Simple CMS was designed to meet the needs of real business people. It was not designed by developers for other developers like the open source systems available and it was not based on somebody's perception of what business needed, based on their own experience

    ReplyDelete
  2. Thanks a stack! I get a few warnings, but that's probably something to do with the virtualenv.

    ReplyDelete