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

Source Code for Module epydoc.markup.restructuredtext

  1  # 
  2  # rst.py: ReStructuredText docstring parsing 
  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  Epydoc parser for ReStructuredText strings.  ReStructuredText is the 
 11  standard markup language used by the Docutils project. 
 12  L{parse_docstring()} provides the primary interface to this module; it 
 13  returns a L{ParsedRstDocstring}, which supports all of the methods 
 14  defined by L{ParsedDocstring}. 
 15   
 16  L{ParsedRstDocstring} is basically just a L{ParsedDocstring} wrapper 
 17  for the L{docutils.nodes.document} class. 
 18   
 19  Creating C{ParsedRstDocstring}s 
 20  =============================== 
 21  C{ParsedRstDocstring}s are created by the C{parse_document} function, 
 22  using the L{docutils.core.publish_string()} method, with the following 
 23  helpers: 
 24   
 25    - An L{_EpydocReader} is used to capture all error messages as it 
 26      parses the docstring. 
 27    - A L{_DocumentPseudoWriter} is used to extract the document itself, 
 28      without actually writing any output.  The document is saved for 
 29      further processing.  The settings for the writer are copied from 
 30      L{docutils.writers.html4css1.Writer}, since those settings will 
 31      be used when we actually write the docstring to html. 
 32   
 33  Using C{ParsedRstDocstring}s 
 34  ============================ 
 35   
 36  C{ParsedRstDocstring}s support all of the methods defined by 
 37  C{ParsedDocstring}; but only the following four methods have 
 38  non-default behavior: 
 39   
 40    - L{to_html()<ParsedRstDocstring.to_html>} uses an 
 41      L{_EpydocHTMLTranslator} to translate the C{ParsedRstDocstring}'s 
 42      document into an HTML segment. 
 43    - L{split_fields()<ParsedRstDocstring.split_fields>} uses a 
 44      L{_SplitFieldsTranslator} to divide the C{ParsedRstDocstring}'s 
 45      document into its main body and its fields.  Special handling 
 46      is done to account for consolidated fields. 
 47    - L{summary()<ParsedRstDocstring.summary>} uses a 
 48      L{_SummaryExtractor} to extract the first sentence from 
 49      the C{ParsedRstDocstring}'s document. 
 50    - L{to_plaintext()<ParsedRstDocstring.to_plaintext>} uses 
 51      C{document.astext()} to convert the C{ParsedRstDocstring}'s 
 52      document to plaintext. 
 53   
 54  @todo: Add ParsedRstDocstring.to_latex() 
 55  @var CONSOLIDATED_FIELDS: A dictionary encoding the set of 
 56  'consolidated fields' that can be used.  Each consolidated field is 
 57  marked by a single tag, and contains a single bulleted list, where 
 58  each list item starts with an identifier, marked as interpreted text 
 59  (C{`...`}).  This module automatically splits these consolidated 
 60  fields into individual fields.  The keys of C{CONSOLIDATED_FIELDS} are 
 61  the names of possible consolidated fields; and the values are the 
 62  names of the field tags that should be used for individual entries in 
 63  the list. 
 64  """ 
 65  __docformat__ = 'epytext en' 
 66   
 67  # Imports 
 68  import re, os, os.path 
 69  from xml.dom.minidom import * 
 70   
 71  from docutils.core import publish_string 
 72  from docutils.writers import Writer 
 73  from docutils.writers.html4css1 import HTMLTranslator, Writer as HTMLWriter 
 74  from docutils.writers.latex2e import LaTeXTranslator, Writer as LaTeXWriter 
 75  from docutils.readers.standalone import Reader as StandaloneReader 
 76  from docutils.utils import new_document 
 77  from docutils.nodes import NodeVisitor, Text, SkipChildren 
 78  from docutils.nodes import SkipNode, TreeCopyVisitor 
 79  from docutils.frontend import OptionParser 
 80  from docutils.parsers.rst import directives 
 81  import docutils.nodes 
 82   
 83  from epydoc.compat import * # Backwards compatibility 
 84  from epydoc.markup import * 
 85  from epydoc.apidoc import ModuleDoc, ClassDoc 
 86  from epydoc.docwriter.dotgraph import * 
 87   
 88  #: A dictionary whose keys are the "consolidated fields" that are 
 89  #: recognized by epydoc; and whose values are the corresponding epydoc 
 90  #: field names that should be used for the individual fields. 
 91  CONSOLIDATED_FIELDS = { 
 92      'parameters': 'param', 
 93      'arguments': 'arg', 
 94      'exceptions': 'except', 
 95      'variables': 'var', 
 96      'ivariables': 'ivar', 
 97      'cvariables': 'cvar', 
 98      'groups': 'group', 
 99      'types': 'type', 
100      'keywords': 'keyword', 
101      } 
102   
103  #: A list of consolidated fields whose bodies may be specified using a 
104  #: definition list, rather than a bulleted list.  For these fields, the 
105  #: 'classifier' for each term in the definition list is translated into 
106  #: a @type field. 
107  CONSOLIDATED_DEFLIST_FIELDS = ['param', 'arg', 'var', 'ivar', 'cvar', 'keyword'] 
108   
109 -def parse_docstring(docstring, errors, **options):
110 """ 111 Parse the given docstring, which is formatted using 112 ReStructuredText; and return a L{ParsedDocstring} representation 113 of its contents. 114 @param docstring: The docstring to parse 115 @type docstring: C{string} 116 @param errors: A list where any errors generated during parsing 117 will be stored. 118 @type errors: C{list} of L{ParseError} 119 @param options: Extra options. Unknown options are ignored. 120 Currently, no extra options are defined. 121 @rtype: L{ParsedDocstring} 122 """ 123 writer = _DocumentPseudoWriter() 124 reader = _EpydocReader(errors) # Outputs errors to the list. 125 publish_string(docstring, writer=writer, reader=reader, 126 settings_overrides={'report_level':10000, 127 'halt_level':10000, 128 'warning_stream':None}) 129 return ParsedRstDocstring(writer.document)
130
131 -class ParsedRstDocstring(ParsedDocstring):
132 """ 133 An encoded version of a ReStructuredText docstring. The contents 134 of the docstring are encoded in the L{_document} instance 135 variable. 136 137 @ivar _document: A ReStructuredText document, encoding the 138 docstring. 139 @type _document: L{docutils.nodes.document} 140 """
141 - def __init__(self, document):
142 """ 143 @type document: L{docutils.nodes.document} 144 """ 145 self._document = document
146
147 - def split_fields(self, errors=None):
148 # Inherit docs 149 visitor = _SplitFieldsTranslator(self._document, errors) 150 self._document.walk(visitor) 151 return self, visitor.fields
152
153 - def summary(self):
154 # Inherit docs 155 visitor = _SummaryExtractor(self._document) 156 try: self._document.walk(visitor) 157 except docutils.nodes.NodeFound: pass 158 return visitor.summary
159 160 # def concatenate(self, other): 161 # result = self._document.copy() 162 # for child in (self._document.get_children() + 163 # other._document.get_children()): 164 # visitor = TreeCopyVisitor(self._document) 165 # child.walkabout(visitor) 166 # result.append(visitor.get_tree_copy()) 167 # return ParsedRstDocstring(result) 168
169 - def to_html(self, docstring_linker, directory=None, 170 docindex=None, context=None, **options):
171 # Inherit docs 172 visitor = _EpydocHTMLTranslator(self._document, docstring_linker, 173 directory, docindex, context) 174 self._document.walkabout(visitor) 175 return ''.join(visitor.body)
176
177 - def to_latex(self, docstring_linker, **options):
178 # Inherit docs 179 visitor = _EpydocLaTeXTranslator(self._document, docstring_linker) 180 self._document.walkabout(visitor) 181 return ''.join(visitor.body)
182
183 - def to_plaintext(self, docstring_linker, **options):
184 # This is should be replaced by something better: 185 return self._document.astext()
186
187 - def __repr__(self): return '<ParsedRstDocstring: ...>'
188
189 -class _EpydocReader(StandaloneReader):
190 """ 191 A reader that captures all errors that are generated by parsing, 192 and appends them to a list. 193 """
194 - def __init__(self, errors):
195 self._errors = errors 196 StandaloneReader.__init__(self)
197
198 - def new_document(self):
199 document = new_document(self.source.source_path, self.settings) 200 # Capture all warning messages. 201 document.reporter.attach_observer(self.report) 202 # These are used so we know how to encode warning messages: 203 self._encoding = document.reporter.encoding 204 self._error_handler = document.reporter.error_handler 205 # Return the new document. 206 return document
207
208 - def report(self, error):
209 try: is_fatal = int(error['level']) > 2 210 except: is_fatal = 1 211 try: linenum = int(error['line']) 212 except: linenum = None 213 214 msg = ''.join([c.astext().encode(self._encoding, self._error_handler) 215 for c in error]) 216 217 self._errors.append(ParseError(msg, linenum, is_fatal))
218
219 -class _DocumentPseudoWriter(Writer):
220 """ 221 A pseudo-writer for the docutils framework, that can be used to 222 access the document itself. The output of C{_DocumentPseudoWriter} 223 is just an empty string; but after it has been used, the most 224 recently processed document is available as the instance variable 225 C{document} 226 227 @type document: L{docutils.nodes.document} 228 @ivar document: The most recently processed document. 229 """
230 - def __init__(self):
231 self.document = None 232 Writer.__init__(self)
233
234 - def translate(self):
235 self.output = ''
236
237 -class _SummaryExtractor(NodeVisitor):
238 """ 239 A docutils node visitor that extracts the first sentence from 240 the first paragraph in a document. 241 """
242 - def __init__(self, document):
243 NodeVisitor.__init__(self, document) 244 self.summary = None
245
246 - def visit_document(self, node):
247 self.summary = None
248
249 - def visit_paragraph(self, node):
250 if self.summary is not None: return 251 252 summary_pieces = [] 253 254 # Extract the first sentence. 255 for child in node: 256 if isinstance(child, docutils.nodes.Text): 257 m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', child.data) 258 if m: 259 summary_pieces.append(docutils.nodes.Text(m.group(1))) 260 break 261 summary_pieces.append(child) 262 263 summary_doc = self.document.copy() # shallow copy 264 summary_para = node.copy() # shallow copy 265 summary_doc[:] = [summary_para] 266 summary_para[:] = summary_pieces 267 self.summary = ParsedRstDocstring(summary_doc) 268 raise docutils.nodes.NodeFound('Found summary')
269
270 - def unknown_visit(self, node):
271 'Ignore all unknown nodes'
272
273 -class _SplitFieldsTranslator(NodeVisitor):
274 """ 275 A docutils translator that removes all fields from a document, and 276 collects them into the instance variable C{fields} 277 278 @ivar fields: The fields of the most recently walked document. 279 @type fields: C{list} of L{Field<markup.Field>} 280 """
281 - def __init__(self, document, errors):
282 NodeVisitor.__init__(self, document) 283 self._errors = errors 284 self.fields = [] 285 self._newfields = {}
286
287 - def visit_document(self, node):
288 self.fields = []
289
290 - def visit_field(self, node):
291 # Remove the field from the tree. 292 node.parent.remove(node) 293 294 # Extract the field name & optional argument 295 tag = node[0].astext().split(None, 1) 296 tagname = tag[0] 297 if len(tag)>1: arg = tag[1] 298 else: arg = None 299 300 # Handle special fields: 301 fbody = node[1] 302 if arg is None: 303 for (list_tag, entry_tag) in CONSOLIDATED_FIELDS.items(): 304 if tagname.lower() == list_tag: 305 try: 306 self.handle_consolidated_field(fbody, entry_tag) 307 return 308 except ValueError, e: 309 estr = 'Unable to split consolidated field ' 310 estr += '"%s" - %s' % (tagname, e) 311 self._errors.append(ParseError(estr, node.line, 312 is_fatal=0)) 313 314 # Use a @newfield to let it be displayed as-is. 315 if not self._newfields.has_key(tagname.lower()): 316 newfield = Field('newfield', tagname.lower(), 317 parse(tagname, 'plaintext')) 318 self.fields.append(newfield) 319 self._newfields[tagname.lower()] = 1 320 321 self._add_field(tagname, arg, fbody)
322
323 - def _add_field(self, tagname, arg, fbody):
324 field_doc = self.document.copy() 325 for child in fbody: field_doc.append(child) 326 field_pdoc = ParsedRstDocstring(field_doc) 327 self.fields.append(Field(tagname, arg, field_pdoc))
328
329 - def visit_field_list(self, node):
330 # Remove the field list from the tree. The visitor will still walk 331 # over the node's children. 332 node.parent.remove(node)
333
334 - def handle_consolidated_field(self, body, tagname):
335 """ 336 Attempt to handle a consolidated section. 337 """ 338 if len(body) != 1: 339 raise ValueError('does not contain a single list.') 340 elif body[0].tagname == 'bullet_list': 341 self.handle_consolidated_bullet_list(body[0], tagname) 342 elif (body[0].tagname == 'definition_list' and 343 tagname in CONSOLIDATED_DEFLIST_FIELDS): 344 self.handle_consolidated_definition_list(body[0], tagname) 345 elif tagname in CONSOLIDATED_DEFLIST_FIELDS: 346 raise ValueError('does not contain a bulleted list or ' 347 'definition list.') 348 else: 349 raise ValueError('does not contain a bulleted list.')
350
351 - def handle_consolidated_bullet_list(self, items, tagname):
352 # Check the contents of the list. In particular, each list 353 # item should have the form: 354 # - `arg`: description... 355 n = 0 356 _BAD_ITEM = ("list item %d is not well formed. Each item must " 357 "consist of a single marked identifier (e.g., `x`), " 358 "optionally followed by a colon or dash and a " 359 "description.") 360 for item in items: 361 n += 1 362 if item.tagname != 'list_item' or len(item) == 0: 363 raise ValueError('bad bulleted list (bad child %d).' % n) 364 if item[0].tagname != 'paragraph': 365 if item[0].tagname == 'definition_list': 366 raise ValueError(('list item %d contains a definition '+ 367 'list (it\'s probably indented '+ 368 'wrong).') % n) 369 else: 370 raise ValueError(_BAD_ITEM % n) 371 if len(item[0]) == 0: 372 raise ValueError(_BAD_ITEM % n) 373 if item[0][0].tagname != 'title_reference': 374 raise ValueError(_BAD_ITEM % n) 375 376 # Everything looks good; convert to multiple fields. 377 for item in items: 378 # Extract the arg 379 arg = item[0][0].astext() 380 381 # Extract the field body, and remove the arg 382 fbody = item[:] 383 fbody[0] = fbody[0].copy() 384 fbody[0][:] = item[0][1:] 385 386 # Remove the separating ":", if present 387 if (len(fbody[0]) > 0 and 388 isinstance(fbody[0][0], docutils.nodes.Text)): 389 child = fbody[0][0] 390 if child.data[:1] in ':-': 391 child.data = child.data[1:].lstrip() 392 elif child.data[:2] == ' -': 393 child.data = child.data[2:].lstrip() 394 395 # Wrap the field body, and add a new field 396 self._add_field(tagname, arg, fbody)
397
398 - def handle_consolidated_definition_list(self, items, tagname):
399 # Check the list contents. 400 n = 0 401 _BAD_ITEM = ("item %d is not well formed. Each item's term must " 402 "consist of a single marked identifier (e.g., `x`), " 403 "optionally followed by a space, colon, space, and " 404 "a type description.") 405 for item in items: 406 n += 1 407 if (item.tagname != 'definition_list_item' or len(item) < 2 or 408 item[0].tagname != 'term' or 409 item[-1].tagname != 'definition'): 410 raise ValueError('bad definition list (bad child %d).' % n) 411 if len(item) > 3: 412 raise ValueError(_BAD_ITEM % n) 413 if item[0][0].tagname != 'title_reference': 414 raise ValueError(_BAD_ITEM % n) 415 for child in item[0][1:]: 416 if child.astext() != '': 417 raise ValueError(_BAD_ITEM % n) 418 419 # Extract it. 420 for item in items: 421 # The basic field. 422 arg = item[0][0].astext() 423 fbody = item[-1] 424 self._add_field(tagname, arg, fbody) 425 # If there's a classifier, treat it as a type. 426 if len(item) == 3: 427 type_descr = item[1] 428 self._add_field('type', arg, type_descr)
429
430 - def unknown_visit(self, node):
431 'Ignore all unknown nodes'
432
433 -def latex_head_prefix():
434 document = new_document('<fake>') 435 translator = _EpydocLaTeXTranslator(document, None) 436 return translator.head_prefix
437
438 -class _EpydocLaTeXTranslator(LaTeXTranslator):
439 - def __init__(self, document, docstring_linker):
440 # Set the document's settings. 441 settings = OptionParser([LaTeXWriter()]).get_default_values() 442 document.settings = settings 443 444 LaTeXTranslator.__init__(self, document) 445 self._linker = docstring_linker 446 447 # Start at section level 3. (Unfortunately, we now have to 448 # set a private variable to make this work; perhaps the standard 449 # latex translator should grow an official way to spell this?) 450 self.section_level = 3 451 self._section_number = [0]*self.section_level
452 453 # Handle interpreted text (crossreferences)
454 - def visit_title_reference(self, node):
455 target = self.encode(node.astext()) 456 xref = self._linker.translate_identifier_xref(target, target) 457 self.body.append(xref) 458 raise SkipNode
459
460 - def visit_document(self, node): pass
461 - def depart_document(self, node): pass
462 463 # For now, just ignore dotgraphs. [XXX]
464 - def visit_dotgraph(self, node): pass
465 - def depart_dotgraph(self, node): pass
466
467 -class _EpydocHTMLTranslator(HTMLTranslator):
468 - def __init__(self, document, docstring_linker, directory, 469 docindex, context):
470 self._linker = docstring_linker 471 self._directory = directory 472 self._docindex = docindex 473 self._context = context 474 475 # Set the document's settings. 476 settings = OptionParser([HTMLWriter()]).get_default_values() 477 document.settings = settings 478 479 # Call the parent constructor. 480 HTMLTranslator.__init__(self, document)
481 482 # Handle interpreted text (crossreferences)
483 - def visit_title_reference(self, node):
484 target = self.encode(node.astext()) 485 xref = self._linker.translate_identifier_xref(target, target) 486 self.body.append(xref) 487 raise SkipNode
488
489 - def should_be_compact_paragraph(self, node):
490 if self.document.children == [node]: 491 return True 492 else: 493 return HTMLTranslator.should_be_compact_paragraph(self, node)
494
495 - def visit_document(self, node): pass
496 - def depart_document(self, node): pass
497
498 - def starttag(self, node, tagname, suffix='\n', **attributes):
499 """ 500 This modified version of starttag makes a few changes to HTML 501 tags, to prevent them from conflicting with epydoc. In particular: 502 - existing class attributes are prefixed with C{'rst-'} 503 - existing names are prefixed with C{'rst-'} 504 - hrefs starting with C{'#'} are prefixed with C{'rst-'} 505 - hrefs not starting with C{'#'} are given target='_top' 506 - all headings (C{<hM{n}>}) are given the css class C{'heading'} 507 """ 508 # Prefix all CSS classes with "rst-" 509 if attributes.has_key('class'): 510 attributes['class'] = 'rst-%s' % attributes['class'] 511 512 # Prefix all names with "rst-", to avoid conflicts 513 if attributes.has_key('id'): 514 attributes['id'] = 'rst-%s' % attributes['id'] 515 if attributes.has_key('name'): 516 attributes['name'] = 'rst-%s' % attributes['name'] 517 if attributes.has_key('href'): 518 if attributes['href'][:1]=='#': 519 attributes['href'] = '#rst-%s' % attributes['href'][1:] 520 else: 521 attributes['target'] = '_top' 522 523 # For headings, use class="heading" 524 if re.match(r'^h\d+$', tagname): 525 attributes['class'] = 'heading' 526 527 return HTMLTranslator.starttag(self, node, tagname, suffix, 528 **attributes)
529
530 - def visit_dotgraph(self, node):
531 if self._directory is None: return # [xx] warning? 532 533 # Generate the graph. 534 graph = node.graph(self._docindex, self._context, self._linker) 535 if graph is None: return 536 537 # Write the graph. 538 image_url = '%s.gif' % graph.uid 539 image_file = os.path.join(self._directory, image_url) 540 self.body.append(graph.to_html(image_file, image_url))
541
542 - def depart_dotgraph(self, node):
543 pass # Nothing to do.
544 545 ###################################################################### 546 #{ Graph Generation Directives 547 ###################################################################### 548 # See http://docutils.sourceforge.net/docs/howto/rst-directives.html 549
550 -class dotgraph(docutils.nodes.image):
551 """ 552 A custom docutils node that should be rendered using Graphviz dot. 553 This node does not directly store the graph; instead, it stores a 554 pointer to a function that can be used to generate the graph. 555 This allows the graph to be built based on information that might 556 not be available yet at parse time. This graph generation 557 function has the following signature: 558 559 >>> def generate_graph(docindex, context, linker, *args): 560 ... 'generates and returns a new DotGraph' 561 562 Where C{docindex} is a docindex containing the documentation that 563 epydoc has built; C{context} is the C{APIDoc} whose docstring 564 contains this dotgraph node; C{linker} is a L{DocstringLinker} 565 that can be used to resolve crossreferences; and C{args} is any 566 extra arguments that are passed to the C{dotgraph} constructor. 567 """
568 - def __init__(self, generate_graph_func, *generate_graph_args):
569 docutils.nodes.image.__init__(self) 570 self.graph_func = generate_graph_func 571 self.args = generate_graph_args
572 - def graph(self, docindex, context, linker):
573 return self.graph_func(docindex, context, linker, *self.args)
574
575 -def _dir_option(argument):
576 """A directive option spec for the orientation of a graph.""" 577 argument = argument.lower().strip() 578 if argument == 'right': return 'LR' 579 if argument == 'left': return 'RL' 580 if argument == 'down': return 'TB' 581 if argument == 'up': return 'BT' 582 raise ValueError('%r unknown; choose from left, right, up, down' % 583 argument)
584
585 -def digraph_directive(name, arguments, options, content, lineno, 586 content_offset, block_text, state, state_machine):
587 """ 588 A custom restructuredtext directive which can be used to display 589 Graphviz dot graphs. This directive takes a single argument, 590 which is used as the graph's name. The contents of the directive 591 are used as the body of the graph. Any href attributes whose 592 value has the form <name> will be replaced by the URL of the object 593 with that name. Here's a simple example:: 594 595 .. digraph:: example_digraph 596 a -> b -> c 597 c -> a [dir=\"none\"] 598 """ 599 if arguments: title = arguments[0] 600 else: title = '' 601 return dotgraph(_construct_digraph, title, options.get('caption'), 602 '\n'.join(content))
603 digraph_directive.arguments = (0, 1, True) 604 digraph_directive.options = {'caption': directives.unchanged} 605 digraph_directive.content = True 606 directives.register_directive('digraph', digraph_directive) 607
608 -def _construct_digraph(docindex, context, linker, title, caption, 609 body):
610 """Graph generator for L{digraph_directive}""" 611 graph = DotGraph(title, body, caption=caption) 612 graph.link(linker) 613 return graph
614
615 -def classtree_directive(name, arguments, options, content, lineno, 616 content_offset, block_text, state, state_machine):
617 """ 618 A custom restructuredtext directive which can be used to 619 graphically display a class hierarchy. If one or more arguments 620 are given, then those classes and all their descendants will be 621 displayed. If no arguments are given, and the directive is in a 622 class's docstring, then that class and all its descendants will be 623 displayed. It is an error to use this directive with no arguments 624 in a non-class docstring. 625 626 Options: 627 - C{:dir:} -- Specifies the orientation of the graph. One of 628 C{down}, C{right} (default), C{left}, C{up}. 629 """ 630 return dotgraph(_construct_classtree, arguments, options)
631 classtree_directive.arguments = (0, 1, True) 632 classtree_directive.options = {'dir': _dir_option} 633 classtree_directive.content = False 634 directives.register_directive('classtree', classtree_directive) 635
636 -def _construct_classtree(docindex, context, linker, arguments, options):
637 """Graph generator for L{classtree_directive}""" 638 if len(arguments) == 1: 639 bases = [docindex.find(name, context) for name in 640 arguments[0].replace(',',' ').split()] 641 bases = [d for d in bases if isinstance(d, ClassDoc)] 642 elif isinstance(context, ClassDoc): 643 bases = [context] 644 else: 645 log.warning("Could not construct class tree: you must " 646 "specify one or more base classes.") 647 return None 648 649 return class_tree_graph(bases, linker, context, **options)
650
651 -def packagetree_directive(name, arguments, options, content, lineno, 652 content_offset, block_text, state, state_machine):
653 """ 654 A custom restructuredtext directive which can be used to 655 graphically display a package hierarchy. If one or more arguments 656 are given, then those packages and all their submodules will be 657 displayed. If no arguments are given, and the directive is in a 658 package's docstring, then that package and all its submodules will 659 be displayed. It is an error to use this directive with no 660 arguments in a non-package docstring. 661 662 Options: 663 - C{:dir:} -- Specifies the orientation of the graph. One of 664 C{down}, C{right} (default), C{left}, C{up}. 665 """ 666 return dotgraph(_construct_packagetree, arguments, options)
667 packagetree_directive.arguments = (0, 1, True) 668 packagetree_directive.options = { 669 'dir': _dir_option, 670 'style': lambda a:directives.choice(a.lower(), ('uml', 'tree'))} 671 packagetree_directive.content = False 672 directives.register_directive('packagetree', packagetree_directive) 673
674 -def _construct_packagetree(docindex, context, linker, arguments, options):
675 """Graph generator for L{packagetree_directive}""" 676 if len(arguments) == 1: 677 packages = [docindex.find(name, context) for name in 678 arguments[0].replace(',',' ').split()] 679 packages = [d for d in packages if isinstance(d, ModuleDoc)] 680 elif isinstance(context, ModuleDoc): 681 packages = [context] 682 else: 683 log.warning("Could not construct package tree: you must " 684 "specify one or more root packages.") 685 return None 686 687 return package_tree_graph(packages, linker, context, **options)
688
689 -def importgraph_directive(name, arguments, options, content, lineno, 690 content_offset, block_text, state, state_machine):
691 return dotgraph(_construct_importgraph, arguments, options)
692 importgraph_directive.arguments = None 693 importgraph_directive.options = {'dir': _dir_option} 694 importgraph_directive.content = False 695 directives.register_directive('importgraph', importgraph_directive) 696
697 -def _construct_importgraph(docindex, context, linker, arguments, options):
698 """Graph generator for L{importgraph_directive}""" 699 modules = [d for d in docindex.root if isinstance(d, ModuleDoc)] 700 return import_graph(modules, docindex, linker, context, **options)
701
702 -def callgraph_directive(name, arguments, options, content, lineno, 703 content_offset, block_text, state, state_machine):
704 return dotgraph(_construct_callgraph, arguments, options)
705 callgraph_directive.arguments = (0, 1, True) 706 callgraph_directive.options = {'dir': _dir_option, 707 'add_callers': directives.flag, 708 'add_callees': directives.flag} 709 callgraph_directive.content = False 710 directives.register_directive('callgraph', callgraph_directive) 711
712 -def _construct_callgraph(docindex, context, linker, arguments, options):
713 """Graph generator for L{callgraph_directive}""" 714 if len(arguments) == 1: 715 docs = [docindex.find(name, context) for name in 716 arguments[0].replace(',',' ').split()] 717 docs = [doc for doc in docs if doc is not None] 718 else: 719 docs = [context] 720 return call_graph(docs, docindex, linker, context, **options)
721