1
2
3
4
5
6
7
8
9
10
11
12
13 from __future__ import absolute_import
14
15 import os
16 import pygments
17 import re
18 from datetime import datetime
19 from pkg_resources import resource_filename
20 from pygments.formatters.html import HtmlFormatter
21 from pygments.lexers import get_all_lexers, get_lexer_by_name
22 from pygments.styles import get_all_styles, get_style_by_name
23
24 from trac.core import *
25 from trac.config import ConfigSection, ListOption, Option
26 from trac.env import ISystemInfoProvider
27 from trac.mimeview.api import IHTMLPreviewRenderer, Mimeview
28 from trac.prefs import IPreferencePanelProvider
29 from trac.util import get_pkginfo, lazy
30 from trac.util.datefmt import http_date, localtz
31 from trac.util.translation import _
32 from trac.web.api import IRequestHandler, HTTPNotFound
33 from trac.web.chrome import ITemplateProvider, add_notice, add_stylesheet
34
35 from genshi import QName, Stream
36 from genshi.core import Attrs, START, END, TEXT
37
38 __all__ = ['PygmentsRenderer']
42 """HTML renderer for syntax highlighting based on Pygments."""
43
44 implements(ISystemInfoProvider, IHTMLPreviewRenderer,
45 IPreferencePanelProvider, IRequestHandler,
46 ITemplateProvider)
47
48 is_valid_default_handler = False
49
50 pygments_lexer_options = ConfigSection('pygments-lexer',
51 """Configure Pygments [%(url)s lexer] options.
52
53 For example, to set the
54 [%(url)s#lexers-for-php-and-related-languages PhpLexer] options
55 `startinline` and `funcnamehighlighting`:
56 {{{#!ini
57 [pygments-lexer]
58 php.startinline = True
59 php.funcnamehighlighting = True
60 }}}
61
62 The lexer name is derived from the class name, with `Lexer` stripped
63 from the end. The lexer //short names// can also be used in place
64 of the lexer name.
65 """, doc_args={'url': 'http://pygments.org/docs/lexers/'})
66
67 default_style = Option('mimeviewer', 'pygments_default_style', 'trac',
68 """The default style to use for Pygments syntax highlighting.""")
69
70 pygments_modes = ListOption('mimeviewer', 'pygments_modes',
71 '', doc=
72 """List of additional MIME types known by Pygments.
73
74 For each, a tuple `mimetype:mode:quality` has to be
75 specified, where `mimetype` is the MIME type,
76 `mode` is the corresponding Pygments mode to be used
77 for the conversion and `quality` is the quality ratio
78 associated to this conversion. That can also be used
79 to override the default quality ratio used by the
80 Pygments render.""")
81
82 expand_tabs = True
83 returns_source = True
84
85 QUALITY_RATIO = 7
86
87 EXAMPLE = """<!DOCTYPE html>
88 <html lang="en">
89 <head>
90 <title>Hello, world!</title>
91 <script>
92 jQuery(document).ready(function($) {
93 $("h1").fadeIn("slow");
94 });
95 </script>
96 </head>
97 <body>
98 <h1>Hello, world!</h1>
99 </body>
100 </html>"""
101
102
103
110
111
112
114 for _, aliases, _, mimetypes in get_all_lexers():
115 for mimetype in mimetypes:
116 yield mimetype, aliases
117
119
120 try:
121 return self._types[mimetype][1]
122 except KeyError:
123 return 0
124
125 - def render(self, context, mimetype, content, filename=None, rev=None):
126 req = context.req
127 style = req.session.get('pygments_style', self.default_style)
128 add_stylesheet(req, '/pygments/%s.css' % style)
129 try:
130 if len(content) > 0:
131 mimetype = mimetype.split(';', 1)[0]
132 language = self._types[mimetype][0]
133 return self._generate(language, content, context)
134 except (KeyError, ValueError):
135 raise Exception("No Pygments lexer found for mime-type '%s'."
136 % mimetype)
137
138
139
141 yield 'pygments', _('Syntax Highlighting')
142
144 styles = list(get_all_styles())
145
146 if req.method == 'POST':
147 style = req.args.get('style')
148 if style and style in styles:
149 req.session['pygments_style'] = style
150 add_notice(req, _("Your preferences have been saved."))
151 req.redirect(req.href.prefs(panel or None))
152
153 for style in sorted(styles):
154 add_stylesheet(req, '/pygments/%s.css' % style, title=style.title())
155 output = self._generate('html', self.EXAMPLE)
156 return 'prefs_pygments.html', {
157 'output': output,
158 'selection': req.session.get('pygments_style', self.default_style),
159 'styles': styles
160 }
161
162
163
165 match = re.match(r'/pygments/([-\w]+)\.css', req.path_info)
166 if match:
167 req.args['style'] = match.group(1)
168 return True
169
197
198
199
202
204 return [resource_filename('trac.mimeview', 'templates')]
205
206
207
208 @lazy
210 lexer_alias_name_map = {}
211 for lexer_name, aliases, _, _ in get_all_lexers():
212 name = aliases[0] if aliases else lexer_name
213 for alias in aliases:
214 lexer_alias_name_map[alias] = name
215 return lexer_alias_name_map
216
217 @lazy
219 lexer_options = {}
220 for key, lexer_option_value in self.pygments_lexer_options.options():
221 try:
222 lexer_name_or_alias, lexer_option_name = key.split('.')
223 except ValueError:
224 pass
225 else:
226 lexer_name = self._lexer_alias_to_name(lexer_name_or_alias)
227 lexer_option = {lexer_option_name: lexer_option_value}
228 lexer_options.setdefault(lexer_name, {}).update(lexer_option)
229 return lexer_options
230
231 @lazy
247
248 - def _generate(self, language, content, context=None):
249 lexer_name = self._lexer_alias_to_name(language)
250 lexer_options = {'stripnl': False}
251 lexer_options.update(self._lexer_options.get(lexer_name, {}))
252 if context:
253 lexer_options.update(context.get_hint('lexer_options', {}))
254 lexer = get_lexer_by_name(lexer_name, **lexer_options)
255 return GenshiHtmlFormatter().generate(lexer.get_tokens(content))
256
259
305