Change the code and see the result

An interesting idea came to me several days ago. I have to write C++ code generator (sourcing data from SQL database), so I installed Jinja2, sharpened text editors and then asked myself: „Wouldn't it be beautiful to see the result just as I type Python code, portion by portion? There's a use for two monitors: the first one displays coding, the second one keeps freshly generated result always ready for review.“

I liked and cherished the idea, and it was maturing: my workbox runs Gentoo Linux, and Linux kernel supports inotify — file change notification system, and there's Python bindings for it. Moreover, since we want not plain text but source code to get displayed, Pygments spring into mind instantly — why not to pygmentize output? And, of course, standard for modern *nix distribuition but glorious neverthe**less** to page the result.

So the idea is basically this: we start watcher-script, which watches (looping endlessly) all the source files we want, and when files are changed, reloads modules, re-runs key function, pygmentizes result string, pipes it to less's stdin through UNIX pipe and waits for next filechange event.

And here's the code:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from inotifyx import *
import sys, os, logging, subprocess

import templater

from pygments import highlight
from pygments.lexers import HtmlLexer
from pygments.formatters import TerminalFormatter

# Logging is helpful in debugging
LOG_FILENAME = '/tmp/debug_viewer.log'
logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)

# Initting inotifyx
fd = init()

watcher = add_watch(fd, 'templater.py', IN_MODIFY)

try:
    # Starting infinite loop
    while True:
        logging.debug('Starting iteration\n')
        try:
            reload(templater)
            string = templater.render_template()
            highlighted = highlight(string, HtmlLexer(), TerminalFormatter())
            logging.debug('Spawning less\n')
            less = subprocess.Popen(['less', '-'],
                                    stdin=subprocess.PIPE)
            logging.debug('less spawned, piping data\n')
            less.stdin.write(highlighted)
            less.stdin = sys.stdin
        except SyntaxError, e_text:
            print 'SyntaxError was raised'
            print SyntaxError
        logging.debug('Data piped, waiting for inotification\n')
        get_events(fd)
        logging.debug('Inotified, killing less\n')
        less.terminate()
        logging.debug('_______________________\n')

except KeyboardInterrupt:
    less.terminate()
    os.close(fd)

Treat log messages I used for debugging (tail -f in other terminal) as a comments (-:E

Few things worth mentioning:

  • there's two packages binding Python to inotify system: more complex and established pyinotify and newer and simpler inotifyx. I opted for second one, because of declared simplicity and speed
  • script stdin.writes() to spawned less process, not Popen.communicates(). This is because .communicate() method waits for returned data, and we don't need this
  • the trick I like most in this quite straightforward script is binding terminal STDIN to less'es one after piping in highlighted code. Before I did this, less was incontrollable, since keypresses didn't reach it

CLI script (refactored mercilessly) on BitBucket

Screencast

Comments

Comments powered by Disqus