Package epydoc :: Package markup :: Module doctest
[hide private]
[frames] | no frames]

Source Code for Module epydoc.markup.doctest

  1  # 
  2  # doctest.py: Syntax Highlighting for doctest blocks 
  3  # Edward Loper 
  4  # 
  5  # Created [06/28/03 02:52 AM] 
  6  # $Id: restructuredtext.py 1210 2006-04-10 13:25:50Z edloper $ 
  7  # 
  8   
  9  """ 
 10  Syntax highlighting for doctest blocks.  This module defines two 
 11  functions, L{doctest_to_html()} and L{doctest_to_latex()}, which can 
 12  be used to perform syntax highlighting on doctest blocks.  It also 
 13  defines the more general C{colorize_doctest()}, which could be used to 
 14  do syntac highlighting on doctest blocks with other output formats. 
 15  (Both C{doctest_to_html()} and C{doctest_to_latex()} are defined using 
 16  C{colorize_doctest()}.) 
 17  """ 
 18  __docformat__ = 'epytext en' 
 19   
 20  import re 
 21  from epydoc.util import plaintext_to_html, plaintext_to_latex 
 22   
 23  __all__ = ['doctest_to_html', 'doctest_to_latex', 
 24             'DoctestColorizer', 'XMLDoctestColorizer',  
 25             'HTMLDoctestColorizer', 'LaTeXDoctestColorizer'] 
 26   
