Synopsis - Synopsis/Formatters/DocBook/Markup/Javadoc.py

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

Generated on Thu Apr 16 16:27:15 2009 by
synopsis (version devel)