This guide assumes you have python 3 and nikola installed.
See the Nikola Getting Started Guide for instructions to install it.
Environment with Nix
If you have Nix installed you can get the environment by running nix-shell
on the root of this project with this shell.nix
file:
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/d9448c95c5d557d0b2e8bfe13e8865e4b1e3943f.tar.gz") {} }:
with pkgs;
mkShell {
LOCALE_ARCHIVE_2_27 = "${glibcLocales}/lib/locale/locale-archive";
buildInputs = [
glibcLocales
python39
python39Packages.Nikola
];
shellHook = ''
export LC_ALL=en_US.UTF-8
'';
}
Setup a Nikola Site
In case you don't have a nikola site around or you want to play in a temporary project
we will create one here:
nikola init my-site
cd my-site
I answered with the default to all of them by hitting enter on all questions, feel free
to give different answers.
Create a New Plugin
In the base folder of the nikola site create a folder for your plugin, I will name mine
my_rst_roles
:
mkdir -p plugins/my_rst_roles
Create a configuration file with the plugin metadata and customize it at plugins/my_rst_roles/my_rst_roles.plugin
:
[Core]
Name = my_rst_roles
Module = my_rst_roles
[Nikola]
PluginCategory = CompilerExtension
Compiler = rest
MinVersion = 7.4.0
[Documentation]
Author = Mariano Guerra
Version = 0.1.0
Website = https://marianoguerra.org
Description = A set of custom reStructuredText roles
Create a python module inside the folder that will contain the plugin logic at plugins/my_rst_roles/my_rst_roles.py
:
from docutils import nodes
from docutils.parsers.rst import roles
from nikola.plugin_categories import RestExtension
from nikola.plugins.compile.rest import add_node
class Span(nodes.Inline, nodes.TextElement):
pass
def visit_span(self, node):
attrs = {}
self.body.append(self.starttag(node, "span", "", **attrs))
def depart_span(self, _node):
self.body.append("</span>")
add_node(Span, visit_span, depart_span)
class Plugin(RestExtension):
name = "my_rst_roles"
def set_site(self, site):
self.site = site
generic_docroles = {
"my-foo": (Span, "my-base my-foo"),
"my-bar": (Span, "my-base my-bar"),
}
for rolename, (nodeclass, classes) in generic_docroles.items():
generic = roles.GenericRole(rolename, nodeclass)
role = roles.CustomRole(rolename, generic, {"classes": [classes]})
roles.register_local_role(rolename, role)
return super(Plugin, self).set_site(site)
Create a New Post or Page to test it
Create a post:
nikola new_post -t "Nikola Restructured Text Roles Plugin Example Project"
Put some content in it:
echo 'Hi :my-foo:`hello`, :my-bar:`world`!' >> posts/nikola-restructured-text-roles-plugin-example-project.rst
Build the site
Check the Generated HTML
You can serve it with:
And open http://localhost:8000 and inspect it with the browser's developer tools, here's
a blog post friendly way with grep:
grep my-foo output/posts/nikola-restructured-text-roles-plugin-example-project/index.html
The output should be something like this:
Hi <span class="my-base my-foo">hello</span>, <span class="my-base my-bar">world</span>!</p>
More Advanced Transformations
I needed to process the children of the Span
node by myself and stop docutils from
walking the children between visit_span
and depart_span
, to do that here's a simplified
version:
class Span(nodes.Inline, nodes.TextElement):
def walk(self, visitor):
visitor.dispatch_visit(self)
# don't stop
return False
def walkabout(self, visitor):
visitor.dispatch_visit(self)
visitor.dispatch_departure(self)
# don't stop
return False
def visit_span(self, node):
cls = " ".join(node.get('classes', []))
child_str = " ".join([format_span_child(child) for child in node.children])
self.body.append("<span class=\"" + cls + "\">" + child_str)
def depart_span(self, _node):
self.body.append("</span>")
def format_span_child(node):
return node.astext()
Here are links to the implementations of some of the relevant functions:
A Real World Use Case
I did this to embed inline UI components in the documentation for instadeq, you can see it in action in This Walkthrough Guide if you scroll a little.
I still have to improve the style and add some missing roles but it's much
better than having to describe the position and look of ui components instead
of just showing them.
As usual, thanks to Roberto Alsina for Nikola
and for telling me how to get started with this plugin.