##// END OF EJS Templates
renderer: fixed the helper funtion to original version. This...
marcink -
r401:40cb52d4 default
parent child Browse files
Show More
@@ -1,305 +1,306 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 Renderer for markup languages with ability to parse using rst or markdown
24 24 """
25 25
26 26 import re
27 27 import os
28 28 import logging
29 29 import itertools
30 30
31 31 from mako.lookup import TemplateLookup
32 32
33 33 from docutils.core import publish_parts
34 34 from docutils.parsers.rst import directives
35 35 import markdown
36 36
37 37 from rhodecode.lib.markdown_ext import (
38 38 UrlizeExtension, GithubFlavoredMarkdownExtension)
39 39 from rhodecode.lib.utils2 import safe_unicode, md5_safe, MENTIONS_REGEX
40 40
41 41 log = logging.getLogger(__name__)
42 42
43 43 # default renderer used to generate automated comments
44 44 DEFAULT_COMMENTS_RENDERER = 'rst'
45 45
46 46
47 47 class MarkupRenderer(object):
48 48 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
49 49
50 50 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
51 51 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
52 52 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
53 53
54 54 # list of readme files to search in file tree and display in summary
55 55 # attached weights defines the search order lower is first
56 56 ALL_READMES = [
57 57 ('readme', 0), ('README', 0), ('Readme', 0),
58 58 ('doc/readme', 1), ('doc/README', 1), ('doc/Readme', 1),
59 59 ('Docs/readme', 2), ('Docs/README', 2), ('Docs/Readme', 2),
60 60 ('DOCS/readme', 2), ('DOCS/README', 2), ('DOCS/Readme', 2),
61 61 ('docs/readme', 2), ('docs/README', 2), ('docs/Readme', 2),
62 62 ]
63 63 # extension together with weights. Lower is first means we control how
64 64 # extensions are attached to readme names with those.
65 65 PLAIN_EXTS = [
66 66 ('', 0), # special case that renders READMES names without extension
67 67 ('.text', 2), ('.TEXT', 2),
68 68 ('.txt', 3), ('.TXT', 3)
69 69 ]
70 70
71 71 RST_EXTS = [
72 72 ('.rst', 1), ('.rest', 1),
73 73 ('.RST', 2), ('.REST', 2)
74 74 ]
75 75
76 76 MARKDOWN_EXTS = [
77 77 ('.md', 1), ('.MD', 1),
78 78 ('.mkdn', 2), ('.MKDN', 2),
79 79 ('.mdown', 3), ('.MDOWN', 3),
80 80 ('.markdown', 4), ('.MARKDOWN', 4)
81 81 ]
82 82
83 83 ALL_EXTS = PLAIN_EXTS + MARKDOWN_EXTS + RST_EXTS
84 84
85 85 def _detect_renderer(self, source, filename=None):
86 86 """
87 87 runs detection of what renderer should be used for generating html
88 88 from a markup language
89 89
90 90 filename can be also explicitly a renderer name
91 91
92 92 :param source:
93 93 :param filename:
94 94 """
95 95
96 96 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
97 97 detected_renderer = 'markdown'
98 98 elif MarkupRenderer.RST_PAT.findall(filename):
99 99 detected_renderer = 'rst'
100 100 elif MarkupRenderer.PLAIN_PAT.findall(filename):
101 101 detected_renderer = 'rst'
102 102 else:
103 103 detected_renderer = 'plain'
104 104
105 105 return getattr(MarkupRenderer, detected_renderer)
106 106
107 107 @classmethod
108 108 def renderer_from_filename(cls, filename, exclude):
109 109 """
110 Detect renderer from filename and optionally use exlcude list to
111 remove some options. This is mostly used in helpers
110 Detect renderer markdown/rst from filename and optionally use exclude
111 list to remove some options. This is mostly used in helpers.
112 Returns None when no renderer can be detected.
112 113 """
113 114 def _filter(elements):
114 115 if isinstance(exclude, (list, tuple)):
115 116 return [x for x in elements if x not in exclude]
116 117 return elements
117 118
118 119 if filename.endswith(
119 120 tuple(_filter([x[0] for x in cls.MARKDOWN_EXTS if x[0]]))):
120 121 return 'markdown'
121 122 if filename.endswith(tuple(_filter([x[0] for x in cls.RST_EXTS if x[0]]))):
122 123 return 'rst'
123 124
124 return 'plain'
125 return None
125 126
126 127 @classmethod
127 128 def generate_readmes(cls, all_readmes, extensions):
128 129 combined = itertools.product(all_readmes, extensions)
129 130 # sort by filename weight(y[0][1]) + extensions weight(y[1][1])
130 131 prioritized_readmes = sorted(combined, key=lambda y: y[0][1] + y[1][1])
131 132 # filename, extension
132 133 return [''.join([x[0][0], x[1][0]]) for x in prioritized_readmes]
133 134
134 135 def pick_readme_order(self, default_renderer):
135 136
136 137 if default_renderer == 'markdown':
137 138 markdown = self.generate_readmes(self.ALL_READMES, self.MARKDOWN_EXTS)
138 139 readme_order = markdown + self.generate_readmes(
139 140 self.ALL_READMES, self.RST_EXTS + self.PLAIN_EXTS)
140 141 elif default_renderer == 'rst':
141 142 markdown = self.generate_readmes(self.ALL_READMES, self.RST_EXTS)
142 143 readme_order = markdown + self.generate_readmes(
143 144 self.ALL_READMES, self.MARKDOWN_EXTS + self.PLAIN_EXTS)
144 145 else:
145 146 readme_order = self.generate_readmes(self.ALL_READMES, self.ALL_EXTS)
146 147
147 148 return readme_order
148 149
149 150 def render(self, source, filename=None):
150 151 """
151 152 Renders a given filename using detected renderer
152 153 it detects renderers based on file extension or mimetype.
153 154 At last it will just do a simple html replacing new lines with <br/>
154 155
155 156 :param file_name:
156 157 :param source:
157 158 """
158 159
159 160 renderer = self._detect_renderer(source, filename)
160 161 readme_data = renderer(source)
161 162 return readme_data
162 163
163 164 @classmethod
164 165 def _flavored_markdown(cls, text):
165 166 """
166 167 Github style flavored markdown
167 168
168 169 :param text:
169 170 """
170 171
171 172 # Extract pre blocks.
172 173 extractions = {}
173 174
174 175 def pre_extraction_callback(matchobj):
175 176 digest = md5_safe(matchobj.group(0))
176 177 extractions[digest] = matchobj.group(0)
177 178 return "{gfm-extraction-%s}" % digest
178 179 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
179 180 text = re.sub(pattern, pre_extraction_callback, text)
180 181
181 182 # Prevent foo_bar_baz from ending up with an italic word in the middle.
182 183 def italic_callback(matchobj):
183 184 s = matchobj.group(0)
184 185 if list(s).count('_') >= 2:
185 186 return s.replace('_', r'\_')
186 187 return s
187 188 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
188 189
189 190 # Insert pre block extractions.
190 191 def pre_insert_callback(matchobj):
191 192 return '\n\n' + extractions[matchobj.group(1)]
192 193 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
193 194 pre_insert_callback, text)
194 195
195 196 return text
196 197
197 198 @classmethod
198 199 def urlify_text(cls, text):
199 200 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
200 201 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
201 202
202 203 def url_func(match_obj):
203 204 url_full = match_obj.groups()[0]
204 205 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
205 206
206 207 return url_pat.sub(url_func, text)
207 208
208 209 @classmethod
209 210 def plain(cls, source, universal_newline=True):
210 211 source = safe_unicode(source)
211 212 if universal_newline:
212 213 newline = '\n'
213 214 source = newline.join(source.splitlines())
214 215
215 216 source = cls.urlify_text(source)
216 217 return '<br />' + source.replace("\n", '<br />')
217 218
218 219 @classmethod
219 220 def markdown(cls, source, safe=True, flavored=True, mentions=False):
220 221 # It does not allow to insert inline HTML. In presence of HTML tags, it
221 222 # will replace them instead with [HTML_REMOVED]. This is controlled by
222 223 # the safe_mode=True parameter of the markdown method.
223 224 extensions = ['codehilite', 'extra', 'def_list', 'sane_lists']
224 225 if flavored:
225 226 extensions.append(GithubFlavoredMarkdownExtension())
226 227
227 228 if mentions:
228 229 mention_pat = re.compile(MENTIONS_REGEX)
229 230
230 231 def wrapp(match_obj):
231 232 uname = match_obj.groups()[0]
232 233 return ' **@%(uname)s** ' % {'uname': uname}
233 234 mention_hl = mention_pat.sub(wrapp, source).strip()
234 235 # we extracted mentions render with this using Mentions false
235 236 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
236 237 mentions=False)
237 238
238 239 source = safe_unicode(source)
239 240 try:
240 241 if flavored:
241 242 source = cls._flavored_markdown(source)
242 243 return markdown.markdown(
243 244 source, extensions, safe_mode=True, enable_attributes=False)
244 245 except Exception:
245 246 log.exception('Error when rendering Markdown')
246 247 if safe:
247 248 log.debug('Fallback to render in plain mode')
248 249 return cls.plain(source)
249 250 else:
250 251 raise
251 252
252 253 @classmethod
253 254 def rst(cls, source, safe=True, mentions=False):
254 255 if mentions:
255 256 mention_pat = re.compile(MENTIONS_REGEX)
256 257
257 258 def wrapp(match_obj):
258 259 uname = match_obj.groups()[0]
259 260 return ' **@%(uname)s** ' % {'uname': uname}
260 261 mention_hl = mention_pat.sub(wrapp, source).strip()
261 262 # we extracted mentions render with this using Mentions false
262 263 return cls.rst(mention_hl, safe=safe, mentions=False)
263 264
264 265 source = safe_unicode(source)
265 266 try:
266 267 docutils_settings = dict(
267 268 [(alias, None) for alias in
268 269 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
269 270
270 271 docutils_settings.update({'input_encoding': 'unicode',
271 272 'report_level': 4})
272 273
273 274 for k, v in docutils_settings.iteritems():
274 275 directives.register_directive(k, v)
275 276
276 277 parts = publish_parts(source=source,
277 278 writer_name="html4css1",
278 279 settings_overrides=docutils_settings)
279 280
280 281 return parts['html_title'] + parts["fragment"]
281 282 except Exception:
282 283 log.exception('Error when rendering RST')
283 284 if safe:
284 285 log.debug('Fallbacking to render in plain mode')
285 286 return cls.plain(source)
286 287 else:
287 288 raise
288 289
289 290
290 291 class RstTemplateRenderer(object):
291 292
292 293 def __init__(self):
293 294 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
294 295 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
295 296 self.template_store = TemplateLookup(
296 297 directories=rst_template_dirs,
297 298 input_encoding='utf-8',
298 299 imports=['from rhodecode.lib import helpers as h'])
299 300
300 301 def _get_template(self, templatename):
301 302 return self.template_store.get_template(templatename)
302 303
303 304 def render(self, template_name, **kwargs):
304 305 template = self._get_template(template_name)
305 306 return template.render(**kwargs)
General Comments 0
You need to be logged in to leave comments. Login now