hgbook

view en/examples/run-example @ 124:c9aad709bd3a

Document the backout command.
author Bryan O'Sullivan <bos@serpentine.com>
date Tue Dec 26 13:08:20 2006 -0800 (2006-12-26)
parents 5b80c922ebdd
children 7b5894fffc37
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 HGMERGE=merge'
139 print >> rcfp, 'export LANG=C'
140 print >> rcfp, 'export LC_ALL=C'
141 print >> rcfp, 'export TZ=GMT'
142 print >> rcfp, 'export HGRC="%s/.hgrc"' % tmpdir
143 print >> rcfp, 'export HGRCPATH=$HGRC'
144 print >> rcfp, 'cd %s' % tmpdir
145 rcfp.close()
146 sys.stdout.flush()
147 sys.stderr.flush()
148 self.pid, self.cfd = pty.fork()
149 if self.pid == 0:
150 cmdline = ['/usr/bin/env', 'bash', '--noediting', '--noprofile',
151 '--norc']
152 try:
153 os.execv(cmdline[0], cmdline)
154 except OSError, err:
155 print >> sys.stderr, '%s: %s' % (cmdline[0], err.strerror)
156 sys.stderr.flush()
157 os._exit(0)
158 self.poll.register(self.cfd, select.POLLIN | select.POLLERR |
159 select.POLLHUP)
161 prompts = {
162 '': '',
163 self.ps1: '$',
164 self.ps2: '>',
165 }
167 try:
168 try:
169 # eat first prompt string from shell
170 self.read()
171 # setup env and prompt
172 ps, output = self.sendreceive('source %s\n' % rcfile)
173 for hunk in self.parse():
174 # is this line a processing instruction?
175 m = self.pi_re.match(hunk)
176 if m:
177 pi, rest = m.groups()
178 if pi == 'name':
179 self.status('.')
180 out = rest
181 assert os.sep not in out
182 if out:
183 ofp = open('%s.%s.out' % (self.name, out), 'w')
184 else:
185 ofp = None
186 elif hunk.strip():
187 # it's something we should execute
188 newps, output = self.sendreceive(hunk)
189 if not ofp:
190 continue
191 # first, print the command we ran
192 if not hunk.startswith('#'):
193 nl = hunk.endswith('\n')
194 hunk = ('%s \\textbf{%s}' %
195 (prompts[ps],
196 tex_escape(hunk.rstrip('\n'))))
197 if nl: hunk += '\n'
198 ofp.write(hunk)
199 # then its output
200 ofp.write(tex_escape(output))
201 ps = newps
202 self.status('\n')
203 open(self.name + '.run', 'w')
204 except:
205 print >> sys.stderr, '(killed)'
206 os.kill(self.pid, signal.SIGKILL)
207 pid, rc = os.wait()
208 raise
209 else:
210 try:
211 ps, output = self.sendreceive('exit\n')
212 if ofp:
213 ofp.write(output)
214 os.close(self.cfd)
215 except IOError:
216 pass
217 os.kill(self.pid, signal.SIGTERM)
218 pid, rc = os.wait()
219 if rc:
220 if os.WIFEXITED(rc):
221 print >> sys.stderr, '(exit %s)' % os.WEXITSTATUS(rc)
222 elif os.WIFSIGNALED(rc):
223 print >> sys.stderr, '(signal %s)' % os.WTERMSIG(rc)
224 return rc
225 finally:
226 shutil.rmtree(tmpdir)
228 def main(path='.'):
229 opts, args = getopt.getopt(sys.argv[1:], 'v', ['verbose'])
230 verbose = False
231 for o, a in opts:
232 if o in ('-v', '--verbose'):
233 verbose = True
234 errs = 0
235 if args:
236 for a in args:
237 try:
238 st = os.lstat(a)
239 except OSError, err:
240 print >> sys.stderr, '%s: %s' % (a, err.strerror)
241 errs += 1
242 continue
243 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
244 if example(a, verbose).run():
245 errs += 1
246 else:
247 print >> sys.stderr, '%s: not a file, or not executable' % a
248 errs += 1
249 return errs
250 for name in os.listdir(path):
251 if name == 'run-example' or name.startswith('.'): continue
252 if name.endswith('.out') or name.endswith('~'): continue
253 if name.endswith('.run'): continue
254 pathname = os.path.join(path, name)
255 st = os.lstat(pathname)
256 if stat.S_ISREG(st.st_mode) and st.st_mode & 0111:
257 if example(pathname, verbose).run():
258 errs += 1
259 print >> open(os.path.join(path, '.run'), 'w'), time.asctime()
260 return errs
262 if __name__ == '__main__':
263 sys.exit(main())