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 C{docutils.nodes.document} class.
18
19 Creating C{ParsedRstDocstring}s
20 ===============================
21
22 C{ParsedRstDocstring}s are created by the C{parse_document} function,
23 using the C{docutils.core.publish_string()} method, with the following
24 helpers:
25
26 - An L{_EpydocReader} is used to capture all error messages as it
27 parses the docstring.
28 - A L{_DocumentPseudoWriter} is used to extract the document itself,
29 without actually writing any output. The document is saved for
30 further processing. The settings for the writer are copied from
31 C{docutils.writers.html4css1.Writer}, since those settings will
32 be used when we actually write the docstring to html.
33
34 Using C{ParsedRstDocstring}s
35 ============================
36
37 C{ParsedRstDocstring}s support all of the methods defined by
38 C{ParsedDocstring}; but only the following four methods have
39 non-default behavior:
40
41 - L{to_html()<ParsedRstDocstring.to_html>} uses an
42 L{_EpydocHTMLTranslator} to translate the C{ParsedRstDocstring}'s
43 document into an HTML segment.
44 - L{split_fields()<ParsedRstDocstring.split_fields>} uses a
45 L{_SplitFieldsTranslator} to divide the C{ParsedRstDocstring}'s
46 document into its main body and its fields. Special handling
47 is done to account for consolidated fields.
48 - L{summary()<ParsedRstDocstring.summary>} uses a
49 L{_SummaryExtractor} to extract the first sentence from
50 the C{ParsedRstDocstring}'s document.
51 - L{to_plaintext()<ParsedRstDocstring.to_plaintext>} uses
52 C{document.astext()} to convert the C{ParsedRstDocstring}'s
53 document to plaintext.
54
55 @todo: Add ParsedRstDocstring.to_latex()
56 @var CONSOLIDATED_FIELDS: A dictionary encoding the set of
57 'consolidated fields' that can be used. Each consolidated field is
58 marked by a single tag, and contains a single bulleted list, where
59 each list item starts with an identifier, marked as interpreted text
60 (C{`...`}). This module automatically splits these consolidated
61 fields into individual fields. The keys of C{CONSOLIDATED_FIELDS} are
62 the names of possible consolidated fields; and the values are the
63 names of the field tags that should be used for individual entries in
64 the list.
65 """
66 __docformat__ = 'epytext en'
67
68
69 import re, os, os.path
70 from xml.dom.minidom import *
71
72 from docutils.core import publish_string
73 from docutils.writers import Writer
74 from docutils.writers.html4css1 import HTMLTranslator, Writer as HTMLWriter
75 from docutils.writers.latex2e import LaTeXTranslator, Writer as LaTeXWriter
76 from docutils.readers.standalone import Reader as StandaloneReader
77 from docutils.utils import new_document
78 from docutils.nodes import NodeVisitor, Text, SkipChildren
79 from docutils.nodes import SkipNode, TreeCopyVisitor
80 from docutils.frontend import OptionParser
81 from docutils.parsers.rst import directives, roles
82 import docutils.nodes
83 import docutils.transforms.frontmatter
84 import docutils.transforms
85 import docutils.utils
86
87 from epydoc.compat import *
88 from epydoc.markup import *
89 from epydoc.apidoc import ModuleDoc, ClassDoc
90 from epydoc.docwriter.dotgraph import *
91 from epydoc.docwriter.xlink import ApiLinkReader
92 from epydoc.markup.doctest import doctest_to_html, doctest_to_latex, \
93 HTMLDoctestColorizer
94
95
96
97
98 CONSOLIDATED_FIELDS = {
99 'parameters': 'param',
100 'arguments': 'arg',
101 'exceptions': 'except',
102 'variables': 'var',
103 'ivariables': 'ivar',
104 'cvariables': 'cvar',
105 'groups': 'group',
106 'types': 'type',
107 'keywords': 'keyword',
108 }
109
110
111
112
113
114 CONSOLIDATED_DEFLIST_FIELDS = ['param', 'arg', 'var', 'ivar', 'cvar', 'keyword']
115
117 """
118 Parse the given docstring, which is formatted using
119 ReStructuredText; and return a L{ParsedDocstring} representation
120 of its contents.
121 @param docstring: The docstring to parse
122 @type docstring: C{string}
123 @param errors: A list where any errors generated during parsing
124 will be stored.
125 @type errors: C{list} of L{ParseError}
126 @param options: Extra options. Unknown options are ignored.
127 Currently, no extra options are defined.
128 @rtype: L{ParsedDocstring}
129 """
130 writer = _DocumentPseudoWriter()
131 reader = _EpydocReader(errors)
132 publish_string(docstring, writer=writer, reader=reader,
133 settings_overrides={'report_level':10000,
134 'halt_level':10000,
135 'warning_stream':None})
136 return ParsedRstDocstring(writer.document)
137
139 """A reporter that ignores all debug messages. This is used to
140 shave a couple seconds off of epydoc's run time, since docutils
141 isn't very fast about processing its own debug messages."""
142 - def debug(self, *args, **kwargs): pass
143
145 """
146 An encoded version of a ReStructuredText docstring. The contents
147 of the docstring are encoded in the L{_document} instance
148 variable.
149
150 @ivar _document: A ReStructuredText document, encoding the
151 docstring.
152 @type _document: C{docutils.nodes.document}
153 """
165
167
168 if errors is None: errors = []
169 visitor = _SplitFieldsTranslator(self._document, errors)
170 self._document.walk(visitor)
171 if len(self._document.children) > 0:
172 return self, visitor.fields
173 else:
174 return None, visitor.fields
175
177
178 visitor = _SummaryExtractor(self._document)
179 try: self._document.walk(visitor)
180 except docutils.nodes.NodeFound: pass
181 return visitor.summary, bool(visitor.other_docs)
182
183
184
185
186
187
188
189
190
191
192 - def to_html(self, docstring_linker, directory=None,
193 docindex=None, context=None, **options):
194
195 visitor = _EpydocHTMLTranslator(self._document, docstring_linker,
196 directory, docindex, context)
197 self._document.walkabout(visitor)
198 return ''.join(visitor.body)
199
200 - def to_latex(self, docstring_linker, **options):
201
202 visitor = _EpydocLaTeXTranslator(self._document, docstring_linker)
203 self._document.walkabout(visitor)
204 return ''.join(visitor.body)
205
206 - def to_plaintext(self, docstring_linker, **options):
207
208 return self._document.astext()
209
210 - def __repr__(self): return '<ParsedRstDocstring: ...>'
211
213 visitor = _TermsExtractor(self._document)
214 self._document.walkabout(visitor)
215 return visitor.terms
216
218 """
219 A reader that captures all errors that are generated by parsing,
220 and appends them to a list.
221 """
222
223
224
225
226
227 version = [int(v) for v in docutils.__version__.split('.')]
228 version += [ 0 ] * (3 - len(version))
229 if version < [0,4,0]:
230 default_transforms = list(ApiLinkReader.default_transforms)
231 try: default_transforms.remove(docutils.transforms.frontmatter.DocInfo)
232 except ValueError: pass
233 else:
237 del version
238
242
252
263
265 """
266 A pseudo-writer for the docutils framework, that can be used to
267 access the document itself. The output of C{_DocumentPseudoWriter}
268 is just an empty string; but after it has been used, the most
269 recently processed document is available as the instance variable
270 C{document}
271
272 @type document: C{docutils.nodes.document}
273 @ivar document: The most recently processed document.
274 """
278
281
283 """
284 A docutils node visitor that extracts the first sentence from
285 the first paragraph in a document.
286 """
291
294
295 _SUMMARY_RE = re.compile(r'(\s*[\w\W]*?\.)(\s|$)')
297 if self.summary is not None:
298
299 self.other_docs = True
300 raise docutils.nodes.NodeFound('Found summary')
301
302 summary_pieces = []
303
304
305 for child in node:
306 if isinstance(child, docutils.nodes.Text):
307 m = self._SUMMARY_RE.match(child.data)
308 if m:
309 summary_pieces.append(docutils.nodes.Text(m.group(1)))
310 other = child.data[m.end():]
311 if other and not other.isspace():
312 self.other_docs = True
313 break
314 summary_pieces.append(child)
315
316 summary_doc = self.document.copy()
317 summary_para = node.copy()
318 summary_doc[:] = [summary_para]
319 summary_para[:] = summary_pieces
320 self.summary = ParsedRstDocstring(summary_doc)
321
324
326 'Ignore all unknown nodes'
327
329 """
330 A docutils node visitor that extracts the terms from documentation.
331
332 Terms are created using the C{:term:} interpreted text role.
333 """
335 NodeVisitor.__init__(self, document)
336
337 self.terms = None
338 """
339 The terms currently found.
340 @type: C{list}
341 """
342
344 self.terms = []
345 self._in_term = False
346
348 if 'term' in node.get('classes'):
349 self._in_term = True
350
352 if 'term' in node.get('classes'):
353 self._in_term = False
354
356 if self._in_term:
357 doc = self.document.copy()
358 doc[:] = [node.copy()]
359 self.terms.append(ParsedRstDocstring(doc))
360
362 'Ignore all unknown nodes'
363
365 'Ignore all unknown nodes'
366
368 """
369 A docutils translator that removes all fields from a document, and
370 collects them into the instance variable C{fields}
371
372 @ivar fields: The fields of the most recently walked document.
373 @type fields: C{list} of L{Field<markup.Field>}
374 """
375
376 ALLOW_UNMARKED_ARG_IN_CONSOLIDATED_FIELD = True
377 """If true, then consolidated fields are not required to mark
378 arguments with C{`backticks`}. (This is currently only
379 implemented for consolidated fields expressed as definition lists;
380 consolidated fields expressed as unordered lists still require
381 backticks for now."""
382
384 NodeVisitor.__init__(self, document)
385 self._errors = errors
386 self.fields = []
387 self._newfields = {}
388
391
393
394 node.parent.remove(node)
395
396
397 tag = node[0].astext().split(None, 1)
398 tagname = tag[0]
399 if len(tag)>1: arg = tag[1]
400 else: arg = None
401
402
403 fbody = node[1]
404 if arg is None:
405 for (list_tag, entry_tag) in CONSOLIDATED_FIELDS.items():
406 if tagname.lower() == list_tag:
407 try:
408 self.handle_consolidated_field(fbody, entry_tag)
409 return
410 except ValueError, e:
411 estr = 'Unable to split consolidated field '
412 estr += '"%s" - %s' % (tagname, e)
413 self._errors.append(ParseError(estr, node.line,
414 is_fatal=0))
415
416
417 if tagname.lower() not in self._newfields:
418 newfield = Field('newfield', tagname.lower(),
419 parse(tagname, 'plaintext'))
420 self.fields.append(newfield)
421 self._newfields[tagname.lower()] = 1
422
423 self._add_field(tagname, arg, fbody)
424
426 field_doc = self.document.copy()
427 for child in fbody: field_doc.append(child)
428 field_pdoc = ParsedRstDocstring(field_doc)
429 self.fields.append(Field(tagname, arg, field_pdoc))
430
432
433
434 node.parent.remove(node)
435
452
454
455
456
457 n = 0
458 _BAD_ITEM = ("list item %d is not well formed. Each item must "
459 "consist of a single marked identifier (e.g., `x`), "
460 "optionally followed by a colon or dash and a "
461 "description.")
462 for item in items:
463 n += 1
464 if item.tagname != 'list_item' or len(item) == 0:
465 raise ValueError('bad bulleted list (bad child %d).' % n)
466 if item[0].tagname != 'paragraph':
467 if item[0].tagname == 'definition_list':
468 raise ValueError(('list item %d contains a definition '+
469 'list (it\'s probably indented '+
470 'wrong).') % n)
471 else:
472 raise ValueError(_BAD_ITEM % n)
473 if len(item[0]) == 0:
474 raise ValueError(_BAD_ITEM % n)
475 if item[0][0].tagname != 'title_reference':
476 raise ValueError(_BAD_ITEM % n)
477
478
479 for item in items:
480
481 arg = item[0][0].astext()
482
483
484 fbody = item[:]
485 fbody[0] = fbody[0].copy()
486 fbody[0][:] = item[0][1:]
487
488
489 if (len(fbody[0]) > 0 and
490 isinstance(fbody[0][0], docutils.nodes.Text)):
491 child = fbody[0][0]
492 if child.data[:1] in ':-':
493 child.data = child.data[1:].lstrip()
494 elif child.data[:2] in (' -', ' :'):
495 child.data = child.data[2:].lstrip()
496
497
498 self._add_field(tagname, arg, fbody)
499
501
502 n = 0
503 _BAD_ITEM = ("item %d is not well formed. Each item's term must "
504 "consist of a single marked identifier (e.g., `x`), "
505 "optionally followed by a space, colon, space, and "
506 "a type description.")
507 for item in items:
508 n += 1
509 if (item.tagname != 'definition_list_item' or len(item) < 2 or
510 item[0].tagname != 'term' or
511 item[-1].tagname != 'definition'):
512 raise ValueError('bad definition list (bad child %d).' % n)
513 if len(item) > 3:
514 raise ValueError(_BAD_ITEM % n)
515 if not ((item[0][0].tagname == 'title_reference') or
516 (self.ALLOW_UNMARKED_ARG_IN_CONSOLIDATED_FIELD and
517 isinstance(item[0][0], docutils.nodes.Text))):
518 raise ValueError(_BAD_ITEM % n)
519 for child in item[0][1:]:
520 if child.astext() != '':
521 raise ValueError(_BAD_ITEM % n)
522
523
524 for item in items:
525
526 arg = item[0][0].astext()
527 fbody = item[-1]
528 self._add_field(tagname, arg, fbody)
529
530 if len(item) == 3:
531 type_descr = item[1]
532 self._add_field('type', arg, type_descr)
533
535 'Ignore all unknown nodes'
536
541
543 settings = None
545
546 if self.settings is None:
547 settings = OptionParser([LaTeXWriter()]).get_default_values()
548 settings.output_encoding = 'utf-8'
549 self.__class__.settings = settings
550 document.settings = self.settings
551
552 LaTeXTranslator.__init__(self, document)
553 self._linker = docstring_linker
554
555
556
557
558 self.section_level = 3
559 self._section_number = [0]*self.section_level
560
561
563 target = self.encode(node.astext())
564 xref = self._linker.translate_identifier_xref(target, target)
565 self.body.append(xref)
566 raise SkipNode()
567
570
571
573 log.warning("Ignoring dotgraph in latex output (dotgraph "
574 "rendering for latex not implemented yet).")
575 raise SkipNode()
576
578 self.body.append(doctest_to_latex(node[0].astext()))
579 raise SkipNode()
580
582 settings = None
583 - def __init__(self, document, docstring_linker, directory,
584 docindex, context):
598
599
601 target = self.encode(node.astext())
602 xref = self._linker.translate_identifier_xref(target, target)
603 self.body.append(xref)
604 raise SkipNode()
605
611
614
615 - def starttag(self, node, tagname, suffix='\n', **attributes):
616 """
617 This modified version of starttag makes a few changes to HTML
618 tags, to prevent them from conflicting with epydoc. In particular:
619 - existing class attributes are prefixed with C{'rst-'}
620 - existing names are prefixed with C{'rst-'}
621 - hrefs starting with C{'#'} are prefixed with C{'rst-'}
622 - hrefs not starting with C{'#'} are given target='_top'
623 - all headings (C{<hM{n}>}) are given the css class C{'heading'}
624 """
625
626 attr_dicts = [attributes]
627 if isinstance(node, docutils.nodes.Node):
628 attr_dicts.append(node.attributes)
629 if isinstance(node, dict):
630 attr_dicts.append(node)
631
632
633
634 for attr_dict in attr_dicts:
635 for (key, val) in attr_dict.items():
636
637
638 if key.lower() in ('class', 'id', 'name'):
639 attr_dict[key] = 'rst-%s' % val
640 elif key.lower() in ('classes', 'ids', 'names'):
641 attr_dict[key] = ['rst-%s' % cls for cls in val]
642 elif key.lower() == 'href':
643 if attr_dict[key][:1]=='#':
644 attr_dict[key] = '#rst-%s' % attr_dict[key][1:]
645 else:
646
647
648 attr_dict['target'] = '_top'
649
650
651 if re.match(r'^h\d+$', tagname):
652 attributes['class'] = ' '.join([attributes.get('class',''),
653 'heading']).strip()
654
655 return HTMLTranslator.starttag(self, node, tagname, suffix,
656 **attributes)
657
659 if self._directory is None: return
660
661
662 graph = node.graph(self._docindex, self._context, self._linker)
663 if graph is None: return
664
665
666 image_url = '%s.gif' % graph.uid
667 image_file = os.path.join(self._directory, image_url)
668 self.body.append(graph.to_html(image_file, image_url))
669 raise SkipNode()
670
678
689
690 -def python_code_directive(name, arguments, options, content, lineno,
691 content_offset, block_text, state, state_machine):
692 """
693 A custom restructuredtext directive which can be used to display
694 syntax-highlighted Python code blocks. This directive takes no
695 arguments, and the body should contain only Python code. This
696 directive can be used instead of doctest blocks when it is
697 inconvenient to list prompts on each line, or when you would
698 prefer that the output not contain prompts (e.g., to make
699 copy/paste easier).
700 """
701 required_arguments = 0
702 optional_arguments = 0
703
704 text = '\n'.join(content)
705 node = docutils.nodes.doctest_block(text, text, codeblock=True)
706 return [ node ]
707
708 python_code_directive.arguments = (0, 0, 0)
709 python_code_directive.content = True
710
711 directives.register_directive('python', python_code_directive)
712
713 -def term_role(name, rawtext, text, lineno, inliner,
714 options={}, content=[]):
715
716 text = docutils.utils.unescape(text)
717 node = docutils.nodes.emphasis(rawtext, text, **options)
718 node.attributes['classes'].append('term')
719
720 return [node], []
721
722 roles.register_local_role('term', term_role)
723
724
725
726
727
728
730 """
731 A custom docutils node that should be rendered using Graphviz dot.
732 This node does not directly store the graph; instead, it stores a
733 pointer to a function that can be used to generate the graph.
734 This allows the graph to be built based on information that might
735 not be available yet at parse time. This graph generation
736 function has the following signature:
737
738 >>> def generate_graph(docindex, context, linker, *args):
739 ... 'generates and returns a new DotGraph'
740
741 Where C{docindex} is a docindex containing the documentation that
742 epydoc has built; C{context} is the C{APIDoc} whose docstring
743 contains this dotgraph node; C{linker} is a L{DocstringLinker}
744 that can be used to resolve crossreferences; and C{args} is any
745 extra arguments that are passed to the C{dotgraph} constructor.
746 """
747 - def __init__(self, generate_graph_func, *generate_graph_args):
748 docutils.nodes.image.__init__(self)
749 self.graph_func = generate_graph_func
750 self.args = generate_graph_args
751 - def graph(self, docindex, context, linker):
752 return self.graph_func(docindex, context, linker, *self.args)
753
755 """A directive option spec for the orientation of a graph."""
756 argument = argument.lower().strip()
757 if argument == 'right': return 'LR'
758 if argument == 'left': return 'RL'
759 if argument == 'down': return 'TB'
760 if argument == 'up': return 'BT'
761 raise ValueError('%r unknown; choose from left, right, up, down' %
762 argument)
763
764 -def digraph_directive(name, arguments, options, content, lineno,
765 content_offset, block_text, state, state_machine):
766 """
767 A custom restructuredtext directive which can be used to display
768 Graphviz dot graphs. This directive takes a single argument,
769 which is used as the graph's name. The contents of the directive
770 are used as the body of the graph. Any href attributes whose
771 value has the form <name> will be replaced by the URL of the object
772 with that name. Here's a simple example::
773
774 .. digraph:: example_digraph
775 a -> b -> c
776 c -> a [dir=\"none\"]
777 """
778 if arguments: title = arguments[0]
779 else: title = ''
780 return [ dotgraph(_construct_digraph, title, options.get('caption'),
781 '\n'.join(content)) ]
782 digraph_directive.arguments = (0, 1, True)
783 digraph_directive.options = {'caption': directives.unchanged}
784 digraph_directive.content = True
785 directives.register_directive('digraph', digraph_directive)
786
793
794 -def classtree_directive(name, arguments, options, content, lineno,
795 content_offset, block_text, state, state_machine):
796 """
797 A custom restructuredtext directive which can be used to
798 graphically display a class hierarchy. If one or more arguments
799 are given, then those classes and all their descendants will be
800 displayed. If no arguments are given, and the directive is in a
801 class's docstring, then that class and all its descendants will be
802 displayed. It is an error to use this directive with no arguments
803 in a non-class docstring.
804
805 Options:
806 - C{:dir:} -- Specifies the orientation of the graph. One of
807 C{down}, C{right} (default), C{left}, C{up}.
808 """
809 return [ dotgraph(_construct_classtree, arguments, options) ]
810 classtree_directive.arguments = (0, 1, True)
811 classtree_directive.options = {'dir': _dir_option}
812 classtree_directive.content = False
813 directives.register_directive('classtree', classtree_directive)
814
816 """Graph generator for L{classtree_directive}"""
817 if len(arguments) == 1:
818 bases = [docindex.find(name, context) for name in
819 arguments[0].replace(',',' ').split()]
820 bases = [d for d in bases if isinstance(d, ClassDoc)]
821 elif isinstance(context, ClassDoc):
822 bases = [context]
823 else:
824 log.warning("Could not construct class tree: you must "
825 "specify one or more base classes.")
826 return None
827
828 return class_tree_graph(bases, linker, context, **options)
829
830 -def packagetree_directive(name, arguments, options, content, lineno,
831 content_offset, block_text, state, state_machine):
832 """
833 A custom restructuredtext directive which can be used to
834 graphically display a package hierarchy. If one or more arguments
835 are given, then those packages and all their submodules will be
836 displayed. If no arguments are given, and the directive is in a
837 package's docstring, then that package and all its submodules will
838 be displayed. It is an error to use this directive with no
839 arguments in a non-package docstring.
840
841 Options:
842 - C{:dir:} -- Specifies the orientation of the graph. One of
843 C{down}, C{right} (default), C{left}, C{up}.
844 """
845 return [ dotgraph(_construct_packagetree, arguments, options) ]
846 packagetree_directive.arguments = (0, 1, True)
847 packagetree_directive.options = {
848 'dir': _dir_option,
849 'style': lambda a:directives.choice(a.lower(), ('uml', 'tree'))}
850 packagetree_directive.content = False
851 directives.register_directive('packagetree', packagetree_directive)
852
854 """Graph generator for L{packagetree_directive}"""
855 if len(arguments) == 1:
856 packages = [docindex.find(name, context) for name in
857 arguments[0].replace(',',' ').split()]
858 packages = [d for d in packages if isinstance(d, ModuleDoc)]
859 elif isinstance(context, ModuleDoc):
860 packages = [context]
861 else:
862 log.warning("Could not construct package tree: you must "
863 "specify one or more root packages.")
864 return None
865
866 return package_tree_graph(packages, linker, context, **options)
867
868 -def importgraph_directive(name, arguments, options, content, lineno,
869 content_offset, block_text, state, state_machine):
871 importgraph_directive.arguments = (0, 1, True)
872 importgraph_directive.options = {'dir': _dir_option}
873 importgraph_directive.content = False
874 directives.register_directive('importgraph', importgraph_directive)
875
877 """Graph generator for L{importgraph_directive}"""
878 if len(arguments) == 1:
879 modules = [ docindex.find(name, context)
880 for name in arguments[0].replace(',',' ').split() ]
881 modules = [d for d in modules if isinstance(d, ModuleDoc)]
882 else:
883 modules = [d for d in docindex.root if isinstance(d, ModuleDoc)]
884
885 return import_graph(modules, docindex, linker, context, **options)
886
887 -def callgraph_directive(name, arguments, options, content, lineno,
888 content_offset, block_text, state, state_machine):
890 callgraph_directive.arguments = (0, 1, True)
891 callgraph_directive.options = {'dir': _dir_option,
892 'add_callers': directives.flag,
893 'add_callees': directives.flag}
894 callgraph_directive.content = False
895 directives.register_directive('callgraph', callgraph_directive)
896
898 """Graph generator for L{callgraph_directive}"""
899 if len(arguments) == 1:
900 docs = [docindex.find(name, context) for name in
901 arguments[0].replace(',',' ').split()]
902 docs = [doc for doc in docs if doc is not None]
903 else:
904 docs = [context]
905 return call_graph(docs, docindex, linker, context, **options)
906