Source

trac-ticketlinks / trac / mimeview / silvercity.py

Full commit
# -*- coding: utf-8 -*-
#
# Copyright (C) 2004-2008 Edgewall Software
# Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Daniel Lundin <daniel@edgewall.com>

"""Syntax highlighting module, based on the SilverCity module.

Get it at: http://silvercity.sourceforge.net/
"""

import re
from StringIO import StringIO

from genshi.core import Markup

from trac.core import *
from trac.config import ListOption
from trac.mimeview.api import IHTMLPreviewRenderer, Mimeview
from trac.util import get_pkginfo

try:
    import SilverCity
    have_silvercity = True
except ImportError:
    have_silvercity = False


__all__ = ['SilverCityRenderer']

types = {
    'text/css':                 ('CSS', 3),
    'text/html':                ('HyperText', 3, {'asp.default.language':1}),
    'application/xml':          ('XML', 3),
    'application/xhtml+xml':    ('HyperText', 3, {'asp.default.language':1}),
    'application/rss+xml':      ('HyperText', 3, {'asp.default.language':1}),
    'application/x-yaml':       ('YAML', 3),
    'text/x-yaml':              ('YAML', 3),
    'application/x-javascript': ('CPP', 3), # Kludgy.
    'text/x-asp':               ('HyperText', 3, {'asp.default.language':2}),
    'text/x-c++hdr':            ('CPP', 3),
    'text/x-c++src':            ('CPP', 3),
    'text/x-chdr':              ('CPP', 3),
    'text/x-csrc':              ('CPP', 3),
    'text/x-perl':              ('Perl', 3),
    'text/x-php':               ('HyperText', 3, {'asp.default.language': 4}),
    'application/x-httpd-php':  ('HyperText', 3, {'asp.default.language': 4}),
    'application/x-httpd-php4': ('HyperText', 3, {'asp.default.language': 4}),
    'application/x-httpd-php3': ('HyperText', 3, {'asp.default.language': 4}),
    'text/x-java':              ('Java', 3),
    'text/x-javascript':        ('CPP', 3), # Kludgy.
    'text/x-psp':               ('HyperText', 3, {'asp.default.language': 3}),
    'text/x-python':            ('Python', 3),
    'text/x-ruby':              ('Ruby', 3),
    'text/x-sql':               ('SQL', 3),
    'text/x-verilog':           ('Verilog', 3),
    'text/xml':                 ('XML', 3),
    'text/xslt':                ('XSLT', 3),
    'image/svg+xml':            ('XML', 3)
}

CRLF_RE = re.compile('\r$', re.MULTILINE)


class SilverCityRenderer(Component):
    """Syntax highlighting based on SilverCity."""

    implements(IHTMLPreviewRenderer)

    silvercity_modes = ListOption('mimeviewer', 'silvercity_modes',
        '', doc=
        """List of additional MIME types known by SilverCity.
        For each, a tuple `mimetype:mode:quality` has to be
        specified, where `mimetype` is the MIME type,
        `mode` is the corresponding SilverCity mode to be used
        for the conversion and `quality` is the quality ratio
        associated to this conversion.
        That can also be used to override the default
        quality ratio used by the SilverCity render, which is 3
        (''since 0.10'').""")

    expand_tabs = True
    returns_source = True

    def __init__(self):
        self.log.debug("SilverCity installed? %r", have_silvercity)
        if have_silvercity:
            self.env.systeminfo.append(('SilverCity',
                                        get_pkginfo(SilverCity).get('version',
                                                                    '?')))
            # TODO: the above works only if setuptools was used to build
            # SilverCity, which is not yet the case by default for 0.9.7.
            # I've not been able to find an alternative way to get version.
        self._types = None

    def get_quality_ratio(self, mimetype):
        # Extend default MIME type to mode mappings with configured ones
        if not have_silvercity:
            return 0
        if not self._types:
            self._types = {}
            self._types.update(types)
            self._types.update(
                Mimeview(self.env).configured_modes_mapping('silvercity'))
        return self._types.get(mimetype, (None, 0))[1]

    def render(self, context, mimetype, content, filename=None, rev=None):
        try:
            mimetype = mimetype.split(';', 1)[0]
            typelang = self._types[mimetype]
            lang = typelang[0]
            module = getattr(SilverCity, lang)
            generator = getattr(module, lang + "HTMLGenerator")
            try:
                allprops = typelang[2]
                propset = SilverCity.PropertySet()
                for p in allprops.keys():
                    propset[p] = allprops[p]
            except IndexError:
                pass
        except (KeyError, AttributeError):
            err = "No SilverCity lexer found for mime-type '%s'." % mimetype
            raise Exception, err

        # SilverCity does not like unicode strings
        content = content.encode('utf-8')
        
        # SilverCity generates extra empty line against some types of
        # the line such as comment or #include with CRLF. So we
        # standardize to LF end-of-line style before call.
        content = CRLF_RE.sub('', content)

        buf = StringIO()
        generator().generate_html(buf, content)

        br_re = re.compile(r'<br\s*/?>$', re.MULTILINE)
        span_default_re = re.compile(r'<span class="\w+_default">(.*?)</span>',
                                     re.DOTALL)
        html = span_default_re.sub(r'\1', br_re.sub('', buf.getvalue()))
        
        # Convert the output back to a unicode string
        html = html.decode('utf-8')

        # SilverCity generates _way_ too many non-breaking spaces...
        # We don't need them anyway, so replace them by normal spaces
        return [Markup(line)
                for line in html.replace('&nbsp;', ' ').splitlines()]