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

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

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