1
2
3
4
5
6
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
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 *
84 from epydoc.markup import *
85 from epydoc.apidoc import ModuleDoc, ClassDoc
86 from epydoc.docwriter.dotgraph import *
87
88
89
90
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
104
105
106
107 CONSOLIDATED_DEFLIST_FIELDS = ['param', 'arg', 'var', 'ivar', 'cvar', 'keyword']
108
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)
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
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 """
142 """
143 @type document: L{docutils.nodes.document}
144 """
145 self._document = document
146
148
149 visitor = _SplitFieldsTranslator(self._document, errors)
150 self._document.walk(visitor)
151 return self, visitor.fields
152
154
155 visitor = _SummaryExtractor(self._document)
156 try: self._document.walk(visitor)
157 except docutils.nodes.NodeFound: pass
158 return visitor.summary
159
160
161
162
163
164
165
166
167
168
169 - def to_html(self, docstring_linker, directory=None,
170 docindex=None, context=None, **options):
171
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
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
185 return self._document.astext()
186
187 - def __repr__(self): return '<ParsedRstDocstring: ...>'
188
190 """
191 A reader that captures all errors that are generated by parsing,
192 and appends them to a list.
193 """
195 self._errors = errors
196 StandaloneReader.__init__(self)
197
207
218
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 """
233
235 self.output = ''
236
238 """
239 A docutils node visitor that extracts the first sentence from
240 the first paragraph in a document.
241 """
245
248
250 if self.summary is not None: return
251
252 summary_pieces = []
253
254
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()
264 summary_para = node.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
271 'Ignore all unknown nodes'
272
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 """
282 NodeVisitor.__init__(self, document)
283 self._errors = errors
284 self.fields = []
285 self._newfields = {}
286
288 self.fields = []
289
291
292 node.parent.remove(node)
293
294
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
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
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
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
330
331
332 node.parent.remove(node)
333
350
352
353
354
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
377 for item in items:
378
379 arg = item[0][0].astext()
380
381
382 fbody = item[:]
383 fbody[0] = fbody[0].copy()
384 fbody[0][:] = item[0][1:]
385
386
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
396 self._add_field(tagname, arg, fbody)
397
399
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
420 for item in items:
421
422 arg = item[0][0].astext()
423 fbody = item[-1]
424 self._add_field(tagname, arg, fbody)
425
426 if len(item) == 3:
427 type_descr = item[1]
428 self._add_field('type', arg, type_descr)
429
431 'Ignore all unknown nodes'
432
437
440
441 settings = OptionParser([LaTeXWriter()]).get_default_values()
442 document.settings = settings
443
444 LaTeXTranslator.__init__(self, document)
445 self._linker = docstring_linker
446
447
448
449
450 self.section_level = 3
451 self._section_number = [0]*self.section_level
452
453
455 target = self.encode(node.astext())
456 xref = self._linker.translate_identifier_xref(target, target)
457 self.body.append(xref)
458 raise SkipNode
459
462
463
466
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
476 settings = OptionParser([HTMLWriter()]).get_default_values()
477 document.settings = settings
478
479
480 HTMLTranslator.__init__(self, document)
481
482
484 target = self.encode(node.astext())
485 xref = self._linker.translate_identifier_xref(target, target)
486 self.body.append(xref)
487 raise SkipNode
488
494
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
509 if attributes.has_key('class'):
510 attributes['class'] = 'rst-%s' % attributes['class']
511
512
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
524 if re.match(r'^h\d+$', tagname):
525 attributes['class'] = 'heading'
526
527 return HTMLTranslator.starttag(self, node, tagname, suffix,
528 **attributes)
529
531 if self._directory is None: return
532
533
534 graph = node.graph(self._docindex, self._context, self._linker)
535 if graph is None: return
536
537
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
543 pass
544
545
546
547
548
549
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
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
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
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
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):
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
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):
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
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