comparison 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
comparison
equal deleted inserted replaced
3:906d9021f9e5 4:33a2e7b9978d
1 #!/usr/bin/python 1 #!/usr/bin/python
2 #
3 # This program takes something that resembles a shell script and runs
4 # it, spitting input (commands from the script) and output into text
5 # files, for use in examples.
2 6
3 import cStringIO 7 import cStringIO
4 import os 8 import os
5 import pty 9 import pty
6 import re 10 import re
11 import shutil
7 import sys 12 import sys
13 import tempfile
14 import time
8 15
16 def tex_escape(s):
17 if '\\' in s:
18 s = s.replace('\\', '\\\\')
19 if '{' in s:
20 s = s.replace('{', '\\{')
21 if '}' in s:
22 s = s.replace('}', '\\}')
23 return s
24
9 class example: 25 class example:
26 shell = '/bin/bash'
27 pi_re = re.compile('#\$\s*(name):\s*(.*)$')
28
10 def __init__(self, name): 29 def __init__(self, name):
11 self.name = name 30 self.name = name
12 31
13 def parse(self): 32 def parse(self):
33 '''yield each hunk of input from the file.'''
14 fp = open(self.name) 34 fp = open(self.name)
15 cfp = cStringIO.StringIO() 35 cfp = cStringIO.StringIO()
16 for line in fp: 36 for line in fp:
17 cfp.write(line) 37 cfp.write(line)
18 if not line.rstrip().endswith('\\'): 38 if not line.rstrip().endswith('\\'):
19 yield cfp.getvalue() 39 yield cfp.getvalue()
20 cfp.seek(0) 40 cfp.seek(0)
21 cfp.truncate() 41 cfp.truncate()
22 42
23 name_re = re.compile('#\s*name:\s*(.*)$')
24
25 def status(self, s): 43 def status(self, s):
26 sys.stdout.write(s) 44 sys.stdout.write(s)
27 if not s.endswith('\n'): 45 if not s.endswith('\n'):
28 sys.stdout.flush() 46 sys.stdout.flush()
29 47
48 def drain(self, ifp, ofp):
49 while True:
50 s = ifp.read(4096)
51 if not s: break
52 if ofp: ofp.write(tex_escape(s))
53
30 def run(self): 54 def run(self):
31 ofp = None 55 ofp = None
32 self.status('running %s ' % os.path.basename(self.name)) 56 basename = os.path.basename(self.name)
33 for hunk in self.parse(): 57 self.status('running %s ' % basename)
34 m = self.name_re.match(hunk) 58 tmpdir = tempfile.mkdtemp(prefix=basename)
35 if m: 59 try:
36 self.status('.') 60 for hunk in self.parse():
37 out = m.group(1) 61 # is this line a processing instruction?
38 assert os.sep not in out 62 m = self.pi_re.match(hunk)
39 if out: 63 if m:
40 ofp = open('%s.%s.out' % (self.name, out), 'w') 64 pi, rest = m.groups()
65 if pi == 'name':
66 self.status('.')
67 out = rest
68 assert os.sep not in out
69 if out:
70 ofp = open('%s.%s.out' % (self.name, out), 'w')
71 else:
72 ofp = None
41 else: 73 else:
42 ofp = None 74 # it's something we should execute
43 elif ofp: ofp.write(hunk) 75 cin, cout = os.popen4('cd %s; %s' % (tmpdir, hunk))
44 self.status('\n') 76 cin.close()
77 if ofp:
78 # first, print the command we ran
79 if not hunk.startswith('#'):
80 nl = hunk.endswith('\n')
81 hunk = ('$ \\textbf{%s}' %
82 tex_escape(hunk.rstrip('\n')))
83 if nl: hunk += '\n'
84 ofp.write(hunk)
85 # then its output
86 self.drain(cout, ofp)
87 self.status('\n')
88 finally:
89 os.wait()
90 shutil.rmtree(tmpdir)
45 91
46 def main(path='.'): 92 def main(path='.'):
47 args = sys.argv[1:] 93 args = sys.argv[1:]
48 if args: 94 if args:
49 for a in args: 95 for a in args:
51 return 97 return
52 for name in os.listdir(path): 98 for name in os.listdir(path):
53 if name == 'run-example' or name.startswith('.'): continue 99 if name == 'run-example' or name.startswith('.'): continue
54 if name.endswith('.out') or name.endswith('~'): continue 100 if name.endswith('.out') or name.endswith('~'): continue
55 example(os.path.join(path, name)).run() 101 example(os.path.join(path, name)).run()
102 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
56 103
57 if __name__ == '__main__': 104 if __name__ == '__main__':
58 main() 105 main()