hgbook

view en/examples/run-example @ 78:a893de25bc24

Add -v option to run-example, to assist with debugging example scripts.
author Bryan O'Sullivan <bos@serpentine.com>
date Mon Sep 04 14:20:05 2006 -0700 (2006-09-04)
parents 773f4a9e7975
children 53427f786a0f
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 getopt
10 import os
11 import pty
12 import re
13 import shutil
14 import signal
15 import stat
16 import sys
17 import tempfile
18 import time
20 tex_subs = {
21 '\\': '\\textbackslash{}',
22 '{': '\\{',
23 '}': '\\}',
24 }
26 def gensubs(s):
27 start = 0
28 for i, c in enumerate(s):
29 sub = tex_subs.get(c)
30 if sub:
31 yield s[start:i]
32 start = i + 1
33 yield sub
34 yield s[start:]
36 def tex_escape(s):
37 return ''.join(gensubs(s))
39 class example:
40 shell = '/usr/bin/env bash'
41 prompt = '__run_example_prompt__ '
42 pi_re = re.compile(r'#\$\s*(name):\s*(.*)$')
44 def __init__(self, name, verbose):
45 self.name = name
46 self.verbose = verbose
48 def parse(self):
49 '''yield each hunk of input from the file.'''
50 fp = open(self.name)
51 cfp = cStringIO.StringIO()
52 for line in fp:
53 cfp.write(line)
54 if not line.rstrip().endswith('\\'):
55 yield cfp.getvalue()
56 cfp.seek(0)
57 cfp.truncate()
59 def status(self, s):
60 sys.stdout.write(s)
61 if not s.endswith('\n'):
62 sys.stdout.flush()
64 def send(self, s):
65 if self.verbose:
66 print >> sys.stderr, '>', self.debugrepr(s)
67 while s:
68 count = os.write(self.cfd, s)
69 s = s[count:]
71 def debugrepr(self, s):
72 rs = repr(s)
73 limit = 60
74 if len(rs) > limit:
75 return ('%s%s ... [%d bytes]' % (rs[:limit], rs[0], len(s)))
76 else:
77 return rs
79 def receive(self):
80 out = cStringIO.StringIO()
81 while True:
82 try:
83 if self.verbose:
84 sys.stderr.write('< ')
85 s = os.read(self.cfd, 1024)
86 except OSError, err:
87 if err.errno == errno.EIO:
88 return ''
89 raise
90 if self.verbose:
91 print >> sys.stderr, self.debugrepr(s)
92 out.write(s)
93 s = out.getvalue()
94 if s.endswith(self.prompt):
95 return s.replace('\r\n', '\n')[:-len(self.prompt)]
97 def sendreceive(self, s):
98 self.send(s)
99 r = self.receive()
100 if r.startswith(s):
101 r = r[len(s):]
102 return r
104 def run(self):
105 ofp = None
106 basename = os.path.basename(self.name)
107 self.status('running %s ' % basename)
108 tmpdir = tempfile.mkdtemp(prefix=basename)
110 rcfile = os.path.join(tmpdir, '.hgrc')
111 rcfp = open(rcfile, 'w')
112 print >> rcfp, '[ui]'
113 print >> rcfp, "username = Bryan O'Sullivan <bos@serpentine.com>"
115 rcfile = os.path.join(tmpdir, '.bashrc')
116 rcfp = open(rcfile, 'w')
117 print >> rcfp, 'PS1="%s"' % self.prompt
118 print >> rcfp, 'unset HISTFILE'
119 print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
120 print >> rcfp, 'export LANG=C'
121 print >> rcfp, 'export LC_ALL=C'
122 print >> rcfp, 'export TZ=GMT'
123 print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
124 print >> rcfp, 'export HGRCPATH=$HGRC'
125 print >> rcfp, 'cd %s' % tmpdir
126 rcfp.close()
127 sys.stdout.flush()
128 sys.stderr.flush()
129 pid, self.cfd = pty.fork()
130 if pid == 0:
131 cmdline = ['/usr/bin/env', 'bash', '--noediting', '--noprofile',
132 '--norc']
133 try:
134 os.execv(cmdline[0], cmdline)
135 except OSError, err:
136 print >> sys.stderr, '%s: %s' % (cmdline[0], err.strerror)
137 sys.stderr.flush()
138 os._exit(0)
139 try:
140 try:
141 # eat first prompt string from shell
142 os.read(self.cfd, 1024)
143 # setup env and prompt
144 self.sendreceive('source %s\n' % rcfile)
145 for hunk in self.parse():
146 # is this line a processing instruction?
147 m = self.pi_re.match(hunk)
148 if m:
149 pi, rest = m.groups()
150 if pi == 'name':
151 self.status('.')
152 out = rest
153 assert os.sep not in out
154 if out:
155 ofp = open('%s.%s.out' % (self.name, out), 'w')
156 else:
157 ofp = None
158 elif hunk.strip():
159 # it's something we should execute
160 output = self.sendreceive(hunk)
161 if not ofp:
162 continue
163 # first, print the command we ran
164 if not hunk.startswith('#'):
165 nl = hunk.endswith('\n')
166 hunk = ('$ \\textbf{%s}' %
167 tex_escape(hunk.rstrip('\n')))
168 if nl: hunk += '\n'
169 ofp.write(hunk)
170 # then its output
171 ofp.write(tex_escape(output))
172 self.status('\n')
173 open(self.name + '.run', 'w')
174 except:
175 print >> sys.stderr, '(killed)'
176 os.kill(pid, signal.SIGKILL)
177 pid, rc = os.wait()
178 raise
179 else:
180 try:
181 output = self.sendreceive('exit\n')
182 if ofp:
183 ofp.write(output)
184 os.close(self.cfd)
185 except IOError:
186 pass
187 os.kill(pid, signal.SIGTERM)
188 pid, rc = os.wait()
189 if rc:
190 if os.WIFEXITED(rc):
191 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc)
192 elif os.WIFSIGNALED(rc):
193 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc)
194 return rc
195 finally:
196 shutil.rmtree(tmpdir)
198 def main(path='.'):
199 opts, args = getopt.getopt(sys.argv[1:], 'v', ['verbose'])
200 verbose = False
201 for o, a in opts:
202 if o in ('-v', '--verbose'):
203 verbose = True
204 errs = 0
205 if args:
206 for a in args:
207 try:
208 st = os.lstat(a)
209 except OSError, err:
210 print >> sys.stderr, '%s: %s' % (a, err.strerror)
211 errs += 1
212 continue
213 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
214 if example(a, verbose).run():
215 errs += 1
216 else:
217 print >> sys.stderr, '%s: not a file, or not executable' % a
218 errs += 1
219 return errs
220 for name in os.listdir(path):
221 if name == 'run-example' or name.startswith('.'): continue
222 if name.endswith('.out') or name.endswith('~'): continue
223 if name.endswith('.run'): continue
224 pathname = os.path.join(path, name)
225 st = os.lstat(pathname)
226 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
227 if example(pathname, verbose).run():
228 errs += 1
229 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
230 return errs
232 if __name__ == '__main__':
233 sys.exit(main())