How to Write a Jinja2 Extension

Writing web pages from Markdown

Posted 2019-05-10 05:46:48 by Ronie Martinez

About EasyAsPy

Writing complex HTML pages is not an easy task particularly for beginner front-end developers and also, writers who does not have a background in HTML. Hypertext Markup Language or HTML is the standard markup language for creating web pages. EasyAsPy, for example, was written using HTML. For simple websites, it is effortless to write in HTML but as the webpages becomes complex, writing becomes tricky.

Below is an example of HTML

<!DOCTYPE html>
<html>
  <head>
    <title>This is a title</title>
  </head>
  <body>
    <p>Hello world!</p>
  </body>
</html>

Markdown, a HTML alternative

Markdown was created as an alternative to HTML. It is a lightweight markup language created specifically for writers with no prior experience in HTML. It features simple text syntax that are human-readable and easy to write.

Heading
=======

## Sub-heading

Paragraphs are separated
by a blank line.

Two spaces at the end of a line  
produces a line break.

Text attributes _italic_, 
**bold**, `monospace`.

Publishing Markdown-based web pages using Python web frameworks

Unfortunately, Python web frameworks does not support this feature out of the box. One way to do this is to use 3rd party libraries available in PyPI to convert Markdown to HTML. python-markdown2 is an example of a Markdown to HTML converter.

import markdown2
markdown2.markdown("*boo!*")  # or use `html = markdown_path(PATH)`
u'<p><em>boo!</em></p>\n'

Extending Jinja2 to support Markdown

Jinja2 is a popular and full featured template engine written in Python. It is commonly used with known web frameworks like Flask and Django.  Extending Jinja2 is very simple and gives developers more options for templating. For example we want to support Markdown using the following syntax:

{% markdown "ABOUT.md" %}

With the above syntax, we want to export the file ABOUT.md directly into our template. Another way that we want to do is to embed Markdown into our template.

{% markdown %}
# Header Title
{% endmarkdown %}

Writing a Jinja2 Extension

Extending Jinja2 can be started by subclassing the jinja2.ext.Extension class and implementing the parse method. To introduce a new tag, we need to initialize the tag attribute with a set object containing the string markdown. In this extension we will be using python-markdown2 to convert Markdown to HTML. If the parameter passed is a file name, the extension will look inside the directory, markdowns. UTF-8 is also supported by using the codecs module.

#!/usr/bin/env python
import codecs

from jinja2 import nodes, TemplateSyntaxError
from jinja2.ext import Extension
from jinja2.nodes import Const
from markdown2 import Markdown


class MarkdownExtension(Extension):
    tags = {'markdown'}

    def __init__(self, environment):
        super(MarkdownExtension, self).__init__(environment)
        environment.extend(
            markdown_dir='markdowns'
        )

    def parse(self, parser):
        line_number = next(parser.stream).lineno
        md_file = [Const('')]
        body = ''
        try:
            md_file = [parser.parse_expression()]
        except TemplateSyntaxError:
            body = parser.parse_statements(['name:endmarkdown'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_to_html', md_file), [], [], body).set_lineno(line_number)

    def _to_html(self, md_file, caller):
        if len(md_file):
            with codecs.open('{}/{}'.format(self.environment.markdown_dir, md_file), 'r', encoding='utf-8') as f:
                return Markdown().convert(f.read())
        else:
            return Markdown().convert(caller())

Using Markdown Extension

To use the extension, pass MarkdownExtension to extensions list parameter of jinja2.Environment.

#!/usr/bin/env python
from jinja2 import Environment

from markdown_extension import MarkdownExtension


def main():
    template = Environment(extensions=[MarkdownExtension]).from_string("""
    {% markdown "ABOUT.md" %}
    """)
    print(template.render())


if __name__ == '__main__':
    main()

Using Markdown Extension with Flask

To use the extension with Flask, simply add the extension to jinja_env.

#!/usr/bin/env python
from flask import Flask, render_template

application = Flask(__name__)
env = application.jinja_env
env.add_extension('markdown_extension.MarkdownExtension')


@application.route('/')
def index():
    return render_template('index.html')


if __name__ == '__main__':
    application.run(debug=True, threaded=True)

Markdown Extension in action

I updated the about page of this site, EasyAsPy by writing it in Markdown and using our MarkdownExtension. This extension also support unicode emojis which you can easily check by navigating to the /about page.

To wrap up

Writing web pages using Markdown speeds up site creation and removes the requirement of having high HTML skills. We should be aware that Markdown was only created as an alternative to HTML and does not support all features supported by HTML.

The example source code for this project is available on Github.

python flask markdown jinja2


Share