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