File: Synopsis/Formatters/HTML/Markup/RST.py
  1#
  2# Copyright (C) 2006 Stefan Seefeld
  3# All rights reserved.
  4# Licensed to the public under the terms of the GNU LGPL (>= 2),
  5# see the file COPYING for details.
  6#
  7
  8from Synopsis.Processor import Parameter
  9from Synopsis.Formatters.HTML.Tags import *
 10from Synopsis.Formatters.HTML.Markup import *
 11from docutils.nodes import *
 12from docutils.core import *
 13from docutils.parsers.rst import roles
 14import re, StringIO
 15
 16def span(name, rawtext, text, lineno, inliner, options={}, content=[]):
 17    """Maps a rols to a <span class="role">...</span>."""
 18
 19    node = inline(rawtext, text)
 20    node['classes'] = [name]
 21    return [node], []
 22
 23
 24class SummaryExtractor(NodeVisitor):
 25    """A SummaryExtractor creates a document containing the first sentence of
 26    a source document."""
 27
 28    def __init__(self, document):
 29
 30        NodeVisitor.__init__(self, document)
 31        self.summary = None
 32
 33
 34    def visit_paragraph(self, node):
 35        """Copy the paragraph but only keep the first sentence."""
 36
 37        if self.summary is not None:
 38            return
 39
 40        summary_pieces = []
 41
 42        # Extract the first sentence.
 43        for child in node:
 44            if isinstance(child, Text):
 45                m = re.match(r'(\s*[\w\W]*?\.)(\s|$)', child.astext())
 46                if m:
 47                    summary_pieces.append(Text(m.group(1)))
 48                    break
 49                else:
 50                    summary_pieces.append(Text(child))
 51            else:
 52                summary_pieces.append(child)
 53
 54        self.summary = self.document.copy()
 55        para = node.copy()
 56        para[:] = summary_pieces
 57        self.summary[:] = [para]
 58
 59
 60    def unknown_visit(self, node):
 61        'Ignore all unknown nodes'
 62
 63        pass
 64
 65
 66class RST(Formatter):
 67    """Format summary and detail documentation according to restructured text markup.
 68    """
 69
 70    roles = Parameter({'equation':span},
 71                      "A (name->function) mapping of custom ReST roles.")
 72
 73    def format(self, decl, view):
 74
 75        formatter = self
 76
 77        def ref(name, rawtext, text, lineno, inliner,
 78                options={}, content=[]):
 79            # This function needs to be local to be able to access
 80            # the current state in form of the formatter.
 81
 82            name = utils.unescape(text)
 83            uri = formatter.lookup_symbol(name, decl.name[:-1])
 84            if uri:
 85                ref = rel(view.filename(), uri)
 86                node = reference(rawtext, name, refuri=ref, **options)
 87            else:
 88                node = emphasis(rawtext, name)
 89            return [node], []
 90
 91        for r in self.roles:
 92            roles.register_local_role(r, self.roles[r])
 93        roles.register_local_role('', ref)
 94
 95        errstream = StringIO.StringIO()
 96        settings = {}
 97        settings['halt_level'] = 2
 98        settings['warning_stream'] = errstream
 99        settings['traceback'] = True
100
101        doc = decl.annotations.get('doc')
102        if doc:
103            try:
104                doctree = publish_doctree(doc.text, settings_overrides=settings)
105                # Extract the summary.
106                extractor = SummaryExtractor(doctree)
107                doctree.walk(extractor)
108
109                reader = docutils.readers.doctree.Reader(parser_name='null')
110
111                # Publish the summary.
112                if extractor.summary:
113                    pub = Publisher(reader, None, None,
114                                    source=io.DocTreeInput(extractor.summary),
115                                    destination_class=io.StringOutput)
116                    pub.set_writer('html')
117                    pub.process_programmatic_settings(None, None, None)
118                    dummy = pub.publish(enable_exit_status=None)
119                    summary = pub.writer.parts['html_body']
120                    # Hack to strip off some redundant blocks to make the output
121                    # more compact.
122                    if (summary.startswith('<div class="document">\n') and
123                        summary.endswith('</div>\n')):
124                        summary=summary[23:-7]
125                else:
126                    summary = ''
127
128                # Publish the details.
129                pub = Publisher(reader, None, None,
130                                source=io.DocTreeInput(doctree),
131                                destination_class=io.StringOutput)
132                pub.set_writer('html')
133                pub.process_programmatic_settings(None, None, None)
134                dummy = pub.publish(enable_exit_status=None)
135                details = pub.writer.parts['html_body']
136                # Hack to strip off some redundant blocks to make the output
137                # more compact.
138                if (details.startswith('<div class="document">\n') and
139                    details.endswith('</div>\n')):
140                    details=details[23:-7]
141
142                return Struct(summary, details)
143
144            except docutils.utils.SystemMessage, error:
145                xx, line, message = str(error).split(':', 2)
146                print 'In DocString attached to declaration at %s:%d:'%(decl.file.name,
147                                                                        decl.line)
148                print '  line %s:%s'%(line, message)
149
150        return Struct('', '')
151