File: Synopsis/Formatters/HTML/Markup/Javadoc.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.Formatters.HTML.Tags import *
  9from Synopsis.Formatters.HTML.Markup import *
 10import re
 11
 12class Javadoc(Formatter):
 13    """
 14    A formatter that formats comments similar to Javadoc.
 15    See `Javadoc Spec`_ for info.
 16
 17    .. _Javadoc Spec: http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/javadoc.html"""
 18
 19    class Block:
 20
 21        def __init__(self, tag, arg, body):
 22            self.tag, self.arg, self.body = tag, arg, body
 23
 24
 25    summary = r'(\s*[\w\W]*?\.)(\s|$)'
 26    block_tag = r'(^\s*\@\w+[\s$])'
 27    inline_tag = r'{@(?P<tag>\w+)\s+(?P<content>[^}]+)}'
 28    inline_tag_split = r'({@\w+\s+[^}]+})'
 29    xref = r'([\w#.]+)(?:\([^\)]*\))?\s*(.*)'
 30
 31    tag_name = {
 32    'author': ['Author', 'Authors'],
 33    'date': ['Date', 'Dates'],
 34    'deprecated': ['Deprecated', 'Deprecated'],
 35    'exception': ['Exception', 'Exceptions'],
 36    'invariant': ['Invariant', 'Invariants'],
 37    'keyword': ['Keyword', 'Keywords'],
 38    'param': ['Parameter', 'Parameters'],
 39    'postcondition': ['Postcondition', 'Postcondition'],
 40    'precondition': ['Precondition', 'Preconditions'],
 41    'return': ['Returns', 'Returns'],
 42    'see': ['See also', 'See also'],
 43    'throws': ['Throws', 'Throws'],
 44    'version': ['Version', 'Versions']}
 45    arg_tags = ['param', 'keyword', 'exception']
 46
 47
 48    def __init__(self):
 49        """Create regex objects for regexps"""
 50
 51        self.summary = re.compile(Javadoc.summary)
 52        self.block_tag = re.compile(Javadoc.block_tag, re.M)
 53        self.inline_tag = re.compile(Javadoc.inline_tag)
 54        self.inline_tag_split = re.compile(Javadoc.inline_tag_split)
 55        self.xref = re.compile(Javadoc.xref)
 56
 57
 58    def split(self, doc):
 59        """Split a javadoc comment into description and blocks."""
 60
 61        chunks = self.block_tag.split(doc)
 62        description = chunks[0]
 63        blocks = []
 64        for i in range(1, len(chunks)):
 65            if i % 2 == 1:
 66                tag = chunks[i].strip()[1:]
 67            else:
 68                if tag in self.arg_tags:
 69                    arg, body = chunks[i].strip().split(None, 1)
 70                else:
 71                    arg, body = None, chunks[i]
 72
 73                if tag == 'see' and body:
 74                    if body[0] in ['"', "'"]:
 75                        if body[-1] == body[0]:
 76                            body = body[1:-1]
 77                    elif body[0] == '<':
 78                        pass
 79                    else:
 80                        # @link tags are interpreted as cross-references
 81                        #       and resolved later (see format_inline_tag)
 82                        body = '{@link %s}'%body
 83                blocks.append(Javadoc.Block(tag, arg, body))
 84
 85        return description, blocks
 86
 87
 88    def extract_summary(self, description):
 89        """Generate a summary from the given description."""
 90
 91        m = self.summary.match(description)
 92        if m:
 93            return m.group(1)
 94        else:
 95            return description.split('\n', 1)[0]+'...'
 96
 97
 98    def format(self, decl, view):
 99        """Format using javadoc markup."""
