hgbook

view en/examples/run-example @ 83:b476081a9c04

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