root/tracpygmentsplugin/0.10/tracpygments/__init__.py

Revision 2006, 8.0 kB (checked in by mitsuhiko, 2 years ago)

TracPygmentsPlugin:

added mitsuhiko's backport

Line 
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2006 Matthew Good <matt@matt-good.net>
4 # All rights reserved.
5 #
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://trac.edgewall.org/wiki/TracLicense.
9 #
10 # Author: Matthew Good <matt@matt-good.net>
11 """Syntax highlighting based on Pygments."""
12
13 from datetime import datetime
14 import os
15 from pkg_resources import resource_filename
16 import re
17 import time
18
19 from trac.core import *
20 from trac.config import ListOption, Option
21 from trac.mimeview.api import IHTMLPreviewRenderer, Mimeview
22 from trac.wiki.api import IWikiMacroProvider
23 from trac.util.datefmt import http_date
24 from trac.util.html import Markup
25 from trac.web import IRequestHandler, IRequestFilter
26 from trac.web.chrome import add_link, ITemplateProvider
27
28 try:
29     import pygments
30     from pygments.lexers import get_lexer_by_name
31     from pygments.formatters.html import HtmlFormatter
32     from pygments.styles import get_style_by_name
33     have_pygments = True
34 except ImportError, e:
35     have_pygments = False
36 else:
37     have_pygments = True
38
39 __all__ = ['PygmentsRenderer']
40
41
42 class PygmentsRenderer(Component):
43     """Syntax highlighting based on Pygments."""
44
45     implements(IHTMLPreviewRenderer, IRequestHandler, IRequestFilter,
46                IWikiMacroProvider, ITemplateProvider)
47
48     default_style = Option('mimeviewer', 'pygments_default_style', 'trac',
49         """The default style to use for Pygments syntax highlighting.""")
50
51     pygments_modes = ListOption('mimeviewer', 'pygments_modes',
52         '', doc=
53         """List of additional MIME types known by Pygments.
54
55         For each, a tuple `mimetype:mode:quality` has to be
56         specified, where `mimetype` is the MIME type,
57         `mode` is the corresponding Pygments mode to be used
58         for the conversion and `quality` is the quality ratio
59         associated to this conversion. That can also be used
60         to override the default quality ratio used by the
61         Pygments render.""")
62
63     expand_tabs = True
64     returns_source = True
65
66     QUALITY_RATIO = 7
67
68     EXAMPLE = """<!DOCTYPE html>
69 <html lang="en">
70   <head>
71     <title>Hello, world!</title>
72     <script>
73       $(document).ready(function() {
74         $("h1").fadeIn("slow");
75       });
76     </script>
77   </head>
78   <body>
79     <h1>Hello, world!</h1>
80   </body>
81 </html>"""
82
83     def __init__(self):
84         self.log.debug("Pygments installed? %r", have_pygments)
85         if have_pygments:
86             version = getattr(pygments, '__version__', None)
87             if version:
88                 self.log.debug('Pygments Version: %s' % version)
89
90         self._types = None
91
92     # IHTMLPreviewRenderer implementation
93
94     def get_quality_ratio(self, mimetype):
95         # Extend default MIME type to mode mappings with configured ones
96         self._init_types()
97         try:
98             return self._types[mimetype][1]
99         except KeyError:
100             return 0
101
102     def render(self, req, mimetype, content, filename=None, rev=None):
103         self._init_types()
104         try:
105             mimetype = mimetype.split(';', 1)[0]
106             language = self._types[mimetype][0]
107             return self._highlight(language, content, True)
108         except (KeyError, ValueError):
109             raise Exception("No Pygments lexer found for mime-type '%s'."
110                             % mimetype)
111
112     # IWikiMacroProvider implementation
113
114     def get_macros(self):
115         self._init_types()
116         return self._languages.keys()
117
118     def get_macro_description(self, name):
119         self._init_types()
120         return 'Syntax highlighting for %s using Pygments' % self._languages[name]
121
122     def render_macro(self, req, name, content):
123         self._init_types()
124         return self._highlight(name, content, False)
125
126     # IRequestFilter
127    
128     def pre_process_request(self, req, handler):
129         return handler
130
131     def post_process_request(self, req, template, content_type):
132         if not getattr(req, '_no_pygments_stylesheet', False):
133             add_link(req, 'stylesheet', self.env.href('pygments', '%s.css' %
134                      req.session.get('pygments_style', self.default_style)))
135         return template, content_type
136
137     # IRequestHandler implementation
138
139     def match_request(self, req):
140         if have_pygments:
141             if re.match(r'/pygments/?$', req.path_info):
142                 return True
143             match = re.match(r'/pygments/(\w+)\.css$', req.path_info)
144             if match:
145                 try:
146                     req.args['style'] = get_style_by_name(match.group(1))
147                 except ValueError:
148                     return False
149                 return True
150         return False
151
152     def process_request(self, req):
153         # settings panel
154         if not 'style' in req.args:
155             req._no_pygments_stylesheet = True
156             styles = list(get_all_styles())
157             styles.sort(lambda a, b: cmp(a.lower(), b.lower()))
158
159             if req.method == 'POST':
160                 style = req.args.get('new_style')
161                 if style and style in styles:
162                     req.session['pygments_style'] = style
163
164             output = self._highlight('html', self.EXAMPLE, False)
165             req.hdf['output'] = Markup(output)
166             req.hdf['current'] = req.session.get('pygments_style',
167                                                  self.default_style)
168             req.hdf['styles'] = styles
169             req.hdf['pygments_path'] = self.env.href.pygments()
170             return 'pygments_settings.cs', None
171
172         # provide stylesheet
173         else:
174             style = req.args['style']
175
176             parts = style.__module__.split('.')
177             filename = resource_filename('.'.join(parts[:-1]), parts[-1] + '.py')
178             mtime = datetime.utcfromtimestamp(os.path.getmtime(filename))
179             last_modified = http_date(time.mktime(mtime.timetuple()))
180             if last_modified == req.get_header('If-Modified-Since'):
181                 req.send_response(304)
182                 req.end_headers()
183                 return
184
185             formatter = HtmlFormatter(style=style)
186             content = u'\n\n'.join([
187                 formatter.get_style_defs('div.code pre'),
188                 formatter.get_style_defs('table.code td')
189             ]).encode('utf-8')
190
191             req.send_response(200)
192             req.send_header('Content-Type', 'text/css; charset=utf-8')
193             req.send_header('Last-Modified', last_modified)
194             req.send_header('Content-Length', len(content))
195             req.write(content)
196
197     # ITemplateProvider methods
198
199     def get_templates_dirs(self):
200         return [resource_filename(__name__, 'templates')]
201
202     def get_htdocs_dirs(self):
203         return ()
204
205     # Internal methods
206
207     def _init_types(self):
208         if self._types is None:
209             self._types = {}
210             self._languages = {}
211             if have_pygments:
212                 for name, aliases, _, mimetypes in get_all_lexers():
213                     for mimetype in mimetypes:
214                         self._types[mimetype] = (aliases[0], self.QUALITY_RATIO)
215                     for alias in aliases:
216                         self._languages[alias] = name
217                 self._types.update(
218                     Mimeview(self.env).configured_modes_mapping('pygments')
219                 )
220
221     def _highlight(self, language, content, annotate):
222         formatter = HtmlFormatter(cssclass=not annotate and 'code' or '')
223         html = pygments.highlight(content, get_lexer_by_name(language),
224                                   formatter).rstrip('\n')
225         if annotate:
226             return html[len('<div><pre>'):-len('</pre></div>')].splitlines()
227         return html
228
229
230 def get_all_lexers():
231     from pygments.lexers._mapping import LEXERS
232     from pygments.plugin import find_plugin_lexers
233
234     for item in LEXERS.itervalues():
235         yield item[1:]
236     for cls in find_plugin_lexers():
237         yield cls.name, cls.aliases, cls.filenames, cls.mimetypes
238
239
240 def get_all_styles():
241     from pygments.styles import find_plugin_styles, STYLE_MAP
242     for name in STYLE_MAP:
243         yield name
244     for name, _ in find_plugin_styles():
245         yield name
Note: See TracBrowser for help on using the browser.