JP van Oosten

Smart quotes in Lektor

Jan 3, 2020


Add the mistune-smartypants plugin to your Lektor project to get curly quotes in all Markdown content! You can use the following command to do that:

lektor plugin add mistune-smartypants
lektor clean # you will want to rebuild all your pages :-)

Moving to Lektor

Recently, I moved my blog away from Jekyll to Lektor. What drew me to Lektor was the flexibility. It allows you to model pages in different ways. The docs calls this a "blueprint" for a page. This would allow me to add different types of pages easily.

Moving the content from the Jekyll blog to Lektor was not that difficult. I created a small script to convert all YAML front matter to the different fields for the blog-post model. Each blog-post now has its own directory (allowing you to add, e.g., attachments) and a file. Each field of the model is added to this file and separated with --- (three dashes). For example:

title: Smart quotes in Lektor
pub_date: 2020-01-03
author: Jean-Paul
Recently, I moved my blog away from Jekyll [...]

Smart quotes?

The only thing that I felt was lacking was the smart quotes. This feature of Jekyll converted the straight quotes (' and ") into "curly quotes" (e.g., the ” counterparts). Even though this feature is missing from Lektor by default, there is an existing package called smartypants. Another bit of flexibility allowed me to integrate smartypants into my blog: Plugins.

The plugin

I started a new plugin with lektor dev new-plugin and answered the necessary prompts. This generated a structure under the packages directory.

I declared smartypants as a dependency in with the following lines (I added them right after the version-line):


The plugin, is quite simple:

from lektor.pluginsystem import Plugin
import smartypants
import jinja2

class MistuneSmartypantsPlugin(Plugin):
    name = 'mistune-smartypants'
    description = u'Adds curly quotes to your markdown paragraphs and headings.'

    def on_markdown_config(self, config, **extra):
        """Adds a mixin to the Mistune parser to add curly quotes"""
        class SmartyPantsMixin(object):
            def paragraph(ren, text):
                return super().paragraph(smartypants.smartypants(text))
            def header(self, text, level, raw=None):
                return super().header(smartypants.smartypants(text), level, raw)

    def on_setup_env(self, **extra):
        """Adds the `smartypants` filter to all jinja2 templates"""
        def smartypants_filter(text):
            return jinja2.Markup(smartypants.smartypants(jinja2.escape(text)))
        self.env.jinja_env.filters['smartypants'] = smartypants_filter

The file defines the plugin class with two methods: one that does its magic by default on all markdown paragraphs and headings (on_markdown_config), and one that adds the smartypants filter to templates so that you can also use it in parts that are not rendered by mistune (on_setup_env).

The plugin is available through pypi1: and therefore through the Lektor plugin system.

If you like the plugin, any feedback is appreciated!

  1. To get it published, I had to install the wheel package with pip install wheel, and create a $HOME/.pypirc file, like in this comment on a setuptools issue (just leave your password out, and it will ask it on the command line).