Package epydoc :: Package docwriter :: Module html_colorize
[hide private]
[frames] | no frames]

Source Code for Module epydoc.docwriter.html_colorize

  1  # 
  2  # epydoc.html: HTML colorizers 
  3  # Edward Loper 
  4  # 
  5  # Created [10/16/02 09:49 PM] 
  6  # $Id: html_colorize.py 1716 2008-02-14 22:21:23Z edloper $ 
  7  # 
  8   
  9  """ 
 10  Functions to produce colorized HTML code for various objects. 
 11  Currently, C{html_colorize} defines functions to colorize 
 12  Python source code. 
 13  """ 
 14  __docformat__ = 'epytext en' 
 15   
 16  import re, codecs 
 17  from epydoc import log 
 18  from epydoc.util import py_src_filename 
 19  from epydoc.apidoc import * 
 20  import tokenize, token, cgi, keyword 
 21  try: from cStringIO import StringIO 
 22  except: from StringIO import StringIO 
 23   
 24  ###################################################################### 
 25  ## Python source colorizer 
 26  ###################################################################### 
 27  """ 
 28  Goals: 
 29    - colorize tokens appropriately (using css) 
 30    - optionally add line numbers 
 31    -  
 32  """ 
 33   
 34  #: Javascript code for the PythonSourceColorizer 
 35  PYSRC_JAVASCRIPTS = '''\ 
 36  function expand(id) { 
 37    var elt = document.getElementById(id+"-expanded"); 
 38    if (elt) elt.style.display = "block"; 
 39    var elt = document.getElementById(id+"-expanded-linenums"); 
 40    if (elt) elt.style.display = "block"; 
 41    var elt = document.getElementById(id+"-collapsed"); 
 42    if (elt) { elt.innerHTML = ""; elt.style.display = "none"; } 
 43    var elt = document.getElementById(id+"-collapsed-linenums"); 
 44    if (elt) { elt.innerHTML = ""; elt.style.display = "none"; } 
 45    var elt = document.getElementById(id+"-toggle"); 
 46    if (elt) { elt.innerHTML = "-"; } 
 47  } 
 48   
 49  function collapse(id) { 
 50    var elt = document.getElementById(id+"-expanded"); 
 51    if (elt) elt.style.display = "none"; 
 52    var elt = document.getElementById(id+"-expanded-linenums"); 
 53    if (elt) elt.style.display = "none"; 
 54    var elt = document.getElementById(id+"-collapsed-linenums"); 
 55    if (elt) { elt.innerHTML = "<br />"; elt.style.display="block"; } 
 56    var elt = document.getElementById(id+"-toggle"); 
 57    if (elt) { elt.innerHTML = "+"; } 
 58    var elt = document.getElementById(id+"-collapsed"); 
 59    if (elt) { 
 60      elt.style.display = "block"; 
 61       
 62      var indent = elt.getAttribute("indent"); 
 63      var pad = elt.getAttribute("pad"); 
 64      var s = "<tt class=\'py-lineno\'>"; 
 65      for (var i=0; i<pad.length; i++) { s += "&nbsp;" } 
 66      s += "</tt>"; 
 67      s += "&nbsp;&nbsp;<tt class=\'py-line\'>"; 
 68      for (var i=0; i<indent.length; i++) { s += "&nbsp;" } 
 69      s += "<a href=\'#\' onclick=\'expand(\\"" + id; 
 70      s += "\\");return false\'>...</a></tt><br />"; 
 71      elt.innerHTML = s; 
 72    } 
 73  } 
 74   
 75  function toggle(id) { 
 76    elt = document.getElementById(id+"-toggle"); 
 77    if (elt.innerHTML == "-") 
 78        collapse(id);  
 79    else 
 80        expand(id); 
 81    return false; 
 82  } 
 83   
 84  function highlight(id) { 
 85    var elt = document.getElementById(id+"-def"); 
 86    if (elt) elt.className = "py-highlight-hdr"; 
 87    var elt = document.getElementById(id+"-expanded"); 
 88    if (elt) elt.className = "py-highlight"; 
 89    var elt = document.getElementById(id+"-collapsed"); 
 90    if (elt) elt.className = "py-highlight"; 
 91  } 
 92   
 93  function num_lines(s) { 
 94    var n = 1; 
 95    var pos = s.indexOf("\\n"); 
 96    while ( pos > 0) { 
 97      n += 1; 
 98      pos = s.indexOf("\\n", pos+1); 
 99    } 
100    return n; 
101  } 
102   
103  // Collapse all blocks that mave more than `min_lines` lines. 
104  function collapse_all(min_lines) { 
105    var elts = document.getElementsByTagName("div"); 
106    for (var i=0; i<elts.length; i++) { 
107      var elt = elts[i]; 
108      var split = elt.id.indexOf("-"); 
109      if (split > 0) 
110        if (elt.id.substring(split, elt.id.length) == "-expanded") 
111          if (num_lines(elt.innerHTML) > min_lines) 
112            collapse(elt.id.substring(0, split)); 
113    } 
114  } 
115   
116  function expandto(href) { 
117    var start = href.indexOf("#")+1; 
118    if (start != 0 && start != href.length) { 
119      if (href.substring(start, href.length) != "-") { 
120        collapse_all(4); 
121        pos = href.indexOf(".", start); 
122        while (pos != -1) { 
123          var id = href.substring(start, pos); 
124          expand(id); 
125          pos = href.indexOf(".", pos+1); 
126        } 
127        var id = href.substring(start, href.length); 
128        expand(id); 
129        highlight(id); 
130      } 
131    } 
132  } 
133   
134  function kill_doclink(id) { 
135    var parent = document.getElementById(id); 
136    parent.removeChild(parent.childNodes.item(0)); 
137  } 
138  function auto_kill_doclink(ev) { 
139    if (!ev) var ev = window.event; 
140    if (!this.contains(ev.toElement)) { 
141      var parent = document.getElementById(this.parentID); 
142      parent.removeChild(parent.childNodes.item(0)); 
143    } 
144  } 
145   
146  function doclink(id, name, targets_id) { 
147    var elt = document.getElementById(id); 
148   
149    // If we already opened the box, then destroy it. 
150    // (This case should never occur, but leave it in just in case.) 
151    if (elt.childNodes.length > 1) { 
152      elt.removeChild(elt.childNodes.item(0)); 
153    } 
154    else { 
155      // The outer box: relative + inline positioning. 
156      var box1 = document.createElement("div"); 
157      box1.style.position = "relative"; 
158      box1.style.display = "inline"; 
159      box1.style.top = 0; 
160      box1.style.left = 0; 
161     
162      // A shadow for fun 
163      var shadow = document.createElement("div"); 
164      shadow.style.position = "absolute"; 
165      shadow.style.left = "-1.3em"; 
166      shadow.style.top = "-1.3em"; 
167      shadow.style.background = "#404040"; 
168       
169      // The inner box: absolute positioning. 
170      var box2 = document.createElement("div"); 
171      box2.style.position = "relative"; 
172      box2.style.border = "1px solid #a0a0a0"; 
173      box2.style.left = "-.2em"; 
174      box2.style.top = "-.2em"; 
175      box2.style.background = "white"; 
176      box2.style.padding = ".3em .4em .3em .4em"; 
177      box2.style.fontStyle = "normal"; 
178      box2.onmouseout=auto_kill_doclink; 
179      box2.parentID = id; 
180   
181      // Get the targets 
182      var targets_elt = document.getElementById(targets_id); 
183      var targets = targets_elt.getAttribute("targets"); 
184      var links = ""; 
185      target_list = targets.split(","); 
186      for (var i=0; i<target_list.length; i++) { 
187          var target = target_list[i].split("="); 
188          links += "<li><a href=\'" + target[1] +  
189                 "\' style=\'text-decoration:none\'>" + 
190                 target[0] + "</a></li>"; 
191      } 
192     
193      // Put it all together. 
194      elt.insertBefore(box1, elt.childNodes.item(0)); 
195      //box1.appendChild(box2); 
196      box1.appendChild(shadow); 
197      shadow.appendChild(box2); 
198      box2.innerHTML = 
199          "Which <b>"+name+"</b> do you want to see documentation for?" + 
200          "<ul style=\'margin-bottom: 0;\'>" + 
201          links +  
202          "<li><a href=\'#\' style=\'text-decoration:none\' " + 
203          "onclick=\'kill_doclink(\\""+id+"\\");return false;\'>"+ 
204          "<i>None of the above</i></a></li></ul>"; 
205    } 
206    return false; 
207  } 
208  ''' 
209   
210  PYSRC_EXPANDTO_JAVASCRIPT = '''\ 
211  <script type="text/javascript"> 
212  <!-- 
213  expandto(location.href); 
214  // --> 
215  </script> 
216  ''' 
217   
218 -class PythonSourceColorizer:
219 """ 220 A class that renders a python module's source code into HTML 221 pages. These HTML pages are intended to be provided along with 222 the API documentation for a module, in case a user wants to learn 223 more about a particular object by examining its source code. 224 Links are therefore generated from the API documentation to the 225 source code pages, and from the source code pages back into the 226 API documentation. 227 228 The HTML generated by C{PythonSourceColorizer} has several notable 229 features: 230 231 - CSS styles are used to color tokens according to their type. 232 (See L{CSS_CLASSES} for a list of the different token types 233 that are identified). 234 235 - Line numbers are included to the left of each line. 236 237 - The first line of each class and function definition includes 238 a link to the API source documentation for that object. 239 240 - The first line of each class and function definition includes 241 an anchor that can be used to link directly to that class or 242 function. 243 244 - If javascript is enabled, and the page is loaded using the 245 anchor for a class or function (i.e., if the url ends in 246 C{'#I{<name>}'}), then that class or function will automatically 247 be highlighted; and all other classes and function definition 248 blocks will be 'collapsed'. These collapsed blocks can be 249 expanded by clicking on them. 250 251 - Unicode input is supported (including automatic detection 252 of C{'coding:'} declarations). 253 254 """ 255 #: A look-up table that is used to determine which CSS class 256 #: should be used to colorize a given token. The following keys 257 #: may be used: 258 #: - Any token name (e.g., C{'STRING'}) 259 #: - Any operator token (e.g., C{'='} or C{'@'}). 260 #: - C{'KEYWORD'} -- Python keywords such as C{'for'} and C{'if'} 261 #: - C{'DEFNAME'} -- the name of a class or function at the top 262 #: of its definition statement. 263 #: - C{'BASECLASS'} -- names of base classes at the top of a class 264 #: definition statement. 265 #: - C{'PARAM'} -- function parameters 266 #: - C{'DOCSTRING'} -- docstrings 267 #: - C{'DECORATOR'} -- decorator names 268 #: If no CSS class can be found for a given token, then it won't 269 #: be marked with any CSS class. 270 CSS_CLASSES = { 271 'NUMBER': 'py-number', 272 'STRING': 'py-string', 273 'COMMENT': 'py-comment', 274 'NAME': 'py-name', 275 'KEYWORD': 'py-keyword', 276 'DEFNAME': 'py-def-name', 277 'BASECLASS': 'py-base-class', 278 'PARAM': 'py-param', 279 'DOCSTRING': 'py-docstring', 280 'DECORATOR': 'py-decorator', 281 'OP': 'py-op', 282 '@': 'py-decorator', 283 } 284 285 #: HTML code for the beginning of a collapsable function or class 286 #: definition block. The block contains two <div>...</div> 287 #: elements -- a collapsed version and an expanded version -- and 288 #: only one of these elements is visible at any given time. By 289 #: default, all definition blocks are expanded. 290 #: 291 #: This string should be interpolated with the following values:: 292 #: (name, indentation, name) 293 #: Where C{name} is the anchor name for the function or class; and 294 #: indentation is a string of whitespace used to indent the 295 #: ellipsis marker in the collapsed version. 296 START_DEF_BLOCK = ( 297 '<div id="%s-collapsed" style="display:none;" ' 298 'pad="%s" indent="%s"></div>' 299 '<div id="%s-expanded">') 300 301 #: HTML code for the end of a collapsable function or class 302 #: definition block. 303 END_DEF_BLOCK = '</div>' 304 305 #: A regular expression used to pick out the unicode encoding for 306 #: the source file. 307 UNICODE_CODING_RE = re.compile(r'.*?\n?.*?coding[:=]\s*([-\w.]+)') 308 309 #: A configuration constant, used to determine whether or not to add 310 #: collapsable <div> elements for definition blocks. 311 ADD_DEF_BLOCKS = True 312 313 #: A configuration constant, used to determine whether or not to 314 #: add line numbers. 315 ADD_LINE_NUMBERS = True 316 317 #: A configuration constant, used to determine whether or not to 318 #: add tooltips for linked names. 319 ADD_TOOLTIPS = True 320 321 #: If true, then try to guess which target is appropriate for 322 #: linked names; if false, then always open a div asking the 323 #: user which one they want. 324 GUESS_LINK_TARGETS = False 325
326 - def __init__(self, module_filename, module_name, 327 docindex=None, url_func=None, name_to_docs=None, 328 tab_width=8):
329 """ 330 Create a new HTML colorizer for the specified module. 331 332 @param module_filename: The name of the file containing the 333 module; its text will be loaded from this file. 334 @param module_name: The dotted name of the module; this will 335 be used to create links back into the API source 336 documentation. 337 """ 338 # Get the source version, if possible. 339 try: module_filename = py_src_filename(module_filename) 340 except: pass 341 342 #: The filename of the module we're colorizing. 343 self.module_filename = module_filename 344 345 #: The dotted name of the module we're colorizing. 346 self.module_name = module_name 347 348 #: A docindex, used to create href links from identifiers to 349 #: the API documentation for their values. 350 self.docindex = docindex 351 352 #: A mapping from short names to lists of ValueDoc, used to 353 #: decide which values an identifier might map to when creating 354 #: href links from identifiers to the API docs for their values. 355 self.name_to_docs = name_to_docs 356 357 #: A function that maps APIDoc -> URL, used to create href 358 #: links from identifiers to the API documentation for their 359 #: values. 360 self.url_func = url_func 361 362 #: The index in C{text} of the last character of the last 363 #: token we've processed. 364 self.pos = 0 365 366 #: A list that maps line numbers to character offsets in 367 #: C{text}. In particular, line C{M{i}} begins at character 368 #: C{line_offset[i]} in C{text}. Since line numbers begin at 369 #: 1, the first element of C{line_offsets} is C{None}. 370 self.line_offsets = [] 371 372 #: A list of C{(toktype, toktext)} for all tokens on the 373 #: logical line that we are currently processing. Once a 374 #: complete line of tokens has been collected in C{cur_line}, 375 #: it is sent to L{handle_line} for processing. 376 self.cur_line = [] 377 378 #: A list of the names of the class or functions that include 379 #: the current block. C{context} has one element for each 380 #: level of indentation; C{context[i]} is the name of the class 381 #: or function defined by the C{i}th level of indentation, or 382 #: C{None} if that level of indentation doesn't correspond to a 383 #: class or function definition. 384 self.context = [] 385 386 #: A list, corresponding one-to-one with L{self.context}, 387 #: indicating the type of each entry. Each element of 388 #: C{context_types} is one of: C{'func'}, C{'class'}, C{None}. 389 self.context_types = [] 390 391 #: A list of indentation strings for each of the current 392 #: block's indents. I.e., the current total indentation can 393 #: be found by taking C{''.join(self.indents)}. 394 self.indents = [] 395 396 #: The line number of the line we're currently processing. 397 self.lineno = 0 398 399 #: The name of the class or function whose definition started 400 #: on the previous logical line, or C{None} if the previous 401 #: logical line was not a class or function definition. 402 self.def_name = None 403 404 #: The type of the class or function whose definition started 405 #: on the previous logical line, or C{None} if the previous 406 #: logical line was not a class or function definition. 407 #: Can be C{'func'}, C{'class'}, C{None}. 408 self.def_type = None 409 410 #: The number of spaces to replace each tab in source code with 411 self.tab_width = tab_width
412 413
414 - def find_line_offsets(self):
415 """ 416 Construct the L{line_offsets} table from C{self.text}. 417 """ 418 # line 0 doesn't exist; line 1 starts at char offset 0. 419 self.line_offsets = [None, 0] 420 # Find all newlines in `text`, and add an entry to 421 # line_offsets for each one. 422 pos = self.text.find('\n') 423 while pos != -1: 424 self.line_offsets.append(pos+1) 425 pos = self.text.find('\n', pos+1) 426 # Add a final entry, marking the end of the string. 427 self.line_offsets.append(len(self.text))
428
429 - def lineno_to_html(self):
430 template = '%%%ds' % self.linenum_size 431 n = template % self.lineno 432 return '<a name="L%s"></a><tt class="py-lineno">%s</tt>' \ 433 % (self.lineno, n)
434
435 - def colorize(self):
436 """ 437 Return an HTML string that renders the source code for the 438 module that was specified in the constructor. 439 """ 440 # Initialize all our state variables 441 self.pos = 0 442 self.cur_line = [] 443 self.context = [] 444 self.context_types = [] 445 self.indents = [] 446 self.lineno = 1 447 self.def_name = None 448 self.def_type = None 449 self.has_decorators = False 450 451 # Cache, used so we only need to list the target elements once 452 # for each variable. 453 self.doclink_targets_cache = {} 454 455 # Load the module's text. 456 self.text = open(self.module_filename).read() 457 self.text = self.text.expandtabs(self.tab_width).rstrip()+'\n' 458 459 # Construct the line_offsets table. 460 self.find_line_offsets() 461 462 num_lines = self.text.count('\n')+1 463 self.linenum_size = len(`num_lines+1`) 464 465 # Call the tokenizer, and send tokens to our `tokeneater()` 466 # method. If anything goes wrong, then fall-back to using 467 # the input text as-is (with no colorization). 468 try: 469 output = StringIO() 470 self.out = output.write 471 tokenize.tokenize(StringIO(self.text).readline, self.tokeneater) 472 html = output.getvalue() 473 if self.has_decorators: 474 html = self._FIX_DECORATOR_RE.sub(r'\2\1', html) 475 except tokenize.TokenError, ex: 476 html = self.text 477 478 # Check for a unicode encoding declaration. 479 m = self.UNICODE_CODING_RE.match(self.text) 480 if m: coding = m.group(1) 481 else: coding = 'iso-8859-1' 482 483 # Decode the html string into unicode, and then encode it back 484 # into ascii, replacing any non-ascii characters with xml 485 # character references. 486 try: 487 html = html.decode(coding).encode('ascii', 'xmlcharrefreplace') 488 except LookupError: 489 coding = 'iso-8859-1' 490 html = html.decode(coding).encode('ascii', 'xmlcharrefreplace') 491 except UnicodeDecodeError, e: 492 log.warning("Unicode error while generating syntax-highlighted " 493 "source code: %s (%s)" % (e, self.module_filename)) 494 html = html.decode(coding, 'ignore').encode( 495 'ascii', 'xmlcharrefreplace') 496 497 498 # Call expandto. 499 html += PYSRC_EXPANDTO_JAVASCRIPT 500 501 return html
502
503 - def tokeneater(self, toktype, toktext, (srow,scol), (erow,ecol), line):
504 """ 505 A callback function used by C{tokenize.tokenize} to handle 506 each token in the module. C{tokeneater} collects tokens into 507 the C{self.cur_line} list until a complete logical line has 508 been formed; and then calls L{handle_line} to process that line. 509 """ 510 # If we encounter any errors, then just give up. 511 if toktype == token.ERRORTOKEN: 512 raise tokenize.TokenError, toktype 513 514 # Did we skip anything whitespace? If so, add a pseudotoken 515 # for it, with toktype=None. (Note -- this skipped string 516 # might also contain continuation slashes; but I won't bother 517 # to colorize them.) 518 startpos = self.line_offsets[srow] + scol 519 if startpos > self.pos: 520 skipped = self.text[self.pos:startpos] 521 self.cur_line.append( (None, skipped) ) 522 523 # Update our position. 524 self.pos = startpos + len(toktext) 525 526 # Update our current line. 527 self.cur_line.append( (toktype, toktext) ) 528 529 # When we reach the end of a line, process it. 530 if toktype == token.NEWLINE or toktype == token.ENDMARKER: 531 self.handle_line(self.cur_line) 532 self.cur_line = []
533 534 _next_uid = 0 535 536 # [xx] note -- this works with byte strings, not unicode strings! 537 # I may change it to use unicode eventually, but when I do it 538 # needs to be changed all at once.
539 - def handle_line(self, line):
540 """ 541 Render a single logical line from the module, and write the 542 generated HTML to C{self.out}. 543 544 @param line: A single logical line, encoded as a list of 545 C{(toktype,tokttext)} pairs corresponding to the tokens in 546 the line. 547 """ 548 # def_name is the name of the function or class defined by 549 # this line; or None if no funciton or class is defined. 550 def_name = None 551 552 # def_type is the type of the function or class defined by 553 # this line; or None if no funciton or class is defined. 554 def_type = None 555 556 # does this line start a class/func def? 557 starting_def_block = False 558 559 in_base_list = False 560 in_param_list = False 561 in_param_default = 0 562 at_module_top = (self.lineno == 1) 563 564 ended_def_blocks = 0 565 566 # The html output. 567 if self.ADD_LINE_NUMBERS: 568 s = self.lineno_to_html() 569 self.lineno += 1 570 else: 571 s = '' 572 s += ' <tt class="py-line">' 573 574 # Loop through each token, and colorize it appropriately. 575 for i, (toktype, toktext) in enumerate(line): 576 if type(s) is not str: 577 if type(s) is unicode: 578 log.error('While colorizing %s -- got unexpected ' 579 'unicode string' % self.module_name) 580 s = s.encode('ascii', 'xmlcharrefreplace') 581 else: 582 raise ValueError('Unexpected value for s -- %s' % 583 type(s).__name__) 584 585 # For each token, determine its css class and whether it 586 # should link to a url. 587 css_class = None 588 url = None 589 tooltip = None 590 onclick = uid = targets = None # these 3 are used together. 591 592 # Is this token the class name in a class definition? If 593 # so, then make it a link back into the API docs. 594 if i>=2 and line[i-2][1] == 'class': 595 in_base_list = True 596 css_class = self.CSS_CLASSES['DEFNAME'] 597 def_name = toktext 598 def_type = 'class' 599 if 'func' not in self.context_types: 600 cls_name = self.context_name(def_name) 601 url = self.name2url(cls_name) 602 s = self.mark_def(s, cls_name) 603 starting_def_block = True 604 605 # Is this token the function name in a function def? If 606 # so, then make it a link back into the API docs. 607 elif i>=2 and line[i-2][1] == 'def': 608 in_param_list = True 609 css_class = self.CSS_CLASSES['DEFNAME'] 610 def_name = toktext 611 def_type = 'func' 612 if 'func' not in self.context_types: 613 cls_name = self.context_name() 614 func_name = self.context_name(def_name) 615 url = self.name2url(cls_name, def_name) 616 s = self.mark_def(s, func_name) 617 starting_def_block = True 618 619 # For each indent, update the indents list (which we use 620 # to keep track of indentation strings) and the context 621 # list. If this indent is the start of a class or 622 # function def block, then self.def_name will be its name; 623 # otherwise, it will be None. 624 elif toktype == token.INDENT: 625 self.indents.append(toktext) 626 self.context.append(self.def_name) 627 self.context_types.append(self.def_type) 628 629 # When we dedent, pop the last elements off the indents 630 # list and the context list. If the last context element 631 # is a name, then we're ending a class or function def 632 # block; so write an end-div tag. 633 elif toktype == token.DEDENT: 634 self.indents.pop() 635 self.context_types.pop() 636 if self.context.pop(): 637 ended_def_blocks += 1 638 639 # If this token contains whitespace, then don't bother to 640 # give it a css tag. 641 elif toktype in (None, tokenize.NL, token.NEWLINE, 642 token.ENDMARKER): 643 css_class = None 644 645 # Check if the token is a keyword. 646 elif toktype == token.NAME and keyword.iskeyword(toktext): 647 css_class = self.CSS_CLASSES['KEYWORD'] 648 649 elif in_base_list and toktype == token.NAME: 650 css_class = self.CSS_CLASSES['BASECLASS'] 651 652 elif (in_param_list and toktype == token.NAME and 653 not in_param_default): 654 css_class = self.CSS_CLASSES['PARAM'] 655 656 # Class/function docstring. 657 elif (self.def_name and line[i-1][0] == token.INDENT and 658 self.is_docstring(line, i)): 659 css_class = self.CSS_CLASSES['DOCSTRING'] 660 661 # Module docstring. 662 elif at_module_top and self.is_docstring(line, i): 663 css_class = self.CSS_CLASSES['DOCSTRING'] 664 665 # check for decorators?? 666 elif (toktype == token.NAME and 667 ((i>0 and line[i-1][1]=='@') or 668 (i>1 and line[i-1][0]==None and line[i-2][1] == '@'))): 669 css_class = self.CSS_CLASSES['DECORATOR'] 670 self.has_decorators = True 671 672 # If it's a name, try to link it. 673 elif toktype == token.NAME: 674 css_class = self.CSS_CLASSES['NAME'] 675 # If we have a variable named `toktext` in the current 676 # context, then link to that. Note that if we're inside 677 # a function, then that function is our context, not 678 # the namespace that contains it. [xx] this isn't always 679 # the right thing to do. 680 if (self.GUESS_LINK_TARGETS and self.docindex is not None 681 and self.url_func is not None): 682 context = [n for n in self.context if n is not None] 683 container = self.docindex.get_vardoc( 684 DottedName(self.module_name, *context)) 685 if isinstance(container, NamespaceDoc): 686 doc = container.variables.get(toktext) 687 if doc is not None: 688 url = self.url_func(doc) 689 tooltip = str(doc.canonical_name) 690 # Otherwise, check the name_to_docs index to see what 691 # else this name might refer to. 692 if (url is None and self.name_to_docs is not None 693 and self.url_func is not None): 694 docs = self.name_to_docs.get(toktext) 695 if docs: 696 tooltip='\n'.join([str(d.canonical_name) 697 for d in docs]) 698 if len(docs) == 1 and self.GUESS_LINK_TARGETS: 699 url = self.url_func(docs[0]) 700 else: 701 uid, onclick, targets = self.doclink(toktext, docs) 702 703 # For all other tokens, look up the CSS class to use 704 # based on the token's type. 705 else: 706 if toktype == token.OP and toktext in self.CSS_CLASSES: 707 css_class = self.CSS_CLASSES[toktext] 708 elif token.tok_name[toktype] in self.CSS_CLASSES: 709 css_class = self.CSS_CLASSES[token.tok_name[toktype]] 710 else: 711 css_class = None 712 713 # update our status.. 714 if toktext == ':': 715 in_base_list = False 716 in_param_list = False 717 if toktext == '=' and in_param_list: 718 in_param_default = True 719 if in_param_default: 720 if toktext in ('(','[','{'): in_param_default += 1 721 if toktext in (')',']','}'): in_param_default -= 1 722 if toktext == ',' and in_param_default == 1: 723 in_param_default = 0 724 725 # Write this token, with appropriate colorization. 726 if tooltip and self.ADD_TOOLTIPS: 727 tooltip_html = ' title="%s"' % tooltip 728 else: tooltip_html = '' 729 if css_class: css_class_html = ' class="%s"' % css_class 730 else: css_class_html = '' 731 if onclick: 732 if targets: targets_html = ' targets="%s"' % targets 733 else: targets_html = '' 734 s += ('<tt id="%s"%s%s><a%s%s href="#" onclick="%s">' % 735 (uid, css_class_html, targets_html, tooltip_html, 736 css_class_html, onclick)) 737 elif url: 738 if isinstance(url, unicode): 739 url = url.encode('ascii', 'xmlcharrefreplace') 740 s += ('<a%s%s href="%s">' % 741 (tooltip_html, css_class_html, url)) 742 elif css_class_html or tooltip_html: 743 s += '<tt%s%s>' % (tooltip_html, css_class_html) 744 if i == len(line)-1: 745 s += ' </tt>' # Closes <tt class="py-line"> 746 s += cgi.escape(toktext) 747 else: 748 try: 749 s += self.add_line_numbers(cgi.escape(toktext), css_class) 750 except Exception, e: 751 print (toktext, css_class, toktext.encode('ascii')) 752 raise 753 754 if onclick: s += "</a></tt>" 755 elif url: s += '</a>' 756 elif css_class_html or tooltip_html: s += '</tt>' 757 758 if self.ADD_DEF_BLOCKS: 759 for i in range(ended_def_blocks): 760 self.out(self.END_DEF_BLOCK) 761 762 # Strip any empty <tt>s. 763 s = re.sub(r'<tt class="[\w+]"></tt>', '', s) 764 765 # Write the line. 766 self.out(s) 767 768 if def_name and starting_def_block: 769 self.out('</div>') 770 771 # Add div's if we're starting a def block. 772 if (self.ADD_DEF_BLOCKS and def_name and starting_def_block and 773 (line[-2][1] == ':')): 774 indentation = (''.join(self.indents)+' ').replace(' ', '+') 775 linenum_padding = '+'*self.linenum_size 776 name=self.context_name(def_name) 777 self.out(self.START_DEF_BLOCK % (name, linenum_padding, 778 indentation, name)) 779 780 self.def_name = def_name 781 self.def_type = def_type
782
783 - def context_name(self, extra=None):
784 pieces = [n for n in self.context if n is not None] 785 if extra is not None: pieces.append(extra) 786 return '.'.join(pieces)
787 808
809 - def doc_descr(self, doc, context):
810 name = str(doc.canonical_name) 811 descr = '%s %s' % (self.doc_kind(doc), name) 812 if isinstance(doc, RoutineDoc): 813 descr += '()' 814 return descr
815 816 # [XX] copied streight from html.py; this should be consolidated, 817 # probably into apidoc.
818 - def doc_kind(self, doc):
819 if isinstance(doc, ModuleDoc) and doc.is_package == True: 820 return 'Package' 821 elif (isinstance(doc, ModuleDoc) and 822 doc.canonical_name[0].startswith('script')): 823 return 'Script' 824 elif isinstance(doc, ModuleDoc): 825 return 'Module' 826 elif isinstance(doc, ClassDoc): 827 return 'Class' 828 elif isinstance(doc, ClassMethodDoc): 829 return 'Class Method' 830 elif isinstance(doc, StaticMethodDoc): 831 return 'Static Method' 832 elif isinstance(doc, RoutineDoc): 833 if (self.docindex is not None and 834 isinstance(self.docindex.container(doc), ClassDoc)): 835 return 'Method' 836 else: 837 return 'Function' 838 else: 839 return 'Variable'
840
841 - def mark_def(self, s, name):
842 replacement = ('<a name="%s"></a><div id="%s-def">\\1' 843 '<a class="py-toggle" href="#" id="%s-toggle" ' 844 'onclick="return toggle(\'%s\');">-</a>\\2' % 845 (name, name, name, name)) 846 return re.sub('(.*) (<tt class="py-line">.*)\Z', replacement, s)
847
848 - def is_docstring(self, line, i):
849 if line[i][0] != token.STRING: return False 850 for toktype, toktext in line[i:]: 851 if toktype not in (token.NEWLINE, tokenize.COMMENT, 852 tokenize.NL, token.STRING, None): 853 return False 854 return True
855
856 - def add_line_numbers(self, s, css_class):
857 result = '' 858 start = 0 859 end = s.find('\n')+1 860 while end: 861 result += s[start:end-1] 862 if css_class: result += '</tt>' 863 result += ' </tt>' # py-line 864 result += '\n' 865 if self.ADD_LINE_NUMBERS: 866 result += self.lineno_to_html() 867 result += ' <tt class="py-line">' 868 if css_class: result += '<tt class="%s">' % css_class 869 start = end 870 end = s.find('\n', end)+1 871 self.lineno += 1 872 result += s[start:] 873 return result
874
875 - def name2url(self, class_name, func_name=None):
876 if class_name: 877 class_name = '%s.%s' % (self.module_name, class_name) 878 if func_name: 879 return '%s-class.html#%s' % (class_name, func_name) 880 else: 881 return '%s-class.html' % class_name 882 else: 883 return '%s-module.html#%s' % (self.module_name, func_name)
884 885 #: A regexp used to move the <div> that marks the beginning of a 886 #: function or method to just before the decorators. 887 _FIX_DECORATOR_RE = re.compile( 888 r'((?:^<a name="L\d+"></a><tt class="py-lineno">\s*\d+</tt>' 889 r'\s*<tt class="py-line">(?:<tt class="py-decorator">.*|\s*</tt>|' 890 r'\s*<tt class="py-comment">.*)\n)+)' 891 r'(<a name="\w+"></a><div id="\w+-def">)', re.MULTILINE)
892 893 _HDR = '''\ 894 <?xml version="1.0" encoding="ascii"?> 895 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 896 "DTD/xhtml1-transitional.dtd"> 897 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 898 <head> 899 <title>$title$</title> 900 <link rel="stylesheet" href="epydoc.css" type="text/css" /> 901 <script type="text/javascript" src="epydoc.js"></script> 902 </head> 903 904 <body bgcolor="white" text="black" link="blue" vlink="#204080" 905 alink="#204080"> 906 ''' 907 _FOOT = '</body></html>' 908 if __name__=='__main__': 909 #s = PythonSourceColorizer('../apidoc.py', 'epydoc.apidoc').colorize() 910 s = PythonSourceColorizer('/tmp/fo.py', 'epydoc.apidoc').colorize() 911 #print s 912 import codecs 913 f = codecs.open('/home/edloper/public_html/color3.html', 'w', 'ascii', 'xmlcharrefreplace') 914 f.write(_HDR+'<pre id="py-src-top" class="py-src">'+s+'</pre>'+_FOOT) 915 f.close() 916