27 -def doctest_to_html(s):
28 """ 29 Perform syntax highlighting on the given doctest string, and 30 return the resulting HTML code. This code consists of a C{<pre>} 31 block with class=py-doctest. Syntax highlighting is performed 32 using the following css classes: 33 34 - C{py-prompt} -- the Python PS1 prompt (>>>) 35 - C{py-more} -- the Python PS2 prompt (...) 36 - C{py-keyword} -- a Python keyword (for, if, etc.) 37 - C{py-builtin} -- a Python builtin name (abs, dir, etc.) 38 - C{py-string} -- a string literal 39 - C{py-comment} -- a comment 40 - C{py-except} -- an exception traceback (up to the next >>>) 41 - C{py-output} -- the output from a doctest block. 42 - C{py-defname} -- the name of a function or class defined by 43 a C{def} or C{class} statement. 44 """ 45 return HTMLDoctestColorizer().colorize_doctest(s)
46
47 -def doctest_to_latex(s):
48 """ 49 Perform syntax highlighting on the given doctest string, and 50 return the resulting LaTeX code. This code consists of an 51 C{alltt} environment. Syntax highlighting is performed using 52 the following new latex commands, which must be defined externally: 53 - C{\pysrcprompt} -- the Python PS1 prompt (>>>) 54 - C{\pysrcmore} -- the Python PS2 prompt (...) 55 - C{\pysrckeyword} -- a Python keyword (for, if, etc.) 56 - C{\pysrcbuiltin} -- a Python builtin name (abs, dir, etc.) 57 - C{\pysrcstring} -- a string literal 58 - C{\pysrccomment} -- a comment 59 - C{\pysrcexcept} -- an exception traceback (up to the next >>>) 60 - C{\pysrcoutput} -- the output from a doctest block. 61 - C{\pysrcdefname} -- the name of a function or class defined by 62 a C{def} or C{class} statement. 63 """ 64 return LaTeXDoctestColorizer().colorize_doctest(s)
65
66 -class DoctestColorizer:
67 """ 68 An abstract base class for performing syntax highlighting on 69 doctest blocks and other bits of Python code. Subclasses should 70 provide definitions for: 71 72 - The L{markup()} method, which takes a substring and a tag, and 73 returns a colorized version of the substring. 74 - The L{PREFIX} and L{SUFFIX} variables, which will be added 75 to the beginning and end of the strings returned by 76 L{colorize_codeblock} and L{colorize_doctest}. 77 """ 78 79 #: A string that is added to the beginning of the strings 80 #: returned by L{colorize_codeblock} and L{colorize_doctest}. 81 #: Typically, this string begins a preformatted area. 82 PREFIX = None 83 84 #: A string that is added to the end of the strings 85 #: returned by L{colorize_codeblock} and L{colorize_doctest}. 86 #: Typically, this string ends a preformatted area. 87 SUFFIX = None 88 89 #: A list of the names of all Python keywords. ('as' is included 90 #: even though it is technically not a keyword.) 91 _KEYWORDS = ("and del for is raise" 92 "assert elif from lambda return" 93 "break else global not try" 94 "class except if or while" 95 "continue exec import pass yield" 96 "def finally in print as").split() 97 98 #: A list of all Python builtins. 99 _BUILTINS = [_BI for _BI in dir(__builtins__) 100 if not _BI.startswith('__')] 101 102 #: A regexp group that matches keywords. 103 _KEYWORD_GRP = '|'.join([r'\b%s\b' % _KW for _KW in _KEYWORDS]) 104 105 #: A regexp group that matches Python builtins. 106 _BUILTIN_GRP = (r'(?<!\.)(?:%s)' % '|'.join([r'\b%s\b' % _BI 107 for _BI in _BUILTINS])) 108 109 #: A regexp group that matches Python strings. 110 _STRING_GRP = '|'.join( 111 [r'("""("""|.*?((?!").)"""))', r'("("|.*?((?!").)"))', 112 r"('''('''|.*?[^\\']'''))", r"('('|.*?[^\\']'))"]) 113 114 #: A regexp group that matches Python comments. 115 _COMMENT_GRP = '(#.*?$)' 116 117 #: A regexp group that matches Python ">>>" prompts. 118 _PROMPT1_GRP = r'^[ \t]*>>>(?:[ \t]|$)' 119 120 #: A regexp group that matches Python "..." prompts. 121 _PROMPT2_GRP = r'^[ \t]*\.\.\.(?:[ \t]|$)' 122 123 #: A regexp group that matches function and class definitions. 124 _DEFINE_GRP = r'\b(?:def|class)[ \t]+\w+' 125 126 #: A regexp that matches Python prompts 127 PROMPT_RE = re.compile('(%s|%s)' % (_PROMPT1_GRP, _PROMPT2_GRP), 128 re.MULTILINE | re.DOTALL) 129 130 #: A regexp that matches Python "..." prompts. 131 PROMPT2_RE = re.compile('(%s)' % _PROMPT2_GRP, 132 re.MULTILINE | re.DOTALL) 133 134 #: A regexp that matches doctest exception blocks. 135 EXCEPT_RE = re.compile(r'^[ \t]*Traceback \(most recent call last\):.*', 136 re.DOTALL | re.MULTILINE) 137 138 #: A regexp that matches doctest directives. 139 DOCTEST_DIRECTIVE_RE = re.compile(r'#[ \t]*doctest:.*') 140 141 #: A regexp that matches all of the regions of a doctest block 142 #: that should be colored. 143 DOCTEST_RE = re.compile( 144 r'(.*?)((?P<STRING>%s)|(?P<COMMENT>%s)|(?P<DEFINE>%s)|' 145 r'(?P<KEYWORD>%s)|(?P<BUILTIN>%s)|' 146 r'(?P<PROMPT1>%s)|(?P<PROMPT2>%s)|(?P<EOS>\Z))' % ( 147 _STRING_GRP, _COMMENT_GRP, _DEFINE_GRP, _KEYWORD_GRP, _BUILTIN_GRP, 148 _PROMPT1_GRP, _PROMPT2_GRP), re.MULTILINE | re.DOTALL) 149 150 #: This regular expression is used to find doctest examples in a 151 #: string. This is copied from the standard Python doctest.py 152 #: module (after the refactoring in Python 2.4+). 153 DOCTEST_EXAMPLE_RE = re.compile(r''' 154 # Source consists of a PS1 line followed by zero or more PS2 lines. 155 (?P<source> 156 (?:^(?P<indent> [ ]*) >>> .*) # PS1 line 157 (?:\n [ ]* \.\.\. .*)* # PS2 lines 158 \n?) 159 # Want consists of any non-blank lines that do not start with PS1. 160 (?P<want> (?:(?![ ]*$) # Not a blank line 161 (?![ ]*>>>) # Not a line starting with PS1 162 .*$\n? # But any other line 163 )*) 164 ''', re.MULTILINE | re.VERBOSE) 165
166 - def colorize_inline(self, s):
167 """ 168 Colorize a string containing Python code. Do not add the 169 L{PREFIX} and L{SUFFIX} strings to the returned value. This 170 method is intended for generating syntax-highlighted strings 171 that are appropriate for inclusion as inline expressions. 172 """ 173 return self.DOCTEST_RE.sub(self.subfunc, s)
174
175 - def colorize_codeblock(self, s):
176 """ 177 Colorize a string containing only Python code. This method 178 differs from L{colorize_doctest} in that it will not search 179 for doctest prompts when deciding how to colorize the string. 180 """ 181 body = self.DOCTEST_RE.sub(self.subfunc, s) 182 return self.PREFIX + body + self.SUFFIX
183
184 - def colorize_doctest(self, s, strip_directives=False):
185 """ 186 Colorize a string containing one or more doctest examples. 187 """ 188 output = [] 189 charno = 0 190 for m in self.DOCTEST_EXAMPLE_RE.finditer(s): 191 # Parse the doctest example: 192 pysrc, want = m.group('source', 'want') 193 # Pre-example text: 194 output.append(s[charno:m.start()]) 195 # Example source code: 196 output.append(self.DOCTEST_RE.sub(self.subfunc, pysrc)) 197 # Example output: 198 if want: 199 if self.EXCEPT_RE.match(want): 200 output += '\n'.join([self.markup(line, 'except') 201 for line in want.split('\n')]) 202 else: 203 output += '\n'.join([self.markup(line, 'output') 204 for line in want.split('\n')]) 205 # Update charno 206 charno = m.end() 207 # Add any remaining post-example text. 208 output.append(s[charno:]) 209 210 return self.PREFIX + ''.join(output) + self.SUFFIX
211
212 - def subfunc(self, match):
213 other, text = match.group(1, 2) 214 #print 'M %20r %20r' % (other, text) # <- for debugging 215 if other: 216 other = '\n'.join([self.markup(line, 'other') 217 for line in other.split('\n')]) 218 219 if match.group('PROMPT1'): 220 return other + self.markup(text, 'prompt') 221 elif match.group('PROMPT2'): 222 return other + self.markup(text, 'more') 223 elif match.group('KEYWORD'): 224 return other + self.markup(text, 'keyword') 225 elif match.group('BUILTIN'): 226 return other + self.markup(text, 'builtin') 227 elif match.group('COMMENT'): 228 return other + self.markup(text, 'comment') 229 elif match.group('STRING') and '\n' not in text: 230 return other + self.markup(text, 'string') 231 elif match.group('STRING'): 232 # It's a multiline string; colorize the string & prompt 233 # portion of each line. 234 pieces = [] 235 for line in text.split('\n'): 236 if self.PROMPT2_RE.match(line): 237 if len(line) > 4: 238 pieces.append(self.markup(line[:4], 'more') + 239 self.markup(line[4:], 'string')) 240 else: 241 pieces.append(self.markup(line[:4], 'more')) 242 elif line: 243 pieces.append(self.markup(line, 'string')) 244 else: 245 pieces.append('') 246 return other + '\n'.join(pieces) 247 elif match.group('DEFINE'): 248 m = re.match('(?P<def>\w+)(?P<space>\s+)(?P<name>\w+)', text) 249 return other + (self.markup(m.group('def'), 'keyword') + 250 self.markup(m.group('space'), 'other') + 251 self.markup(m.group('name'), 'defname')) 252 elif match.group('EOS') is not None: 253 return other 254 else: 255 assert 0, 'Unexpected match!'
256
257 - def markup(self, s, tag):
258 """ 259 Apply syntax highlighting to a single substring from a doctest 260 block. C{s} is the substring, and C{tag} is the tag that 261 should be applied to the substring. C{tag} will be one of the 262 following strings: 263 264 - C{prompt} -- the Python PS1 prompt (>>>) 265 - C{more} -- the Python PS2 prompt (...) 266 - C{keyword} -- a Python keyword (for, if, etc.) 267 - C{builtin} -- a Python builtin name (abs, dir, etc.) 268 - C{string} -- a string literal 269 - C{comment} -- a comment 270 - C{except} -- an exception traceback (up to the next >>>) 271 - C{output} -- the output from a doctest block. 272 - C{defname} -- the name of a function or class defined by 273 a C{def} or C{class} statement. 274 - C{other} -- anything else (does *not* include output.) 275 """ 276 raise AssertionError("Abstract method")
277
278 -class XMLDoctestColorizer(DoctestColorizer):
279 """ 280 A subclass of DoctestColorizer that generates XML-like output. 281 This class is mainly intended to be used for testing purposes. 282 """ 283 PREFIX = '<colorized>\n' 284 SUFFIX = '</colorized>\n'
285 - def markup(self, s, tag):
286 s = s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') 287 if tag == 'other': return s 288 else: return '<%s>%s</%s>' % (tag, s, tag)
289
290 -class HTMLDoctestColorizer(DoctestColorizer):
291 """A subclass of DoctestColorizer that generates HTML output.""" 292 PREFIX = '<pre class="py-doctest">\n' 293 SUFFIX = '</pre>\n'
294 - def markup(self, s, tag):
295 if tag == 'other': 296 return plaintext_to_html(s) 297 else: 298 return ('<span class="py-%s">%s</span>' % 299 (tag, plaintext_to_html(s)))
300
301 -class LaTeXDoctestColorizer(DoctestColorizer):
302 """A subclass of DoctestColorizer that generates LaTeX output.""" 303 PREFIX = '\\begin{alltt}\n' 304 SUFFIX = '\\end{alltt}\n'
305 - def markup(self, s, tag):
306 if tag == 'other': 307 return plaintext_to_latex(s) 308 else: 309 return '\\pysrc%s{%s}' % (tag, plaintext_to_latex(s))
310