##// END OF EJS Templates
markdown: enable gfm by default, this is much standard now and we should use it instead of plain markdown
marcink -
r318:388bda45 default
parent child Browse files
Show More
@@ -1,230 +1,231 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
27 27 import re
28 28 import os
29 29 import logging
30 30 from mako.lookup import TemplateLookup
31 31
32 32 from docutils.core import publish_parts
33 33 from docutils.parsers.rst import directives
34 34 import markdown
35 35
36 36 from rhodecode.lib.markdown_ext import (
37 37 UrlizeExtension, GithubFlavoredMarkdownExtension)
38 38 from rhodecode.lib.utils2 import safe_unicode, md5_safe, MENTIONS_REGEX
39 39
40 40 log = logging.getLogger(__name__)
41 41
42 42 # default renderer used to generate automated comments
43 43 DEFAULT_COMMENTS_RENDERER = 'rst'
44 44
45 45
46 46 class MarkupRenderer(object):
47 47 RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
48 48
49 49 MARKDOWN_PAT = re.compile(r'\.(md|mkdn?|mdown|markdown)$', re.IGNORECASE)
50 50 RST_PAT = re.compile(r'\.re?st$', re.IGNORECASE)
51 51 PLAIN_PAT = re.compile(r'^readme$', re.IGNORECASE)
52 52
53 53 def _detect_renderer(self, source, filename=None):
54 54 """
55 55 runs detection of what renderer should be used for generating html
56 56 from a markup language
57 57
58 58 filename can be also explicitly a renderer name
59 59
60 60 :param source:
61 61 :param filename:
62 62 """
63 63
64 64 if MarkupRenderer.MARKDOWN_PAT.findall(filename):
65 65 detected_renderer = 'markdown'
66 66 elif MarkupRenderer.RST_PAT.findall(filename):
67 67 detected_renderer = 'rst'
68 68 elif MarkupRenderer.PLAIN_PAT.findall(filename):
69 69 detected_renderer = 'rst'
70 70 else:
71 71 detected_renderer = 'plain'
72 72
73 73 return getattr(MarkupRenderer, detected_renderer)
74 74
75 75 def render(self, source, filename=None):
76 76 """
77 77 Renders a given filename using detected renderer
78 78 it detects renderers based on file extension or mimetype.
79 79 At last it will just do a simple html replacing new lines with <br/>
80 80
81 81 :param file_name:
82 82 :param source:
83 83 """
84 84
85 85 renderer = self._detect_renderer(source, filename)
86 86 readme_data = renderer(source)
87 87 return readme_data
88 88
89 89 @classmethod
90 90 def _flavored_markdown(cls, text):
91 91 """
92 92 Github style flavored markdown
93 93
94 94 :param text:
95 95 """
96 96
97 97 # Extract pre blocks.
98 98 extractions = {}
99 99
100 100 def pre_extraction_callback(matchobj):
101 101 digest = md5_safe(matchobj.group(0))
102 102 extractions[digest] = matchobj.group(0)
103 103 return "{gfm-extraction-%s}" % digest
104 104 pattern = re.compile(r'<pre>.*?</pre>', re.MULTILINE | re.DOTALL)
105 105 text = re.sub(pattern, pre_extraction_callback, text)
106 106
107 107 # Prevent foo_bar_baz from ending up with an italic word in the middle.
108 108 def italic_callback(matchobj):
109 109 s = matchobj.group(0)
110 110 if list(s).count('_') >= 2:
111 111 return s.replace('_', r'\_')
112 112 return s
113 113 text = re.sub(r'^(?! {4}|\t)\w+_\w+_\w[\w_]*', italic_callback, text)
114 114
115 115 # Insert pre block extractions.
116 116 def pre_insert_callback(matchobj):
117 117 return '\n\n' + extractions[matchobj.group(1)]
118 118 text = re.sub(r'\{gfm-extraction-([0-9a-f]{32})\}',
119 119 pre_insert_callback, text)
120 120
121 121 return text
122 122
123 123 @classmethod
124 124 def urlify_text(cls, text):
125 125 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
126 126 r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
127 127
128 128 def url_func(match_obj):
129 129 url_full = match_obj.groups()[0]
130 130 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
131 131
132 132 return url_pat.sub(url_func, text)
133 133
134 134 @classmethod
135 135 def plain(cls, source, universal_newline=True):
136 136 source = safe_unicode(source)
137 137 if universal_newline:
138 138 newline = '\n'
139 139 source = newline.join(source.splitlines())
140 140
141 141 source = cls.urlify_text(source)
142 142 return '<br />' + source.replace("\n", '<br />')
143 143
144 144 @classmethod
145 def markdown(cls, source, safe=True, flavored=False, mentions=False):
145 def markdown(cls, source, safe=True, flavored=True, mentions=False):
146 146 # It does not allow to insert inline HTML. In presence of HTML tags, it
147 147 # will replace them instead with [HTML_REMOVED]. This is controlled by
148 148 # the safe_mode=True parameter of the markdown method.
149 149 extensions = ['codehilite', 'extra', 'def_list', 'sane_lists']
150 150 if flavored:
151 151 extensions.append(GithubFlavoredMarkdownExtension())
152 152
153 153 if mentions:
154 154 mention_pat = re.compile(MENTIONS_REGEX)
155 155
156 156 def wrapp(match_obj):
157 157 uname = match_obj.groups()[0]
158 158 return ' **@%(uname)s** ' % {'uname': uname}
159 159 mention_hl = mention_pat.sub(wrapp, source).strip()
160 160 # we extracted mentions render with this using Mentions false
161 161 return cls.markdown(mention_hl, safe=safe, flavored=flavored,
162 162 mentions=False)
163 163
164 164 source = safe_unicode(source)
165 165 try:
166 166 if flavored:
167 167 source = cls._flavored_markdown(source)
168 168 return markdown.markdown(
169 169 source, extensions, safe_mode=True, enable_attributes=False)
170 170 except Exception:
171 171 log.exception('Error when rendering Markdown')
172 172 if safe:
173 log.debug('Fallbacking to render in plain mode')
173 log.debug('Fallback to render in plain mode')
174 174 return cls.plain(source)
175 175 else:
176 176 raise
177 177
178 178 @classmethod
179 179 def rst(cls, source, safe=True, mentions=False):
180 180 if mentions:
181 181 mention_pat = re.compile(MENTIONS_REGEX)
182 182
183 183 def wrapp(match_obj):
184 184 uname = match_obj.groups()[0]
185 185 return ' **@%(uname)s** ' % {'uname': uname}
186 186 mention_hl = mention_pat.sub(wrapp, source).strip()
187 187 # we extracted mentions render with this using Mentions false
188 188 return cls.rst(mention_hl, safe=safe, mentions=False)
189 189
190 190 source = safe_unicode(source)
191 191 try:
192 docutils_settings = dict([(alias, None) for alias in
193 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
192 docutils_settings = dict(
193 [(alias, None) for alias in
194 cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
194 195
195 196 docutils_settings.update({'input_encoding': 'unicode',
196 197 'report_level': 4})
197 198
198 199 for k, v in docutils_settings.iteritems():
199 200 directives.register_directive(k, v)
200 201
201 202 parts = publish_parts(source=source,
202 203 writer_name="html4css1",
203 204 settings_overrides=docutils_settings)
204 205
205 206 return parts['html_title'] + parts["fragment"]
206 207 except Exception:
207 208 log.exception('Error when rendering RST')
208 209 if safe:
209 210 log.debug('Fallbacking to render in plain mode')
210 211 return cls.plain(source)
211 212 else:
212 213 raise
213 214
214 215
215 216 class RstTemplateRenderer(object):
216 217
217 218 def __init__(self):
218 219 base = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
219 220 rst_template_dirs = [os.path.join(base, 'templates', 'rst_templates')]
220 221 self.template_store = TemplateLookup(
221 222 directories=rst_template_dirs,
222 223 input_encoding='utf-8',
223 224 imports=['from rhodecode.lib import helpers as h'])
224 225
225 226 def _get_template(self, templatename):
226 227 return self.template_store.get_template(templatename)
227 228
228 229 def render(self, template_name, **kwargs):
229 230 template = self._get_template(template_name)
230 231 return template.render(**kwargs)
General Comments 0
You need to be logged in to leave comments. Login now