view en/examples/run-example @ 45:6b7b0339e7d6

Don't rerun examples unnecessarily.
author Bryan O'Sullivan <bos@serpentine.com>
date Sun, 23 Jul 2006 23:34:24 -0700
parents 5cee64874312
children a2f0b010d6e3
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 signal
import stat
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'
    prompt = '__run_example_prompt__\n'
    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 send(self, s):
        self.cfp.write(s)
        self.cfp.flush()

    def receive(self):
        out = cStringIO.StringIO()
        while True:
            s = self.cfp.readline().replace('\r\n', '\n')
            if not s or s == self.prompt:
                break
            out.write(s)
        return out.getvalue()
        
    def sendreceive(self, s):
        self.send(s)
        r = self.receive()
        if r.startswith(s):
            r = r[len(s):]
        return r
    
    def run(self):
        ofp = None
        basename = os.path.basename(self.name)
        self.status('running %s ' % basename)
        tmpdir = tempfile.mkdtemp(prefix=basename)
        rcfile = os.path.join(tmpdir, '.bashrc')
        rcfp = open(rcfile, 'w')
        print >> rcfp, 'PS1="%s"' % self.prompt
        print >> rcfp, 'unset HISTFILE'
        print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
        print >> rcfp, 'export LANG=C'
        print >> rcfp, 'export LC_ALL=C'
        print >> rcfp, 'export TZ=GMT'
        print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
        print >> rcfp, 'export HGRCPATH=$HGRC'
        print >> rcfp, 'cd %s' % tmpdir
        rcfp.close()
        pid, fd = pty.fork()
        if pid == 0:
            #os.execl(self.shell, self.shell)
            os.system('/bin/bash --noediting --noprofile --norc')
            sys.exit(0)
        self.cfp = os.fdopen(fd, 'w+')
        try:
            # setup env and prompt
            self.sendreceive('source %s\n\n' % rcfile)
            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
                elif hunk.strip():
                    # it's something we should execute
                    output = self.sendreceive(hunk)
                    if not ofp:
                        continue
                    # 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
                    ofp.write(tex_escape(output))
            self.status('\n')
            open(self.name + '.run', 'w')
        finally:
            try:
                output = self.sendreceive('exit\n')
                if ofp:
                    ofp.write(output)
                self.cfp.close()
            except IOError:
                pass
            os.kill(pid, signal.SIGTERM)
            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
        if name.endswith('.run'): continue
        pathname = os.path.join(path, name)
        st = os.lstat(pathname)
        if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
            example(pathname).run()
    print >> open(os.path.join(path, '.run'), 'w'), time.asctime()

if __name__ == '__main__':
    main()