100
101        doc = decl.annotations.get('doc')
102        doc = doc and doc.text or ''
103        if not doc:
104            return Struct('', '')
105        description, blocks = self.split(doc)
106
107        details = self.format_description(description, view, decl)
108        summary = self.extract_summary(details)
109        details += self.format_params(blocks, view, decl)
110        details += self.format_tag('return', blocks, view, decl)
111        details += self.format_throws(blocks, view, decl)
112        details += self.format_tag('precondition', blocks, view, decl)
113        details += self.format_tag('postcondition', blocks, view, decl)
114        details += self.format_tag('invariant', blocks, view, decl)
115        details += self.format_tag('author', blocks, view, decl)
116        details += self.format_tag('date', blocks, view, decl)
117        details += self.format_tag('version', blocks, view, decl)
118        details += self.format_tag('deprecated', blocks, view, decl)
119        details += self.format_tag('see', blocks, view, decl)
120
121        return Struct(summary, details)
122
123
124    def format_description(self, text, view, decl):
125
126        return self.format_inlines(view, decl, text)
127
128
129    def format_inlines(self, view, decl, text):
130        """Formats inline tags in the text."""
131
132        chunks = self.inline_tag_split.split(text)
133        text = ''
134        # Every other chunk is now an inlined tag, which we process
135        # in this loop.
136        for i in range(len(chunks)):
137            if i % 2 == 0:
138                text += chunks[i]
139            else:
140                m = self.inline_tag.match(chunks[i])
141                if m:
142                    text += self.format_inline_tag(m.group('tag'),
143                                                   m.group('content'),
144                                                   view, decl)
145        return text
146
147
148    def format_params(self, blocks, view, decl):
149        """Formats a list of (param, description) tags"""
150
151        content = ''
152        params = [b for b in blocks if b.tag == 'param']
153        def row(dt, dd):
154            return element('tr',
155                           element('th', dt, Class='dt') +
156                           element('td', dd, Class='dd'))
157        if params:
158            content += div('tag-heading',"Parameters:")
159            dl = element('table', ''.join([row(p.arg, p.body) for p in params]),
160                         Class='dl')
161            content += div('tag-section', dl)
162        kwds = [b for b in blocks if b.tag == 'keyword']
163        if kwds:
164            content += div('tag-heading',"Keywords:")
165            dl = element('dl', ''.join([row( k.arg, k.body) for k in kwds]), Class='dl')
166            content += div('tag-section', dl)
167        return content
168
169
170    def format_throws(self, blocks, view, decl):
171
172        content = ''
173        throws = [b for b in blocks if b.tag in ['throws', 'exception']]
174        if throws:
175            content += div('tag-heading',"Throws:")
176            dl = element('dl', ''.join([element('dt', t.arg) + element('dd', t.body)
177                                        for t in throws]))
178            content += div('tag-section', dl)
179        return content
180
181
182    def format_tag(self, tag, blocks, view, decl):
183
184        content = ''
185        items = [b for b in blocks if b.tag == tag]
186        if items:
187            content += div('tag-heading', self.tag_name[tag][1])
188            content += div('tag-section',
189                           '<br/>'.join([self.format_inlines(view, decl, i.body)
190                                         for i in items]))
191        return content
192
193    def format_inline_tag(self, tag, content, view, decl):
194
195        text = ''
196        if tag == 'link':
197            xref = self.xref.match(content)
198            name, label = xref.groups()
199            if not label:
200                label = name
201            # javadoc uses '{@link  package.class#member  label}'
202            # Here we simply replace '#' by either '::' or '.' to match
203            # language-specific qualification rules.
204            if '#' in name:
205                if '::' in name:
206                    name = name.replace('#', '::')
207                else:
208                    name = name.replace('#', '.')
209            target = self.lookup_symbol(name, decl.name[:-1])
210            if target:
211                url = rel(view.filename(), target)
212                text += href(url, label)
213            else:
214                text += label
215        elif tag == 'code':
216            text += '<code>%s</code>'%escape(content)
217        elif tag == 'literal':
218            text += '<code>%s</code>'%escape(content)
219
220        return text
221