hgbook

view en/examples/run-example @ 75:2bfa2499e971

Make run-example print better error messages when things go wrong.
author Bryan O'Sullivan <bos@serpentine.com>
date Wed Aug 30 00:11:17 2006 -0700 (2006-08-30)
parents 9604dd885616
children 773f4a9e7975
line source
1 #!/usr/bin/env 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 errno
9 import os
10 import pty
11 import re
12 import shutil
13 import signal
14 import stat
15 import sys
16 import tempfile
17 import time
19 def tex_escape(s):
20 if '\\' in s:
21 s = s.replace('\\', '\\\\')
22 if '{' in s:
23 s = s.replace('{', '\\{')
24 if '}' in s:
25 s = s.replace('}', '\\}')
26 return s
28 class example:
29 shell = '/usr/bin/env bash'
30 prompt = '__run_example_prompt__ '
31 pi_re = re.compile(r'#\$\s*(name):\s*(.*)$')
33 def __init__(self, name):
34 self.name = name
36 def parse(self):
37 '''yield each hunk of input from the file.'''
38 fp = open(self.name)
39 cfp = cStringIO.StringIO()
40 for line in fp:
41 cfp.write(line)
42 if not line.rstrip().endswith('\\'):
43 yield cfp.getvalue()
44 cfp.seek(0)
45 cfp.truncate()
47 def status(self, s):
48 sys.stdout.write(s)
49 if not s.endswith('\n'):
50 sys.stdout.flush()
52 def send(self, s):
53 while s:
54 count = os.write(self.cfd, s)
55 s = s[count:]
57 def receive(self):
58 out = cStringIO.StringIO()
59 while True:
60 try:
61 s = os.read(self.cfd, 1024)
62 except OSError, err:
63 if err.errno == errno.EIO:
64 return ''
65 raise
66 out.write(s)
67 s = out.getvalue()
68 if s.endswith(self.prompt):
69 return s.replace('\r\n', '\n')[:-len(self.prompt)]
71 def sendreceive(self, s):
72 self.send(s)
73 r = self.receive()
74 if r.startswith(s):
75 r = r[len(s):]
76 return r
78 def run(self):
79 ofp = None
80 basename = os.path.basename(self.name)
81 self.status('running %s ' % basename)
82 tmpdir = tempfile.mkdtemp(prefix=basename)
83 rcfile = os.path.join(tmpdir, '.bashrc')
84 rcfp = open(rcfile, 'w')
85 print >> rcfp, 'PS1="%s"' % self.prompt
86 print >> rcfp, 'unset HISTFILE'
87 print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
88 print >> rcfp, 'export LANG=C'
89 print >> rcfp, 'export LC_ALL=C'
90 print >> rcfp, 'export TZ=GMT'
91 print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
92 print >> rcfp, 'export HGRCPATH=$HGRC'
93 print >> rcfp, 'cd %s' % tmpdir
94 rcfp.close()
95 sys.stdout.flush()
96 sys.stderr.flush()
97 pid, self.cfd = pty.fork()
98 if pid == 0:
99 cmdline = ['/usr/bin/env', 'bash', '--noediting', '--noprofile',
100 '--norc']
101 try:
102 os.execv(cmdline[0], cmdline)
103 except OSError, err:
104 print >> sys.stderr, '%s: %s' % (cmdline[0], err.strerror)
105 sys.stderr.flush()
106 os._exit(0)
107 try:
108 try:
109 # eat first prompt string from shell
110 os.read(self.cfd, 1024)
111 # setup env and prompt
112 self.sendreceive('source %s\n' % rcfile)
113 for hunk in self.parse():
114 # is this line a processing instruction?
115 m = self.pi_re.match(hunk)
116 if m:
117 pi, rest = m.groups()
118 if pi == 'name':
119 self.status('.')
120 out = rest
121 assert os.sep not in out
122 if out:
123 ofp = open('%s.%s.out' % (self.name, out), 'w')
124 else:
125 ofp = None
126 elif hunk.strip():
127 # it's something we should execute
128 output = self.sendreceive(hunk)
129 if not ofp:
130 continue
131 # first, print the command we ran
132 if not hunk.startswith('#'):
133 nl = hunk.endswith('\n')
134 hunk = ('$ \\textbf{%s}' %
135 tex_escape(hunk.rstrip('\n')))
136 if nl: hunk += '\n'
137 ofp.write(hunk)
138 # then its output
139 ofp.write(tex_escape(output))
140 self.status('\n')
141 open(self.name + '.run', 'w')
142 except:
143 print >> sys.stderr, '(killed)'
144 os.kill(pid, signal.SIGKILL)
145 pid, rc = os.wait()
146 raise
147 else:
148 try:
149 output = self.sendreceive('exit\n')
150 if ofp:
151 ofp.write(output)
152 os.close(self.cfd)
153 except IOError:
154 pass
155 os.kill(pid, signal.SIGTERM)
156 pid, rc = os.wait()
157 if rc:
158 if os.WIFEXITED(rc):
159 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc)
160 elif os.WIFSIGNALED(rc):
161 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc)
162 return rc
163 finally:
164 shutil.rmtree(tmpdir)
166 def main(path='.'):
167 args = sys.argv[1:]
168 errs = 0
169 if args:
170 for a in args:
171 try:
172 st = os.lstat(a)
173 except OSError, err:
174 print >> sys.stderr, '%s: %s' % (a, err.strerror)
175 errs += 1
176 continue
177 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
178 if example(a).run():
179 errs += 1
180 else:
181 print >> sys.stderr, '%s: not a file, or not executable' % a
182 errs += 1
183 return errs
184 for name in os.listdir(path):
185 if name == 'run-example' or name.startswith('.'): continue
186 if name.endswith('.out') or name.endswith('~'): continue
187 if name.endswith('.run'): continue
188 pathname = os.path.join(path, name)
189 st = os.lstat(pathname)
190 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
191 if example(pathname).run():
192 errs += 1
193 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
194 return errs
196 if __name__ == '__main__':
197 sys.exit(main())