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