The sphinx website for your project

Static site generator, I love and hate them at the sametime. They really render your markdowns into a beautiful website without need of knowing web technologies. Then there are the constant hustles once you try to customize it, change a theme, adding different functionalities or upgrade. It is YOUR website, you want it look different than others, right. At some point, whether you like it or not, the SSG users will take the role of website designers by tangling with CSS.

This site, in particular, is generated by pelican, a python based SSG. I choosed it because I am comfortable with python. Pelican was among the top tier candidates. I was happy with it at the first, you get a Makefile to build and publish your website. But after a while when you come back, your python virtualenv was broken... I can't complain that much really, I've been using this for several years now, usually satisfied.

Today I am here to address another generator, the sphinx-doc. To my feeling, it is rather a frankenstein style SSG. I use it generate the Taiwins website. It is all powerful when you want to host the documents of your project, but you don't like the doxygen style. In my setup, I took the doxyrest route (didn't get breathe to work).

Sphinx is good for hosting documentations. We have an index page. a navbar that direct us to documentations, external links like code repo. It was simple and perfect. The new challenge here is that I want to have a blog system inside for since like release notes and dev blogs. In this your website may be more "alive". Luckily someone already did it. The ABlog extension is specfic for that.

Doxygen Stage

Doxygen scans EVERYING other than generates document from source based on Makefile. It is sort of a compiler of its own so carefully configure your EXCLUDE_PATTERNS in your Doxyfile. Doxygen also collects your Markdowns if there it finds it and append them in the "related pages section", so you may want to exclude them somehow. I just choose to document in ReStructuredText so doxygen doesn't read them. And finally, for sphinx to work with doxygen, set GENERATE_XML = YES in the Doxyfile.

Doxyrest Stage

Doxyrest takes XMLs generated from Doxygen and output into Sphinx RSTs(Sphinx RST is a special dialect which use many non standard directives like :ref: that usually not available outside). It also requires a config called doxyrest-config.lua, you give an input xml(the index.xml generated by doxygen) and it outputs RSTs with an index.rst. Doxyrest is rather straightforward but there are many options you can pass in.

Sphinx Stage

Now we have a handful of RST documents. The index.rst from doxyrest is the document intro page so you may want to rename it to something else. Then we need to work with sphinx configuration. This is certainly a beast of its own. But note that we need to enable some extensions here :

extensions = [
    'doxyrest',
    'cpplexer',
    'ablog',
    'sphinx.ext.intersphinx'
]

doxyrest takes doxyrest and cpplexer and ablog requires ablog and intersphinx.

Before we finally run sphinx-build, there are something to note here, sphinx takes a unified template, namely layout.html unless you specify the html_additional_pages option in conf.py. It sort of works for a few pages like index.rst and about.rst. But since we uses ablog extension we don't want to list all the posts there. Note that ablog also generates a handful htmls like archives, postlist, etc. My solution right now is pretty much a hack. I have my own layout.html which extends the theme. Inside, an ugly if-else statement is present:

{% if pagename == "index" %}
    {% include 'master.html' %}
      {% elif pagename == posts or pagename in ablog %}
        <!-- apparently the inheritance screwed up the include, we have to do this way -->
        <div class="row">
      <div class="{{ bs_span_prefix }}2"></div>
      <div class="body {{ bs_span_prefix }}{{ bs_content_width }} content" role="main">{% block body %}{% endblock %}</div>
      <div class="{{ bs_span_prefix }}2"></div>
    </div>
      {% else %}
        {% include 'document.html' %}
{% endif %}

The first thing to notice is that I have three different templates:

  1. for index.html, use master.html
  2. the documents, uses document.html.
  3. The blog posts and post list. The condition pagename in ablog will pick out the blog posts for us and pagename == posts picks the post list.

The second thing your probably notice is that why blog posts are not using include? The reason is rather awkward. The ablog templates is actually the sub-template of us. If we uses an include, the jinja sub-templates doesn't seem to read what is inside. But again, this is a hack.

In Conclusion

I have to say, if you want to make your project a website with documentations and custom pages. Sphinx is pretty much the only choice. And it also has a blogging extension for posting more frequent contents. On the other hand, this pipeline is long and slow compare to other SSGs, configurations is two times the work. As what they say, you can never have all the good eggs in one basket.