hgbook

view en/examples/run-example @ 6:69d90ab9fd80

Really run example command sequences under a single shell.
Grotesque hackery involved. Bring strong stomachs.
author Bryan O'Sullivan <bos@serpentine.com>
date Mon Jun 26 10:15:49 2006 -0700 (2006-06-26)
parents 33a2e7b9978d
children 187702df428b 5ad16196cef4
line source
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.
7 import cStringIO
8 import os
9 import pty
10 import re
11 import shutil
12 import signal
13 import sys
14 import tempfile
15 import time
17 def tex_escape(s):
18 if '\\' in s:
19 s = s.replace('\\', '\\\\')
20 if '{' in s:
21 s = s.replace('{', '\\{')
22 if '}' in s:
23 s = s.replace('}', '\\}')
24 return s
26 class example:
27 shell = '/bin/bash'
28 prompt = '__run_example_prompt__\n'
29 pi_re = re.compile('#\$\s*(name):\s*(.*)$')
31 def __init__(self, name):
32 self.name = name
34 def parse(self):
35 '''yield each hunk of input from the file.'''
36 fp = open(self.name)
37 cfp = cStringIO.StringIO()
38 for line in fp:
39 cfp.write(line)
40 if not line.rstrip().endswith('\\'):
41 yield cfp.getvalue()
42 cfp.seek(0)
43 cfp.truncate()
45 def status(self, s):
46 sys.stdout.write(s)
47 if not s.endswith('\n'):
48 sys.stdout.flush()
50 def send(self, s):
51 self.cfp.write(s)
52 self.cfp.flush()
54 def receive(self):
55 out = cStringIO.StringIO()
56 while True:
57 s = self.cfp.readline().replace('\r\n', '\n')
58 if not s or s == self.prompt:
59 break
60 out.write(s)
61 return out.getvalue()
63 def sendreceive(self, s):
64 self.send(s)
65 r = self.receive()
66 if r.startswith(s):
67 r = r[len(s):]
68 return r
70 def run(self):
71 ofp = None
72 basename = os.path.basename(self.name)
73 self.status('running %s ' % basename)
74 tmpdir = tempfile.mkdtemp(prefix=basename)
75 rcfile = os.path.join(tmpdir, '.bashrc')
76 rcfp = open(rcfile, 'w')
77 print >> rcfp, 'PS1="%s"' % self.prompt
78 print >> rcfp, 'unset HISTFILE'
79 print >> rcfp, 'export LANG=C'
80 print >> rcfp, 'export LC_ALL=C'
81 print >> rcfp, 'export TZ=GMT'
82 print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
83 print >> rcfp, 'export HGRCPATH=$HGRC'
84 print >> rcfp, 'cd %s' % tmpdir
85 rcfp.close()
86 pid, fd = pty.fork()
87 if pid == 0:
88 #os.execl(self.shell, self.shell)
89 os.system('/bin/bash --noediting --noprofile --rcfile %s' % rcfile)
90 sys.exit(0)
91 self.cfp = os.fdopen(fd, 'w+')
92 try:
93 self.receive()
94 for hunk in self.parse():
95 # is this line a processing instruction?
96 m = self.pi_re.match(hunk)
97 if m:
98 pi, rest = m.groups()
99 if pi == 'name':
100 self.status('.')
101 out = rest
102 assert os.sep not in out
103 if out:
104 ofp = open('%s.%s.out' % (self.name, out), 'w')
105 else:
106 ofp = None
107 elif hunk.strip():
108 # it's something we should execute
109 output = self.sendreceive(hunk)
110 if not ofp:
111 continue
112 # first, print the command we ran
113 if not hunk.startswith('#'):
114 nl = hunk.endswith('\n')
115 hunk = ('$ \\textbf{%s}' %
116 tex_escape(hunk.rstrip('\n')))
117 if nl: hunk += '\n'
118 ofp.write(hunk)
119 # then its output
120 ofp.write(output)
121 self.status('\n')
122 finally:
123 try:
124 output = self.sendreceive('exit\n')
125 if ofp:
126 ofp.write(output)
127 self.cfp.close()
128 except IOError:
129 pass
130 os.kill(pid, signal.SIGTERM)
131 os.wait()
132 shutil.rmtree(tmpdir)
134 def main(path='.'):
135 args = sys.argv[1:]
136 if args:
137 for a in args:
138 example(a).run()
139 return
140 for name in os.listdir(path):
141 if name == 'run-example' or name.startswith('.'): continue
142 if name.endswith('.out') or name.endswith('~'): continue
143 example(os.path.join(path, name)).run()
144 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
146 if __name__ == '__main__':
147 main()