Using rst for presentations

September 12, 2010 at 06:03 PM | categories: Programming | View Comments


Presentations are hard

Not even just giving them, but the process of taking an idea and putting it down in a concise and clear manner, that will be simple to show and distribute.

I am not going to make a claim that I am good at giving or writing up presentations, but I do think I have a pretty slick setup going for making them simple to show and distribute with minimal effort.

reStructuredText

People may be familiar with it already, but it's a markup language that's well supported in python and used in the Sphinx project. It fits my brain well (in most places) and only requires a text editor.

Since it comes with some tools to take a simple plain text document and transform it into other well known formats (latex, html, and pdf), caused me to use it in a number of projects and for notes without much friction.

How I use reST

I mentioned in a previous post about my setup and how I leverage fabric to build and post my presentations, but I didn't really get too much into the specifics of how the rst document was formatted, and built.

I write a presentation in rst just like I would write any other document in rst. The goal I had was to write once, and have that document change into other formats without an issue. I do leverage a few classes and directives that aren't in the normal rst toolbox, to get my presentations just so. But these aren't out of line, and after a tweak or two in my pipeline don't break the other formats I build to.

s5

S5 is a slide show format based entirely on XHTML, CSS, and JavaScript.

And rst2s5 takes a reStructuredText document and complies it into the corresponding s5 representation. Giving back a plain html page with some JavaScript magic that is simple to post and host.

No need for server side scripting, or fancy Apache/lighttpd/nginx setups or any need for proxies or their kin. So using s5 alone will give me the goal of simple to show, since I can post a presentation and have accesses to it anywhere there is internet and a browser. I can even keep a copy on a thumb drive in case the internet dies, and browse the slides locally.

reST with s5

To meet the last part of my goal, I have to have a simple distribution medium. For a presentation that is a pdf. It's akin to a paper, even though it is much more broken down and split up into slides. Leveraging the handout class that the s5 and pdf converters from rst know, I am able to have parts of the presentation invisible in slide form, and show up only when the presentation is expanded, or complied into a pdf.

eg

=========
GNU tools
=========
----------------------------
*mostly for text processing*
----------------------------

.. class:: right

    `Morgan Goose http://morgangoose.com`
    January 2010

.. class:: handout

    This work is licensed under the Creative Commons
    Attribution-Noncommercial-Share Alike 3.0 United States License.
    To view a copy of this license, visit
    http://creativecommons.org/licenses/by-nc-sa/3.0/us/ or send a letter
    to Creative Commons, 171 Second Street, Suite 300, San Francisco,
    California, 94105, USA.

So the above will show my name on the first slide, but not the license. In the pdf though that will all be on the first page. More tips on the s5 rst meshing can be found on the docutils site's section for slide-shows

Code blocks

Normal reST had code directives that will differentiate the code, and in most instances (Sphinx/Trac) will attempt to highlight the code accordingly. I ran into issues here because rst2pdf and rst2s5 had different ideas on what these should be named and neither really was highlighting the code. After searching a bit I found that pygments, a code highlighter in python, already had some docutils hooks that they mention on their site.

Using that as a stepping stone I added in code, code, and source code, directives to use pygments for the code they contained. In my presentations though I made sure to only use code because this is the directive that rst2pdf is expecting when it goes to format the document.

After that my code goes through the ringer as shown in the post I gave for fabric, in the line executing the rst-directive.py file and passing in the pygments css for the theme that I prefer.

the final rst-directive.py looks like this though:

