hgbook

diff 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 Jun 25 22:04:50 2006 -0700 (2006-06-25)
parents 906d9021f9e5
children 69d90ab9fd80
line diff
     1.1 --- a/en/examples/run-example	Sat Jun 24 17:42:40 2006 -0700
     1.2 +++ b/en/examples/run-example	Sun Jun 25 22:04:50 2006 -0700
     1.3 @@ -1,16 +1,36 @@
     1.4  #!/usr/bin/python
     1.5 +#
     1.6 +# This program takes something that resembles a shell script and runs
     1.7 +# it, spitting input (commands from the script) and output into text
     1.8 +# files, for use in examples.
     1.9  
    1.10  import cStringIO
    1.11  import os
    1.12  import pty
    1.13  import re
    1.14 +import shutil
    1.15  import sys
    1.16 +import tempfile
    1.17 +import time
    1.18  
    1.19 +def tex_escape(s):
    1.20 +    if '\\' in s:
    1.21 +        s = s.replace('\\', '\\\\')
    1.22 +    if '{' in s:
    1.23 +        s = s.replace('{', '\\{')
    1.24 +    if '}' in s:
    1.25 +        s = s.replace('}', '\\}')
    1.26 +    return s
    1.27 +        
    1.28  class example:
    1.29 +    shell = '/bin/bash'
    1.30 +    pi_re = re.compile('#\$\s*(name):\s*(.*)$')
    1.31 +    
    1.32      def __init__(self, name):
    1.33          self.name = name
    1.34  
    1.35      def parse(self):
    1.36 +        '''yield each hunk of input from the file.'''
    1.37          fp = open(self.name)
    1.38          cfp = cStringIO.StringIO()
    1.39          for line in fp:
    1.40 @@ -20,28 +40,54 @@
    1.41                  cfp.seek(0)
    1.42                  cfp.truncate()
    1.43          
    1.44 -    name_re = re.compile('#\s*name:\s*(.*)$')
    1.45 -    
    1.46      def status(self, s):
    1.47          sys.stdout.write(s)
    1.48          if not s.endswith('\n'):
    1.49              sys.stdout.flush()
    1.50  
    1.51 +    def drain(self, ifp, ofp):
    1.52 +        while True:
    1.53 +            s = ifp.read(4096)
    1.54 +            if not s: break
    1.55 +            if ofp: ofp.write(tex_escape(s))
    1.56 +        
    1.57      def run(self):
    1.58          ofp = None
    1.59 -        self.status('running %s ' % os.path.basename(self.name))
    1.60 -        for hunk in self.parse():
    1.61 -            m = self.name_re.match(hunk)
    1.62 -            if m:
    1.63 -                self.status('.')
    1.64 -                out = m.group(1)
    1.65 -                assert os.sep not in out
    1.66 -                if out:
    1.67 -                    ofp = open('%s.%s.out' % (self.name, out), 'w')
    1.68 +        basename = os.path.basename(self.name)
    1.69 +        self.status('running %s ' % basename)
    1.70 +        tmpdir = tempfile.mkdtemp(prefix=basename)
    1.71 +        try:
    1.72 +            for hunk in self.parse():
    1.73 +                # is this line a processing instruction?
    1.74 +                m = self.pi_re.match(hunk)
    1.75 +                if m:
    1.76 +                    pi, rest = m.groups()
    1.77 +                    if pi == 'name':
    1.78 +                        self.status('.')
    1.79 +                        out = rest
    1.80 +                        assert os.sep not in out
    1.81 +                        if out:
    1.82 +                            ofp = open('%s.%s.out' % (self.name, out), 'w')
    1.83 +                        else:
    1.84 +                            ofp = None
    1.85                  else:
    1.86 -                    ofp = None
    1.87 -            elif ofp: ofp.write(hunk)
    1.88 -        self.status('\n')
    1.89 +                    # it's something we should execute
    1.90 +                    cin, cout = os.popen4('cd %s; %s' % (tmpdir, hunk))
    1.91 +                    cin.close()
    1.92 +                    if ofp:
    1.93 +                        # first, print the command we ran
    1.94 +                        if not hunk.startswith('#'):
    1.95 +                            nl = hunk.endswith('\n')
    1.96 +                            hunk = ('$ \\textbf{%s}' %
    1.97 +                                    tex_escape(hunk.rstrip('\n')))
    1.98 +                            if nl: hunk += '\n'
    1.99 +                        ofp.write(hunk)
   1.100 +                    # then its output
   1.101 +                    self.drain(cout, ofp)
   1.102 +            self.status('\n')
   1.103 +        finally:
   1.104 +            os.wait()
   1.105 +            shutil.rmtree(tmpdir)
   1.106  
   1.107  def main(path='.'):
   1.108      args = sys.argv[1:]
   1.109 @@ -53,6 +99,7 @@
   1.110          if name == 'run-example' or name.startswith('.'): continue
   1.111          if name.endswith('.out') or name.endswith('~'): continue
   1.112          example(os.path.join(path, name)).run()
   1.113 +    print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
   1.114  
   1.115  if __name__ == '__main__':
   1.116      main()