Markdown and web.py Tutorial

February 24, 2012

This is a short tutorial which describes how to build a simple web.py application which serves a directory structure of Markdown-formatted text files as HTML using Python-Markdown.

First, you need to have web.py and Python-Markdown installed. On Debian Linux, this is as simple as typing

sudo apt-get install python-markdown python-webpy

With MacPorts, it’s a little more difficult (see this page for more details):

sudo port install python27 py27-markdown
sudo port select --set python python27
sudo easy_install web.py

The second command above makes the MacPorts version of Python 2.7 the default Python on the system.

Now, the directory structure for the app will be as follows:

webpy-markdown
|- app.py
|- pages
|  |- index.txt
|  `- other.txt
`- templates
   `- page.html

The Python script app.py contains the application code itself, the pages directory contains two Markdown-formatted text files, and the templates directory contains a single HTML page template. All of these files used in the tutorial are available in a tarball: webpy-markdown.tar.gz.

Let’s start with the app. First, we import the web.py and Python-Markdown modules:

import web
import markdown

Then, as is standard with web.py apps, we define urls, mapping requested URLs to the Python classes which handle them. In this case, we map everything to the page class:

# URLs: map everything to the page class
urls = (
    '/(.*)', 'page',
)

Next we create the template renderer and application instances, telling web.py that the templates are stored in the templates/ directory and passing along the URL mapping from above:

# Templates are found in the templates directory
render = web.template.render('templates')

# Application
app = web.application(urls, globals())

Since our app will need to convert Markdown to HTML, we also create an instance of Python-Markdown called md:

# Markdown
md = markdown.Markdown(output_format='html4')

Finally, we create the page class, which will have a single GET method to handle HTTP GET requests. We must map the given URL to a text file in the pages/ directory, load that file, convert the content to HTML, and serve the rendered template. When the

class page:
    def GET(self, url):
        # Handle index pages: path/ maps to path/index.txt
        if url == "" or url.endswith("/"):
            url += "index"

        # Each URL maps to the corresponding .txt file in pages/
        page_file = 'pages/%s.txt' %(url)

        # Try to open the text file, returning a 404 upon failure
        try:
            f = open(page_file, 'r')
        except IOError:
            return web.notfound()

        # Read the entire file, converting Markdown content to HTML
        content = f.read()
        content = md.convert(content)

        # Render the page.html template using the converted content
        return render.page(content)

There are a couple of notable things here. First, we handle index pages in a special way, mapping URLs like /foo/ to /foo/index.txt. Second, if the file corresponding to the requested URL cannot be opened, then we return a 404 error via web.notfound(). Finally, the last line renders the template stored in the file named page.html.

Lastly, as always, we want to run the application if this file is being invoked as an executable.

if __name__ == '__main__':
    app.run()

The template file is very simple, and is listed below. The first line indicates that the render.<templatename> function must be called with a single argument, content. This is substituted in the body in place of $:content (without escaping the HTML, due to the colon).

$def with (content)
<!doctype html>
<html>
  <head>
    <title>Markdown Website</title>
  </head>
  <body>
    $:content
  </body>
</html>

Now, create a file called pages/index.txt with some Markdown content, such as the following:

# Markdown Website

This file is `pages/index.txt`, converted to HTML using Pyton-Markdown.

Here is a link to [another page](other).

Finally, to start the server, type the following:

python app.py

Then, open http://localhost:8080/ in your browser and you should see something like the following:

Markdown web.py app in browser
Markdown web.py app in browser