# -*- coding: utf-8 -*-
"""
    The Pygments reStructuredText directive
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    This fragment is a Docutils_ 0.5 directive that renders source code
    (to HTML only, currently) via Pygments.

    To use it, adjust the options below and copy the code into a module
    that you import on initialization.  The code then automatically
    registers a ``sourcecode`` directive that you can use instead of
    normal code blocks like this::

        .. sourcecode-block:: python

            My code goes here.

    If you want to have different code styles, e.g. one with line numbers
    and one without, add formatters with their names in the VARIANTS dict
    below.  You can invoke them instead of the DEFAULT one by using a
    directive option::

        .. sourcecode-block:: python
            :linenos:

            My code goes here.

    Look at the `directive documentation`_ to get all the gory details.

    .. _Docutils: http://docutils.sf.net/
    .. _directive documentation:
       http://docutils.sourceforge.net/docs/howto/rst-directives.html

    :copyright: Copyright 2006-2009 by the Pygments team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

# Options
# ~~~~~~~

# Set to True if you want inline CSS styles instead of classes
INLINESTYLES = False
STYLE = "fruity"

from pygments.formatters import HtmlFormatter

# The default formatter
DEFAULT = HtmlFormatter(noclasses=INLINESTYLES, style=STYLE)

# Add name -> formatter pairs for every variant you want to use
VARIANTS = {
    'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=False),
}


from docutils import nodes
from docutils.parsers.rst import directives, Directive

from pygments import highlight
from pygments.lexers import get_lexer_by_name, TextLexer

class Pygments(Directive):
    """ Source code execution.
    """
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = dict([(key, directives.flag) for key in VARIANTS])
    has_content = True

    def run(self):
        self.assert_has_content()
        try:
            lexer = get_lexer_by_name(self.arguments[0])
        except ValueError:
            # no lexer found - use the text one instead of an exception
            lexer = TextLexer()
        # take an arbitrary option if more than one is given
        formatter = self.options and VARIANTS[self.options.keys()[0]] or DEFAULT

        print >>open('pygments.css', 'w'), formatter.get_style_defs('.highlight')
        parsed = highlight(u'\n'.join(self.content), lexer, formatter)
        return [nodes.raw('', parsed, format='html')]

directives.register_directive('sourcecode', Pygments)
directives.register_directive('code', Pygments)
directives.register_directive('code', Pygments)

from docutils.core import publish_cmdline, default_description

description = ('Generates S5 (X)HTML slideshow documents from standalone '
               'reStructuredText sources.  ' + default_description)

publish_cmdline(writer_name='s5', description=description)

And in combination with my fabric setup I can make new posts, publish to html and pdf, and republish with relative ease:

$ fab new:new_stuff
$ vim new_stuff/new_stuff.rst
$ fab upload:new_stuff






How fabric gets it right

February 11, 2010 at 12:56 AM | categories: Tools, Programming, Linux | View Comments


I like fabric. A lot.

Its a easy to use tool that continually makes my life simpler, and my projects smarter and more automated. Not much out there can really say that. At least nothing I use daily, without noticing, and dependably.

I used to use vellum, and that did what I needed. But fabric being under active development, and getting new features each version it seems is a huge plus. That and it does the network stuff for you, along with the nitty gritty.

Recently I have been giving presentations to the Ohio State University's Open Source Club about gnu tools, python tools, and soon some cli apps. Fabric really helped make this simple for me to get a whole system down for making and uploading these.

I made all of those presentations in restrctured text, and compiled them into their final formats. All of which was scripted in fabric. I became really attached to ReST after getting introduced to it watching Catherine Devlin give a talk about restructured text at Ohio Linux Fest. I ended up finding a cool rst2s5 command that makes nice presentations and with a little tweaking it now also has syntax highlighted code blocks, and can make nice pdfs.

In starting to use fabric you'll notice the basic idea is that you'd make a fabfile that works a lot like a Makefile or a SConstruct file would, with make and scons respectively. You'll make calls with the fab command in the directory the fabfile is located and it will supply the targets.

Below in this example, two targets are made, pack and deploy. The pack target will just makes a tarball, using the local function fabric provides. The deploy target calls pack to make this tarball, then using the put function will place the tarball into the tmp directory, then change into the web dir provided, and extract the archive. It knows automaticly to do this to both hosts I provided, and since I am using an ssh key does all this trickery autonomously.

fabfile.py:
from fabric.api import *

env.user = 'username'
env.hosts = ['host1.com', 'host2.com']

def pack():
    local('tar czf /tmp/project_foo.tgz project_foo/', capture=False)

def deploy():
    pack()
    put('/tmp/project_foo.tgz', '/tmp/')

    with cd('/var/www/foo/'):
        run('tar xzf /tmp/project_foo.tgz')

Fabric can do a lot more than just deploy

It's docs have a lot of detail, and explain most everything well. A last example of some a cool fabric config would be the one I use to publish my presentations to this site.

fabfile.py:
from fabric.api import *

env.roledefs = {
    'production': ["morgangoose.com"],
    }


def setup_vars(project):
    global presentation
    global presentation_archive
    global rst_source
    global pdf

    project = project.strip("/")
    presentation = project
    presentation_archive = "%s.tar.gz" % presentation
    rst_source = "%s.rst" % presentation
    pdf = "%s.pdf" % presentation

@roles('production')
def upload(project):
    env.user = "username"
    p_dir = "/var/www/html/p/"

    package(project)
    put(presentation_archive, p_dir)
    put("%s/%s" % (presentation, pdf), p_dir)
    with cd(p_dir):
        run("rm -rf %s/" % presentation)
        run("tar zxvf %s" % presentation_archive)

    local("rm -f %s" % presentation_archive)

def package(project):
    setup_vars(project)
    make_presentation()
    local("tar zcvf %s %s" % (presentation_archive, presentation))

def make_presentation():
    #PDF first
    local("rst2pdf %s/%s -o %s/%s" % (
        presentation, rst_source, presentation, pdf, ))

    #Then s5 html presentation
    local("python rst-directive.py \
            --stylesheet=pygments.css \
            --theme=small-black \
            --quiet \
            %s/%s > %s/index.html" % (
                presentation, rst_source, presentation, ))

def new(project):
    setup_vars(project)
    local("mkdir -p %s/{,files}" % presentation)
    local("cp -R ui %s/" % presentation)
    local("touch %s/%s" % (presentation, rst_source))

This has some more complicated bits, where it uses the role decorator to specify only to use the hosts listed in the production role definitions.

It also takes advantage of an awesome feature I didn't know fabric had where, one can send arguments to a fabric target. So the project parameter in the targets here can be, and is, supplied via the command line.

For example

I used this to deploy the updates to my most recent presentation:

$ fab upload:tool_oriented_python

That's telling fabric to run the upload target, and send the string "tool_oriented_python" as an argument to the function.

If you forget the targets you have just do:

$ fab -l






« Previous Page -- Next Page »