hgbook

view en/examples/run-example @ 109:1b67dc96f27a

Snapshot of concepts chapter.
author Bryan O'Sullivan <bos@serpentine.com>
date Fri Nov 10 12:42:00 2006 -0800 (2006-11-10)
parents b476081a9c04
children c9aad709bd3a
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 select
14 import shutil
15 import signal
16 import stat
17 import sys
18 import tempfile
19 import time
21 tex_subs = {
22 '\\': '\\textbackslash{}',
23 '{': '\\{',
24 '}': '\\}',
25 }
27 def gensubs(s):
28 start = 0
29 for i, c in enumerate(s):
30 sub = tex_subs.get(c)
31 if sub:
32 yield s[start:i]
33 start = i + 1
34 yield sub
35 yield s[start:]
37 def tex_escape(s):
38 return ''.join(gensubs(s))
40 class example:
41 shell = '/usr/bin/env bash'
42 ps1 = '__run_example_ps1__ '
43 ps2 = '__run_example_ps2__ '
44 pi_re = re.compile(r'#\$\s*(name):\s*(.*)$')
46 timeout = 5
48 def __init__(self, name, verbose):
49 self.name = name
50 self.verbose = verbose
51 self.poll = select.poll()
53 def parse(self):
54 '''yield each hunk of input from the file.'''
55 fp = open(self.name)
56 cfp = cStringIO.StringIO()
57 for line in fp:
58 cfp.write(line)
59 if not line.rstrip().endswith('\\'):
60 yield cfp.getvalue()
61 cfp.seek(0)
62 cfp.truncate()
64 def status(self, s):
65 sys.stdout.write(s)
66 if not s.endswith('\n'):
67 sys.stdout.flush()
69 def send(self, s):
70 if self.verbose:
71 print >> sys.stderr, '>', self.debugrepr(s)
72 while s:
73 count = os.write(self.cfd, s)
74 s = s[count:]
76 def debugrepr(self, s):
77 rs = repr(s)
78 limit = 60
79 if len(rs) > limit:
80 return ('%s%s ... [%d bytes]' % (rs[:limit], rs[0], len(s)))
81 else:
82 return rs
84 timeout = 5
86 def read(self):
87 events = self.poll.poll(self.timeout * 1000)
88 if not events:
89 print >> sys.stderr, '[timed out after %d seconds]' % self.timeout
90 os.kill(self.pid, signal.SIGHUP)
91 return ''
92 return os.read(self.cfd, 1024)
94 def receive(self):
95 out = cStringIO.StringIO()
96 while True:
97 try:
98 if self.verbose:
99 sys.stderr.write('< ')
100 s = self.read()
101 except OSError, err:
102 if err.errno == errno.EIO:
103 return '', ''
104 raise
105 if self.verbose:
106 print >> sys.stderr, self.debugrepr(s)
107 out.write(s)
108 s = out.getvalue()
109 if s.endswith(self.ps1):
110 return self.ps1, s.replace('\r\n', '\n')[:-len(self.ps1)]
111 if s.endswith(self.ps2):
112 return self.ps2, s.replace('\r\n', '\n')[:-len(self.ps2)]
114 def sendreceive(self, s):
115 self.send(s)
116 ps, r = self.receive()
117 if r.startswith(s):
118 r = r[len(s):]
119 return ps, r
121 def run(self):
122 ofp = None
123 basename = os.path.basename(self.name)
124 self.status('running %s ' % basename)
125 tmpdir = tempfile.mkdtemp(prefix=basename)
127 rcfile = os.path.join(tmpdir, '.hgrc')
128 rcfp = open(rcfile, 'w')
129 print >> rcfp, '[ui]'
130 print >> rcfp, "username = Bryan O'Sullivan <bos@serpentine.com>"
132 rcfile = os.path.join(tmpdir, '.bashrc')
133 rcfp = open(rcfile, 'w')
134 print >> rcfp, 'PS1="%s"' % self.ps1
135 print >> rcfp, 'PS2="%s"' % self.ps2
136 print >> rcfp, 'unset HISTFILE'
137 print >> rcfp, 'export EXAMPLE_DIR="%s"' % os.getcwd()
138 print >> rcfp, 'export LANG=C'
139 print >> rcfp, 'export LC_ALL=C'
140 print >> rcfp, 'export TZ=GMT'
141 print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
142 print >> rcfp, 'export HGRCPATH=$HGRC'
143 print >> rcfp, 'cd %s' % tmpdir
144 rcfp.close()
145 sys.stdout.flush()
146 sys.stderr.flush()
147 self.pid, self.cfd = pty.fork()
148 if self.pid == 0:
149 cmdline = ['/usr/bin/env', 'bash', '--noediting', '--noprofile',
150 '--norc']
151 try:
152 os.execv(cmdline[0], cmdline)
153 except OSError, err:
154 print >> sys.stderr, '%s: %s' % (cmdline[0], err.strerror)
155 sys.stderr.flush()
156 os._exit(0)
157 self.poll.register(self.cfd, select.POLLIN | select.POLLERR |
158 select.POLLHUP)
160 prompts = {
161 '': '',
162 self.ps1: '$',
163 self.ps2: '>',
164 }
166 try:
167 try:
168 # eat first prompt string from shell
169 self.read()
170 # setup env and prompt
171 ps, output = self.sendreceive('source %s\n' % rcfile)
172 for hunk in self.parse():
173 # is this line a processing instruction?
174 m = self.pi_re.match(hunk)
175 if m:
176 pi, rest = m.groups()
177 if pi == 'name':
178 self.status('.')
179 out = rest
180 assert os.sep not in out
181 if out:
182 ofp = open('%s.%s.out' % (self.name, out), 'w')
183 else:
184 ofp = None
185 elif hunk.strip():
186 # it's something we should execute
187 newps, output = self.sendreceive(hunk)
188 if not ofp:
189 continue
190 # first, print the command we ran
191 if not hunk.startswith('#'):
192 nl = hunk.endswith('\n')
193 hunk = ('%s \\textbf{%s}' %
194 (prompts[ps],
195 tex_escape(hunk.rstrip('\n'))))
196 if nl: hunk += '\n'
197 ofp.write(hunk)
198 # then its output
199 ofp.write(tex_escape(output))
200 ps = newps
201 self.status('\n')
202 open(self.name + '.run', 'w')
203 except:
204 print >> sys.stderr, '(killed)'
205 os.kill(self.pid, signal.SIGKILL)
206 pid, rc = os.wait()
207 raise
208 else:
209 try:
210 ps, output = self.sendreceive('exit\n')
211 if ofp:
212 ofp.write(output)
213 os.close(self.cfd)
214 except IOError:
215 pass
216 os.kill(self.pid, signal.SIGTERM)
217 pid, rc = os.wait()
218 if rc:
219 if os.WIFEXITED(rc):
220 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc)
221 elif os.WIFSIGNALED(rc):
222 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc)
223 return rc
224 finally:
225 shutil.rmtree(tmpdir)
227 def main(path='.'):
228 opts, args = getopt.getopt(sys.argv[1:], 'v', ['verbose'])
229 verbose = False
230 for o, a in opts:
231 if o in ('-v', '--verbose'):
232 verbose = True
233 errs = 0
234 if args:
235 for a in args:
236 try:
237 st = os.lstat(a)
238 except OSError, err:
239 print >> sys.stderr, '%s: %s' % (a, err.strerror)
240 errs += 1
241 continue
242 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
243 if example(a, verbose).run():
244 errs += 1
245 else:
246 print >> sys.stderr, '%s: not a file, or not executable' % a
247 errs += 1
248 return errs
249 for name in os.listdir(path):
250 if name == 'run-example' or name.startswith('.'): continue
251 if name.endswith('.out') or name.endswith('~'): continue
252 if name.endswith('.run'): continue
253 pathname = os.path.join(path, name)
254 st = os.lstat(pathname)
255 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
256 if example(pathname, verbose).run():
257 errs += 1
258 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
259 return errs
261 if __name__ == '__main__':
262 sys.exit(main())