Articles on this website before this one are all written in markdown format. I want to
write some articles in reStructuredText (rst) format but the default docutils
tool
that comes with Pelican has a flaw causing me to stay away from the rst format.
The problem is that the top title of an aritle (e.g. "Pelican Source Code and Plugin"
on this page) is rendered as h2
html tag, and section title (e.g. "Pelican Plugin"
section below) starts with h3
tag. It is designed this way since the start of the website.
In a markdown file, I can add two #
to specify a title as h2
. There is
no easy way to specify the heading levels in rst files.
The same problem is described in this question on github.
When I am reading Pelican source code, I find that the Pelican class in the
__init__.py
file has an init_plugins
method. This method calls each plugin’s
register function as shown below.
def init_plugins(self):
self.plugins = []
......
for plugin in self.settings['PLUGINS']:
.....
plugin.register()
self.plugins.append(plugin)
The Pelican plugin page has an existing plugin headerid
which implements a similar
function. It adds anchor tags to headings in rst files.
Google searching class names
in the headerid
plugin lead me to a very nice article
reStructureText(RST) Tutorial.
This article discusses the exact same heading problem I have with the rst format and
has code examples on how to solve it. It becomes easy for me to modify the code in
this article to a Pelican plugin which is very similar to headerid
. I name this new
Pelican plugin headinglower
which lowers the heading levels in rst files.
The headinglower
plugin has two files. The __init__.py
file only has one line of code.
# __init__.py
from .headinglower import *
The headinglower.py
file has the following code.
# headinglower.py
from pelican import readers, logger
from pelican.readers import PelicanHTMLTranslator
from pelican import signals
from docutils import nodes
def init_headinglower(sender):
logger.debug('Init Headinglower Plugin')
def register():
signals.initialized.connect(init_headinglower)
class ModPelicanHTMLTranslator(PelicanHTMLTranslator):
def visit_title(self, node):
"""Only 6 section levels are supported by HTML."""
close_tag = '</p>\n'
if isinstance(node.parent, nodes.topic):
self.body.append(
self.starttag(node, 'p', '', CLASS='topic-title'))
elif isinstance(node.parent, nodes.sidebar):
self.body.append(
self.starttag(node, 'p', '', CLASS='sidebar-title'))
elif isinstance(node.parent, nodes.Admonition):
self.body.append(
self.starttag(node, 'p', '', CLASS='admonition-title'))
elif isinstance(node.parent, nodes.table):
self.body.append(
self.starttag(node, 'caption', ''))
close_tag = '</caption>\n'
elif isinstance(node.parent, nodes.document):
self.body.append(self.starttag(node, 'h1', '', CLASS='title'))
close_tag = '</h1>\n'
self.in_document_title = len(self.body)
else:
assert isinstance(node.parent, nodes.section)
## revise here, comment out ( - 1 )
h_level = self.section_level + self.initial_header_level # - 1
atts = {}
if (len(node.parent) >= 2 and
isinstance(node.parent[1], nodes.subtitle)):
atts['CLASS'] = 'with-subtitle'
self.body.append(
self.starttag(node, 'h%s' % h_level, '', **atts))
atts = {}
if node.hasattr('refid'):
atts['class'] = 'toc-backref'
atts['href'] = '#' + node['refid']
if atts:
self.body.append(self.starttag({}, 'a', '', **atts))
close_tag = '</a></h%s>\n' % (h_level)
else:
close_tag = '</h%s>\n' % (h_level)
self.context.append(close_tag)
readers.PelicanHTMLTranslator = ModPelicanHTMLTranslator
The code add a method visit_title
to the PelicanHTMLTranslator class. It overrides a method
defined in HTMLTranslator
class of docutils.writers._html_base module.
Add the following settings to the pelicanconf.py
. Pelican will automatically load the
plugin.
PLUGIN_PATHS = ['plugin/', ]
PLUGINS=['headinglower',]
The above method works well and solves my problem. But there is a better and easier
way to solve the exact problem. Pelican has over 100 settings, and one
of them is DOCUTILS_SETTINGS
. It is described on the documentation page as:
Extra configuration settings for the docutils publisher (applicable only to reStructuredText). See Docutils Configuration settings for more details.
The RstReader
class in readers.py
file of Pelican source code has a method
_get_publisher
. It has the following lines of code.
extra_params = {'initial_header_level': '2',
'syntax_highlight': 'short',
'input_encoding': 'utf-8',
'language_code': self._language_code,
'halt_level': 2,
'traceback': True,
'warning_stream': StringIO(),
'embed_stylesheet': False}
user_params = self.settings.get('DOCUTILS_SETTINGS')
if user_params:
extra_params.update(user_params)
I can simply set the initial_header_level
value to 3 and the problem is solved.
Add the following settings in the pelicanconf.py
, the first section title heading will
become h3
. Note the article title heading level h2
is actually set in the
article.html
template file. I also comment out the two plugin settings
shown in the previous section.
DOCUTILS_SETTINGS = {'initial_header_level': '3', }
The rst file of this article is available on github. Click here to read the source file and click "Raw" button to see the text file.