1b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik#!/usr/bin/env python2.4
4b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
5b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
6b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
7b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
8b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris CraikThese are functions for use when doctest-testing a document.
9b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik"""
10b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
11b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport subprocess
12b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport doctest
13b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport os
14b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport sys
15b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport shutil
16b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport re
17b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport cgi
18b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport rfc822
19b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom cStringIO import StringIO
20b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikfrom paste.util import PySourceColor
21b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
22b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
23b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikhere = os.path.abspath(__file__)
24b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikpaste_parent = os.path.dirname(
25b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    os.path.dirname(os.path.dirname(here)))
26b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
27b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef run(command):
28b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    data = run_raw(command)
29b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if data:
30b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        print(data)
31b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
32b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef run_raw(command):
33b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
34b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Runs the string command, returns any output.
35b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
36b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    proc = subprocess.Popen(command, shell=True,
37b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                            stderr=subprocess.STDOUT,
38b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                            stdout=subprocess.PIPE, env=_make_env())
39b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    data = proc.stdout.read()
40b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    proc.wait()
41b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    while data.endswith('\n') or data.endswith('\r'):
42b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        data = data[:-1]
43b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if data:
44b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        data = '\n'.join(
45b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            [l for l in data.splitlines() if l])
46b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return data
47b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
48b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return ''
49b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
50b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef run_command(command, name, and_print=False):
51b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    output = run_raw(command)
52b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    data = '$ %s\n%s' % (command, output)
53b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    show_file('shell-command', name, description='shell transcript',
54b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik              data=data)
55b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if and_print and output:
56b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        print(output)
57b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
58b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef _make_env():
59b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    env = os.environ.copy()
60b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    env['PATH'] = (env.get('PATH', '')
61b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                   + ':'
62b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                   + os.path.join(paste_parent, 'scripts')
63b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                   + ':'
64b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                   + os.path.join(paste_parent, 'paste', '3rd-party',
65b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                                  'sqlobject-files', 'scripts'))
66b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    env['PYTHONPATH'] = (env.get('PYTHONPATH', '')
67b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                         + ':'
68b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                         + paste_parent)
69b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return env
70b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
71b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef clear_dir(dir):
72b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
73b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Clears (deletes) the given directory
74b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
75b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    shutil.rmtree(dir, True)
76b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
77b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef ls(dir=None, recurse=False, indent=0):
78b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
79b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Show a directory listing
80b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
81b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    dir = dir or os.getcwd()
82b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    fns = os.listdir(dir)
83b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    fns.sort()
84b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for fn in fns:
85b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        full = os.path.join(dir, fn)
86b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if os.path.isdir(full):
87b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            fn = fn + '/'
88b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        print(' '*indent + fn)
89b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if os.path.isdir(full) and recurse:
90b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            ls(dir=full, recurse=True, indent=indent+2)
91b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
92b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdefault_app = None
93b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdefault_url = None
94b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
95b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef set_default_app(app, url):
96b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    global default_app
97b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    global default_url
98b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    default_app = app
99b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    default_url = url
100b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
101b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef resource_filename(fn):
102b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
103b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    Returns the filename of the resource -- generally in the directory
104b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    resources/DocumentName/fn
105b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
106b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return os.path.join(
107b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        os.path.dirname(sys.testing_document_filename),
108b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        'resources',
109b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        os.path.splitext(os.path.basename(sys.testing_document_filename))[0],
110b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        fn)
111b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
112b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef show(path_info, example_name):
113b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    fn = resource_filename(example_name + '.html')
114b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    out = StringIO()
115b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    assert default_app is not None, (
116b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        "No default_app set")
117b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    url = default_url + path_info
118b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    out.write('<span class="doctest-url"><a href="%s">%s</a></span><br>\n'
119b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik              % (url, url))
120b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    out.write('<div class="doctest-example">\n')
121b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    proc = subprocess.Popen(
122b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ['paster', 'serve' '--server=console', '--no-verbose',
123b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik         '--url=' + path_info],
124b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        stderr=subprocess.PIPE,
125b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        stdout=subprocess.PIPE,
126b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        env=_make_env())
127b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    stdout, errors = proc.communicate()
128b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    stdout = StringIO(stdout)
129b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    headers = rfc822.Message(stdout)
130b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    content = stdout.read()
131b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for header, value in headers.items():
132b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if header.lower() == 'status' and int(value.split()[0]) == 200:
133b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            continue
134b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if header.lower() in ('content-type', 'content-length'):
135b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            continue
136b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if (header.lower() == 'set-cookie'
137b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            and value.startswith('_SID_')):
138b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            continue
139b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        out.write('<span class="doctest-header">%s: %s</span><br>\n'
140b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                  % (header, value))
141b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    lines = [l for l in content.splitlines() if l.strip()]
142b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for line in lines:
143b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        out.write(line + '\n')
144b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if errors:
145b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        out.write('<pre class="doctest-errors">%s</pre>'
146b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                  % errors)
147b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    out.write('</div>\n')
148b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    result = out.getvalue()
149b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not os.path.exists(fn):
150b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f = open(fn, 'wb')
151b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f.write(result)
152b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f.close()
153b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
154b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f = open(fn, 'rb')
155b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        expected = f.read()
156b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f.close()
157b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if not html_matches(expected, result):
158b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print('Pages did not match.  Expected from %s:' % fn)
159b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print('-'*60)
160b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print(expected)
161b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print('='*60)
162b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print('Actual output:')
163b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print('-'*60)
164b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            print(result)
165b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
166b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef html_matches(pattern, text):
167b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    regex = re.escape(pattern)
168b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    regex = regex.replace(r'\.\.\.', '.*')
169b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    regex = re.sub(r'0x[0-9a-f]+', '.*', regex)
170b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    regex = '^%s$' % regex
171b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return re.search(regex, text)
172b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
173b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef convert_docstring_string(data):
174b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if data.startswith('\n'):
175b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        data = data[1:]
176b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    lines = data.splitlines()
177b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    new_lines = []
178b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for line in lines:
179b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if line.rstrip() == '.':
180b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            new_lines.append('')
181b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
182b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            new_lines.append(line)
183b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    data = '\n'.join(new_lines) + '\n'
184b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return data
185b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
186b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef create_file(path, version, data):
187b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    data = convert_docstring_string(data)
188b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    write_data(path, data)
189b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    show_file(path, version)
190b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
191b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef append_to_file(path, version, data):
192b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    data = convert_docstring_string(data)
193b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f = open(path, 'a')
194b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.write(data)
195b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.close()
196b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # I think these appends can happen so quickly (in less than a second)
197b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # that the .pyc file doesn't appear to be expired, even though it
198b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # is after we've made this change; so we have to get rid of the .pyc
199b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    # file:
200b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if path.endswith('.py'):
201b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        pyc_file = path + 'c'
202b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if os.path.exists(pyc_file):
203b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            os.unlink(pyc_file)
204b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    show_file(path, version, description='added to %s' % path,
205b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik              data=data)
206b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
207b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef show_file(path, version, description=None, data=None):
208b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    ext = os.path.splitext(path)[1]
209b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if data is None:
210b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f = open(path, 'rb')
211b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        data = f.read()
212b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        f.close()
213b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if ext == '.py':
214b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        html = ('<div class="source-code">%s</div>'
215b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                % PySourceColor.str2html(data, PySourceColor.dark))
216b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    else:
217b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        html = '<pre class="source-code">%s</pre>' % cgi.escape(data, 1)
218b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    html = '<span class="source-filename">%s</span><br>%s' % (
219b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        description or path, html)
220b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    write_data(resource_filename('%s.%s.gen.html' % (path, version)),
221b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik               html)
222b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
223b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef call_source_highlight(input, format):
224b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    proc = subprocess.Popen(['source-highlight', '--out-format=html',
225b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                             '--no-doc', '--css=none',
226b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                             '--src-lang=%s' % format], shell=False,
227b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                            stdout=subprocess.PIPE)
228b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    stdout, stderr = proc.communicate(input)
229b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    result = stdout
230b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    proc.wait()
231b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    return result
232b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
233b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
234b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef write_data(path, data):
235b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    dir = os.path.dirname(os.path.abspath(path))
236b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not os.path.exists(dir):
237b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        os.makedirs(dir)
238b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f = open(path, 'wb')
239b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.write(data)
240b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.close()
241b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
242b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
243b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikdef change_file(path, changes):
244b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f = open(os.path.abspath(path), 'rb')
245b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    lines = f.readlines()
246b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.close()
247b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for change_type, line, text in changes:
248b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if change_type == 'insert':
249b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            lines[line:line] = [text]
250b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        elif change_type == 'delete':
251b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            lines[line:text] = []
252b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
253b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            assert 0, (
254b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                "Unknown change_type: %r" % change_type)
255b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f = open(path, 'wb')
256b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.write(''.join(lines))
257b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    f.close()
258b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
259b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikclass LongFormDocTestParser(doctest.DocTestParser):
260b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
261b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
262b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    This parser recognizes some reST comments as commands, without
263b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    prompts or expected output, like:
264b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
265b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    .. run:
266b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
267b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        do_this(...
268b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ...)
269b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    """
270b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
271b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    _EXAMPLE_RE = re.compile(r"""
272b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Source consists of a PS1 line followed by zero or more PS2 lines.
273b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        (?: (?P<source>
274b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                (?:^(?P<indent> [ ]*) >>>    .*)    # PS1 line
275b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                (?:\n           [ ]*  \.\.\. .*)*)  # PS2 lines
276b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            \n?
277b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Want consists of any non-blank lines that do not start with PS1.
278b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (?P<want> (?:(?![ ]*$)    # Not a blank line
279b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                         (?![ ]*>>>)  # Not a line starting with PS1
280b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                         .*$\n?       # But any other line
281b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                      )*))
282b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        |
283b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        (?: # This is for longer commands that are prefixed with a reST
284b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # comment like '.. run:' (two colons makes that a directive).
285b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # These commands cannot have any output.
286b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
287b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (?:^\.\.[ ]*(?P<run>run):[ ]*\n) # Leading command/command
288b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (?:[ ]*\n)?         # Blank line following
289b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (?P<runsource>
290b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                (?:(?P<runindent> [ ]+)[^ ].*$)
291b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                (?:\n [ ]+ .*)*)
292b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            )
293b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        |
294b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        (?: # This is for shell commands
295b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
296b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (?P<shellsource>
297b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                (?:^(P<shellindent> [ ]*) [$] .*)   # Shell line
298b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                (?:\n               [ ]*  [>] .*)*) # Continuation
299b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            \n?
300b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Want consists of any non-blank lines that do not start with $
301b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (?P<shellwant> (?:(?![ ]*$)
302b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                              (?![ ]*[$]$)
303b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                              .*$\n?
304b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                           )*))
305b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """, re.MULTILINE | re.VERBOSE)
306b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
307b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def _parse_example(self, m, name, lineno):
308b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        r"""
309b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        Given a regular expression match from `_EXAMPLE_RE` (`m`),
310b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return a pair `(source, want)`, where `source` is the matched
311b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        example's source code (with prompts and indentation stripped);
312b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        and `want` is the example's expected output (with indentation
313b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        stripped).
314b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
315b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        `name` is the string's name, and `lineno` is the line number
316b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        where the example starts; both are used for error messages.
317b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
318b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        >>> def parseit(s):
319b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ...     p = LongFormDocTestParser()
320b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ...     return p._parse_example(p._EXAMPLE_RE.search(s), '<string>', 1)
321b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        >>> parseit('>>> 1\n1')
322b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ('1', {}, '1', None)
323b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        >>> parseit('>>> (1\n... +1)\n2')
324b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ('(1\n+1)', {}, '2', None)
325b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        >>> parseit('.. run:\n\n    test1\n    test2\n')
326b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        ('test1\ntest2', {}, '', None)
327b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
328b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Get the example's indentation level.
329b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        runner = m.group('run') or ''
330b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        indent = len(m.group('%sindent' % runner))
331b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
332b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Divide source into lines; check that they're properly
333b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # indented; and then strip their indentation & prompts.
334b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        source_lines = m.group('%ssource' % runner).split('\n')
335b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if runner:
336b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self._check_prefix(source_lines[1:], ' '*indent, name, lineno)
337b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
338b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self._check_prompt_blank(source_lines, indent, name, lineno)
339b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self._check_prefix(source_lines[2:], ' '*indent + '.', name, lineno)
340b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if runner:
341b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            source = '\n'.join([sl[indent:] for sl in source_lines])
342b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
343b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            source = '\n'.join([sl[indent+4:] for sl in source_lines])
344b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
345b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if runner:
346b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            want = ''
347b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            exc_msg = None
348b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        else:
349b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Divide want into lines; check that it's properly indented; and
350b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # then strip the indentation.  Spaces before the last newline should
351b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # be preserved, so plain rstrip() isn't good enough.
352b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            want = m.group('want')
353b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            want_lines = want.split('\n')
354b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if len(want_lines) > 1 and re.match(r' *$', want_lines[-1]):
355b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                del want_lines[-1]  # forget final newline & spaces after it
356b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            self._check_prefix(want_lines, ' '*indent, name,
357b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                               lineno + len(source_lines))
358b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            want = '\n'.join([wl[indent:] for wl in want_lines])
359b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
360b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # If `want` contains a traceback message, then extract it.
361b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            m = self._EXCEPTION_RE.match(want)
362b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if m:
363b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                exc_msg = m.group('msg')
364b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            else:
365b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                exc_msg = None
366b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
367b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Extract options from the source.
368b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        options = self._find_options(source, name, lineno)
369b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
370b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return source, options, want, exc_msg
371b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
372b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
373b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    def parse(self, string, name='<string>'):
374b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
375b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        Divide the given string into examples and intervening text,
376b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        and return them as a list of alternating Examples and strings.
377b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        Line numbers for the Examples are 0-based.  The optional
378b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        argument `name` is a name identifying this string, and is only
379b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        used for error messages.
380b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        """
381b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        string = string.expandtabs()
382b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # If all lines begin with the same indentation, then strip it.
383b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        min_indent = self._min_indent(string)
384b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        if min_indent > 0:
385b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            string = '\n'.join([l[min_indent:] for l in string.split('\n')])
386b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
387b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        output = []
388b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        charno, lineno = 0, 0
389b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Find all doctest examples in the string:
390b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        for m in self._EXAMPLE_RE.finditer(string):
391b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Add the pre-example text to `output`.
392b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            output.append(string[charno:m.start()])
393b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Update lineno (lines before this example)
394b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            lineno += string.count('\n', charno, m.start())
395b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Extract info from the regexp match.
396b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            (source, options, want, exc_msg) = \
397b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                     self._parse_example(m, name, lineno)
398b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Create an Example, and add it to the list.
399b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            if not self._IS_BLANK_OR_COMMENT(source):
400b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                # @@: Erg, this is the only line I need to change...
401b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                output.append(doctest.Example(
402b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    source, want, exc_msg,
403b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    lineno=lineno,
404b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    indent=min_indent+len(m.group('indent') or m.group('runindent')),
405b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                    options=options))
406b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Update lineno (lines inside this example)
407b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            lineno += string.count('\n', m.start(), m.end())
408b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            # Update charno.
409b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            charno = m.end()
410b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # Add any remaining post-example text to `output`.
411b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        output.append(string[charno:])
412b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        return output
413b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
414b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
415b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
416b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikif __name__ == '__main__':
417b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if sys.argv[1:] and sys.argv[1] == 'doctest':
418b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        doctest.testmod()
419b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        sys.exit()
420b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not paste_parent in sys.path:
421b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        sys.path.append(paste_parent)
422b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    for fn in sys.argv[1:]:
423b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        fn = os.path.abspath(fn)
424b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        # @@: OK, ick; but this module gets loaded twice
425b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        sys.testing_document_filename = fn
426b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        doctest.testfile(
427b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            fn, module_relative=False,
428b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            optionflags=doctest.ELLIPSIS|doctest.REPORT_ONLY_FIRST_FAILURE,
429b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik            parser=LongFormDocTestParser())
430b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        new = os.path.splitext(fn)[0] + '.html'
431b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        assert new != fn
432b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        os.system('rst2html.py %s > %s' % (fn, new))
433