view en/examples/run-example @ 4:33a2e7b9978d

Make it possible to include example input and output from real programs. Instead of having to cut and paste example text, the task is automated.
author Bryan O'Sullivan <bos@serpentine.com>
date Sun, 25 Jun 2006 22:04:50 -0700
parents 906d9021f9e5
children 69d90ab9fd80
line wrap: on
line source

#!/usr/bin/python
#
# This program takes something that resembles a shell script and runs
# it, spitting input (commands from the script) and output into text
# files, for use in examples.

import cStringIO
import os
import pty
import re
import shutil
import sys
import tempfile
import time

def tex_escape(s):
    if '\\' in s:
        s = s.replace('\\', '\\\\')
    if '{' in s:
        s = s.replace('{', '\\{')
    if '}' in s:
        s = s.replace('}', '\\}')
    return s
        
class example:
    shell = '/bin/bash'
    pi_re = re.compile('#\$\s*(name):\s*(.*)$')
    
    def __init__(self, name):
        self.name = name

    def parse(self):
        '''yield each hunk of input from the file.'''
        fp = open(self.name)
        cfp = cStringIO.StringIO()
        for line in fp:
            cfp.write(line)
            if not line.rstrip().endswith('\\'):
                yield cfp.getvalue()
                cfp.seek(0)
                cfp.truncate()
        
    def status(self, s):
        sys.stdout.write(s)
        if not s.endswith('\n'):
            sys.stdout.flush()

    def drain(self, ifp, ofp):
        while True:
            s = ifp.read(4096)
            if not s: break
            if ofp: ofp.write(tex_escape(s))
        
    def run(self):
        ofp = None
        basename = os.path.basename(self.name)
        self.status('running %s ' % basename)
        tmpdir = tempfile.mkdtemp(prefix=basename)
        try:
            for hunk in self.parse():
                # is this line a processing instruction?
                m = self.pi_re.match(hunk)
                if m:
                    pi, rest = m.groups()
                    if pi == 'name':
                        self.status('.')
                        out = rest
                        assert os.sep not in out
                        if out:
                            ofp = open('%s.%s.out' % (self.name, out), 'w')
                        else:
                            ofp = None
                else:
                    # it's something we should execute
                    cin, cout = os.popen4('cd %s; %s' % (tmpdir, hunk))
                    cin.close()
                    if ofp:
                        # first, print the command we ran
                        if not hunk.startswith('#'):
                            nl = hunk.endswith('\n')
                            hunk = ('$ \\textbf{%s}' %
                                    tex_escape(hunk.rstrip('\n')))
                            if nl: hunk += '\n'
                        ofp.write(hunk)
                    # then its output
                    self.drain(cout, ofp)
            self.status('\n')
        finally:
            os.wait()
            shutil.rmtree(tmpdir)

def main(path='.'):
    args = sys.argv[1:]
    if args:
        for a in args:
            example(a).run()
        return
    for name in os.listdir(path):
        if name == 'run-example' or name.startswith('.'): continue
        if name.endswith('.out') or name.endswith('~'): continue
        example(os.path.join(path, name)).run()
    print >> open(os.path.join(path, '.run'), 'w'), time.asctime()

if __name__ == '__main__':
    main()