Mercurial > hgbook
comparison en/examples/run-example @ 4:33a2e7b9978d
Make it possible to include example input and output from real programs.
Instead of having to cut and paste example text, the task is automated.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Sun, 25 Jun 2006 22:04:50 -0700 |
parents | 906d9021f9e5 |
children | 69d90ab9fd80 |
comparison
equal
deleted
inserted
replaced
3:906d9021f9e5 | 4:33a2e7b9978d |
---|---|
1 #!/usr/bin/python | 1 #!/usr/bin/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. | |
2 | 6 |
3 import cStringIO | 7 import cStringIO |
4 import os | 8 import os |
5 import pty | 9 import pty |
6 import re | 10 import re |
11 import shutil | |
7 import sys | 12 import sys |
13 import tempfile | |
14 import time | |
8 | 15 |
16 def tex_escape(s): | |
17 if '\\' in s: | |
18 s = s.replace('\\', '\\\\') | |
19 if '{' in s: | |
20 s = s.replace('{', '\\{') | |
21 if '}' in s: | |
22 s = s.replace('}', '\\}') | |
23 return s | |
24 | |
9 class example: | 25 class example: |
26 shell = '/bin/bash' | |
27 pi_re = re.compile('#\$\s*(name):\s*(.*)$') | |
28 | |
10 def __init__(self, name): | 29 def __init__(self, name): |
11 self.name = name | 30 self.name = name |
12 | 31 |
13 def parse(self): | 32 def parse(self): |
33 '''yield each hunk of input from the file.''' | |
14 fp = open(self.name) | 34 fp = open(self.name) |
15 cfp = cStringIO.StringIO() | 35 cfp = cStringIO.StringIO() |
16 for line in fp: | 36 for line in fp: |
17 cfp.write(line) | 37 cfp.write(line) |
18 if not line.rstrip().endswith('\\'): | 38 if not line.rstrip().endswith('\\'): |
19 yield cfp.getvalue() | 39 yield cfp.getvalue() |
20 cfp.seek(0) | 40 cfp.seek(0) |
21 cfp.truncate() | 41 cfp.truncate() |
22 | 42 |
23 name_re = re.compile('#\s*name:\s*(.*)$') | |
24 | |
25 def status(self, s): | 43 def status(self, s): |
26 sys.stdout.write(s) | 44 sys.stdout.write(s) |
27 if not s.endswith('\n'): | 45 if not s.endswith('\n'): |
28 sys.stdout.flush() | 46 sys.stdout.flush() |
29 | 47 |
48 def drain(self, ifp, ofp): | |
49 while True: | |
50 s = ifp.read(4096) | |
51 if not s: break | |
52 if ofp: ofp.write(tex_escape(s)) | |
53 | |
30 def run(self): | 54 def run(self): |
31 ofp = None | 55 ofp = None |
32 self.status('running %s ' % os.path.basename(self.name)) | 56 basename = os.path.basename(self.name) |
33 for hunk in self.parse(): | 57 self.status('running %s ' % basename) |
34 m = self.name_re.match(hunk) | 58 tmpdir = tempfile.mkdtemp(prefix=basename) |
35 if m: | 59 try: |
36 self.status('.') | 60 for hunk in self.parse(): |
37 out = m.group(1) | 61 # is this line a processing instruction? |
38 assert os.sep not in out | 62 m = self.pi_re.match(hunk) |
39 if out: | 63 if m: |
40 ofp = open('%s.%s.out' % (self.name, out), 'w') | 64 pi, rest = m.groups() |
65 if pi == 'name': | |
66 self.status('.') | |
67 out = rest | |
68 assert os.sep not in out | |
69 if out: | |
70 ofp = open('%s.%s.out' % (self.name, out), 'w') | |
71 else: | |
72 ofp = None | |
41 else: | 73 else: |
42 ofp = None | 74 # it's something we should execute |
43 elif ofp: ofp.write(hunk) | 75 cin, cout = os.popen4('cd %s; %s' % (tmpdir, hunk)) |
44 self.status('\n') | 76 cin.close() |
77 if ofp: | |
78 # first, print the command we ran | |
79 if not hunk.startswith('#'): | |
80 nl = hunk.endswith('\n') | |
81 hunk = ('$ \\textbf{%s}' % | |
82 tex_escape(hunk.rstrip('\n'))) | |
83 if nl: hunk += '\n' | |
84 ofp.write(hunk) | |
85 # then its output | |
86 self.drain(cout, ofp) | |
87 self.status('\n') | |
88 finally: | |
89 os.wait() | |
90 shutil.rmtree(tmpdir) | |
45 | 91 |
46 def main(path='.'): | 92 def main(path='.'): |
47 args = sys.argv[1:] | 93 args = sys.argv[1:] |
48 if args: | 94 if args: |
49 for a in args: | 95 for a in args: |
51 return | 97 return |
52 for name in os.listdir(path): | 98 for name in os.listdir(path): |
53 if name == 'run-example' or name.startswith('.'): continue | 99 if name == 'run-example' or name.startswith('.'): continue |
54 if name.endswith('.out') or name.endswith('~'): continue | 100 if name.endswith('.out') or name.endswith('~'): continue |
55 example(os.path.join(path, name)).run() | 101 example(os.path.join(path, name)).run() |
102 print >> open(os.path.join(path, '.run'), 'w'), time.asctime() | |
56 | 103 |
57 if __name__ == '__main__': | 104 if __name__ == '__main__': |
58 main() | 105 main() |