##// END OF EJS Templates
extend helper for better breadcrumbs for repo groups
marcink -
r3012:58f3bba0 beta
parent child Browse files
Show More
@@ -1,1130 +1,1133
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14
14
15 from datetime import datetime
15 from datetime import datetime
16 from pygments.formatters.html import HtmlFormatter
16 from pygments.formatters.html import HtmlFormatter
17 from pygments import highlight as code_highlight
17 from pygments import highlight as code_highlight
18 from pylons import url, request, config
18 from pylons import url, request, config
19 from pylons.i18n.translation import _, ungettext
19 from pylons.i18n.translation import _, ungettext
20 from hashlib import md5
20 from hashlib import md5
21
21
22 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html.tools import *
23 from webhelpers.html.tools import *
24 from webhelpers.html.builder import make_tag
24 from webhelpers.html.builder import make_tag
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 submit, text, password, textarea, title, ul, xml_declaration, radio
28 submit, text, password, textarea, title, ul, xml_declaration, radio
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 replace_whitespace, urlify, truncate, wrap_paragraphs
36 replace_whitespace, urlify, truncate, wrap_paragraphs
37 from webhelpers.date import time_ago_in_words
37 from webhelpers.date import time_ago_in_words
38 from webhelpers.paginate import Page
38 from webhelpers.paginate import Page
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
41
41
42 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils import repo_name_slug
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.markup_renderer import MarkupRenderer
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.db import URL_SEP, Permission
51 from rhodecode.model.db import URL_SEP, Permission
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 html_escape_table = {
56 html_escape_table = {
57 "&": "&",
57 "&": "&",
58 '"': """,
58 '"': """,
59 "'": "'",
59 "'": "'",
60 ">": ">",
60 ">": ">",
61 "<": "&lt;",
61 "<": "&lt;",
62 }
62 }
63
63
64
64
65 def html_escape(text):
65 def html_escape(text):
66 """Produce entities within text."""
66 """Produce entities within text."""
67 return "".join(html_escape_table.get(c, c) for c in text)
67 return "".join(html_escape_table.get(c, c) for c in text)
68
68
69
69
70 def shorter(text, size=20):
70 def shorter(text, size=20):
71 postfix = '...'
71 postfix = '...'
72 if len(text) > size:
72 if len(text) > size:
73 return text[:size - len(postfix)] + postfix
73 return text[:size - len(postfix)] + postfix
74 return text
74 return text
75
75
76
76
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 """
78 """
79 Reset button
79 Reset button
80 """
80 """
81 _set_input_attrs(attrs, type, name, value)
81 _set_input_attrs(attrs, type, name, value)
82 _set_id_attr(attrs, id, name)
82 _set_id_attr(attrs, id, name)
83 convert_boolean_attrs(attrs, ["disabled"])
83 convert_boolean_attrs(attrs, ["disabled"])
84 return HTML.input(**attrs)
84 return HTML.input(**attrs)
85
85
86 reset = _reset
86 reset = _reset
87 safeid = _make_safe_id_component
87 safeid = _make_safe_id_component
88
88
89
89
90 def FID(raw_id, path):
90 def FID(raw_id, path):
91 """
91 """
92 Creates a uniqe ID for filenode based on it's hash of path and revision
92 Creates a uniqe ID for filenode based on it's hash of path and revision
93 it's safe to use in urls
93 it's safe to use in urls
94
94
95 :param raw_id:
95 :param raw_id:
96 :param path:
96 :param path:
97 """
97 """
98
98
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100
100
101
101
102 def get_token():
102 def get_token():
103 """Return the current authentication token, creating one if one doesn't
103 """Return the current authentication token, creating one if one doesn't
104 already exist.
104 already exist.
105 """
105 """
106 token_key = "_authentication_token"
106 token_key = "_authentication_token"
107 from pylons import session
107 from pylons import session
108 if not token_key in session:
108 if not token_key in session:
109 try:
109 try:
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 except AttributeError: # Python < 2.4
111 except AttributeError: # Python < 2.4
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 session[token_key] = token
113 session[token_key] = token
114 if hasattr(session, 'save'):
114 if hasattr(session, 'save'):
115 session.save()
115 session.save()
116 return session[token_key]
116 return session[token_key]
117
117
118
118
119 class _GetError(object):
119 class _GetError(object):
120 """Get error from form_errors, and represent it as span wrapped error
120 """Get error from form_errors, and represent it as span wrapped error
121 message
121 message
122
122
123 :param field_name: field to fetch errors for
123 :param field_name: field to fetch errors for
124 :param form_errors: form errors dict
124 :param form_errors: form errors dict
125 """
125 """
126
126
127 def __call__(self, field_name, form_errors):
127 def __call__(self, field_name, form_errors):
128 tmpl = """<span class="error_msg">%s</span>"""
128 tmpl = """<span class="error_msg">%s</span>"""
129 if form_errors and field_name in form_errors:
129 if form_errors and field_name in form_errors:
130 return literal(tmpl % form_errors.get(field_name))
130 return literal(tmpl % form_errors.get(field_name))
131
131
132 get_error = _GetError()
132 get_error = _GetError()
133
133
134
134
135 class _ToolTip(object):
135 class _ToolTip(object):
136
136
137 def __call__(self, tooltip_title, trim_at=50):
137 def __call__(self, tooltip_title, trim_at=50):
138 """
138 """
139 Special function just to wrap our text into nice formatted
139 Special function just to wrap our text into nice formatted
140 autowrapped text
140 autowrapped text
141
141
142 :param tooltip_title:
142 :param tooltip_title:
143 """
143 """
144 tooltip_title = escape(tooltip_title)
144 tooltip_title = escape(tooltip_title)
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 return tooltip_title
146 return tooltip_title
147 tooltip = _ToolTip()
147 tooltip = _ToolTip()
148
148
149
149
150 class _FilesBreadCrumbs(object):
150 class _FilesBreadCrumbs(object):
151
151
152 def __call__(self, repo_name, rev, paths):
152 def __call__(self, repo_name, rev, paths):
153 if isinstance(paths, str):
153 if isinstance(paths, str):
154 paths = safe_unicode(paths)
154 paths = safe_unicode(paths)
155 url_l = [link_to(repo_name, url('files_home',
155 url_l = [link_to(repo_name, url('files_home',
156 repo_name=repo_name,
156 repo_name=repo_name,
157 revision=rev, f_path=''),
157 revision=rev, f_path=''),
158 class_='ypjax-link')]
158 class_='ypjax-link')]
159 paths_l = paths.split('/')
159 paths_l = paths.split('/')
160 for cnt, p in enumerate(paths_l):
160 for cnt, p in enumerate(paths_l):
161 if p != '':
161 if p != '':
162 url_l.append(link_to(p,
162 url_l.append(link_to(p,
163 url('files_home',
163 url('files_home',
164 repo_name=repo_name,
164 repo_name=repo_name,
165 revision=rev,
165 revision=rev,
166 f_path='/'.join(paths_l[:cnt + 1])
166 f_path='/'.join(paths_l[:cnt + 1])
167 ),
167 ),
168 class_='ypjax-link'
168 class_='ypjax-link'
169 )
169 )
170 )
170 )
171
171
172 return literal('/'.join(url_l))
172 return literal('/'.join(url_l))
173
173
174 files_breadcrumbs = _FilesBreadCrumbs()
174 files_breadcrumbs = _FilesBreadCrumbs()
175
175
176
176
177 class CodeHtmlFormatter(HtmlFormatter):
177 class CodeHtmlFormatter(HtmlFormatter):
178 """
178 """
179 My code Html Formatter for source codes
179 My code Html Formatter for source codes
180 """
180 """
181
181
182 def wrap(self, source, outfile):
182 def wrap(self, source, outfile):
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184
184
185 def _wrap_code(self, source):
185 def _wrap_code(self, source):
186 for cnt, it in enumerate(source):
186 for cnt, it in enumerate(source):
187 i, t = it
187 i, t = it
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 yield i, t
189 yield i, t
190
190
191 def _wrap_tablelinenos(self, inner):
191 def _wrap_tablelinenos(self, inner):
192 dummyoutfile = StringIO.StringIO()
192 dummyoutfile = StringIO.StringIO()
193 lncount = 0
193 lncount = 0
194 for t, line in inner:
194 for t, line in inner:
195 if t:
195 if t:
196 lncount += 1
196 lncount += 1
197 dummyoutfile.write(line)
197 dummyoutfile.write(line)
198
198
199 fl = self.linenostart
199 fl = self.linenostart
200 mw = len(str(lncount + fl - 1))
200 mw = len(str(lncount + fl - 1))
201 sp = self.linenospecial
201 sp = self.linenospecial
202 st = self.linenostep
202 st = self.linenostep
203 la = self.lineanchors
203 la = self.lineanchors
204 aln = self.anchorlinenos
204 aln = self.anchorlinenos
205 nocls = self.noclasses
205 nocls = self.noclasses
206 if sp:
206 if sp:
207 lines = []
207 lines = []
208
208
209 for i in range(fl, fl + lncount):
209 for i in range(fl, fl + lncount):
210 if i % st == 0:
210 if i % st == 0:
211 if i % sp == 0:
211 if i % sp == 0:
212 if aln:
212 if aln:
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 (la, i, mw, i))
214 (la, i, mw, i))
215 else:
215 else:
216 lines.append('<span class="special">%*d</span>' % (mw, i))
216 lines.append('<span class="special">%*d</span>' % (mw, i))
217 else:
217 else:
218 if aln:
218 if aln:
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 else:
220 else:
221 lines.append('%*d' % (mw, i))
221 lines.append('%*d' % (mw, i))
222 else:
222 else:
223 lines.append('')
223 lines.append('')
224 ls = '\n'.join(lines)
224 ls = '\n'.join(lines)
225 else:
225 else:
226 lines = []
226 lines = []
227 for i in range(fl, fl + lncount):
227 for i in range(fl, fl + lncount):
228 if i % st == 0:
228 if i % st == 0:
229 if aln:
229 if aln:
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 else:
231 else:
232 lines.append('%*d' % (mw, i))
232 lines.append('%*d' % (mw, i))
233 else:
233 else:
234 lines.append('')
234 lines.append('')
235 ls = '\n'.join(lines)
235 ls = '\n'.join(lines)
236
236
237 # in case you wonder about the seemingly redundant <div> here: since the
237 # in case you wonder about the seemingly redundant <div> here: since the
238 # content in the other cell also is wrapped in a div, some browsers in
238 # content in the other cell also is wrapped in a div, some browsers in
239 # some configurations seem to mess up the formatting...
239 # some configurations seem to mess up the formatting...
240 if nocls:
240 if nocls:
241 yield 0, ('<table class="%stable">' % self.cssclass +
241 yield 0, ('<table class="%stable">' % self.cssclass +
242 '<tr><td><div class="linenodiv" '
242 '<tr><td><div class="linenodiv" '
243 'style="background-color: #f0f0f0; padding-right: 10px">'
243 'style="background-color: #f0f0f0; padding-right: 10px">'
244 '<pre style="line-height: 125%">' +
244 '<pre style="line-height: 125%">' +
245 ls + '</pre></div></td><td id="hlcode" class="code">')
245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 else:
246 else:
247 yield 0, ('<table class="%stable">' % self.cssclass +
247 yield 0, ('<table class="%stable">' % self.cssclass +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 ls + '</pre></div></td><td id="hlcode" class="code">')
249 ls + '</pre></div></td><td id="hlcode" class="code">')
250 yield 0, dummyoutfile.getvalue()
250 yield 0, dummyoutfile.getvalue()
251 yield 0, '</td></tr></table>'
251 yield 0, '</td></tr></table>'
252
252
253
253
254 def pygmentize(filenode, **kwargs):
254 def pygmentize(filenode, **kwargs):
255 """pygmentize function using pygments
255 """pygmentize function using pygments
256
256
257 :param filenode:
257 :param filenode:
258 """
258 """
259
259
260 return literal(code_highlight(filenode.content,
260 return literal(code_highlight(filenode.content,
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262
262
263
263
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 """
265 """
266 pygmentize function for annotation
266 pygmentize function for annotation
267
267
268 :param filenode:
268 :param filenode:
269 """
269 """
270
270
271 color_dict = {}
271 color_dict = {}
272
272
273 def gen_color(n=10000):
273 def gen_color(n=10000):
274 """generator for getting n of evenly distributed colors using
274 """generator for getting n of evenly distributed colors using
275 hsv color and golden ratio. It always return same order of colors
275 hsv color and golden ratio. It always return same order of colors
276
276
277 :returns: RGB tuple
277 :returns: RGB tuple
278 """
278 """
279
279
280 def hsv_to_rgb(h, s, v):
280 def hsv_to_rgb(h, s, v):
281 if s == 0.0:
281 if s == 0.0:
282 return v, v, v
282 return v, v, v
283 i = int(h * 6.0) # XXX assume int() truncates!
283 i = int(h * 6.0) # XXX assume int() truncates!
284 f = (h * 6.0) - i
284 f = (h * 6.0) - i
285 p = v * (1.0 - s)
285 p = v * (1.0 - s)
286 q = v * (1.0 - s * f)
286 q = v * (1.0 - s * f)
287 t = v * (1.0 - s * (1.0 - f))
287 t = v * (1.0 - s * (1.0 - f))
288 i = i % 6
288 i = i % 6
289 if i == 0:
289 if i == 0:
290 return v, t, p
290 return v, t, p
291 if i == 1:
291 if i == 1:
292 return q, v, p
292 return q, v, p
293 if i == 2:
293 if i == 2:
294 return p, v, t
294 return p, v, t
295 if i == 3:
295 if i == 3:
296 return p, q, v
296 return p, q, v
297 if i == 4:
297 if i == 4:
298 return t, p, v
298 return t, p, v
299 if i == 5:
299 if i == 5:
300 return v, p, q
300 return v, p, q
301
301
302 golden_ratio = 0.618033988749895
302 golden_ratio = 0.618033988749895
303 h = 0.22717784590367374
303 h = 0.22717784590367374
304
304
305 for _ in xrange(n):
305 for _ in xrange(n):
306 h += golden_ratio
306 h += golden_ratio
307 h %= 1
307 h %= 1
308 HSV_tuple = [h, 0.95, 0.95]
308 HSV_tuple = [h, 0.95, 0.95]
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311
311
312 cgenerator = gen_color()
312 cgenerator = gen_color()
313
313
314 def get_color_string(cs):
314 def get_color_string(cs):
315 if cs in color_dict:
315 if cs in color_dict:
316 col = color_dict[cs]
316 col = color_dict[cs]
317 else:
317 else:
318 col = color_dict[cs] = cgenerator.next()
318 col = color_dict[cs] = cgenerator.next()
319 return "color: rgb(%s)! important;" % (', '.join(col))
319 return "color: rgb(%s)! important;" % (', '.join(col))
320
320
321 def url_func(repo_name):
321 def url_func(repo_name):
322
322
323 def _url_func(changeset):
323 def _url_func(changeset):
324 author = changeset.author
324 author = changeset.author
325 date = changeset.date
325 date = changeset.date
326 message = tooltip(changeset.message)
326 message = tooltip(changeset.message)
327
327
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 "</b> %s<br/></div>")
330 "</b> %s<br/></div>")
331
331
332 tooltip_html = tooltip_html % (author, date, message)
332 tooltip_html = tooltip_html % (author, date, message)
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 short_id(changeset.raw_id))
334 short_id(changeset.raw_id))
335 uri = link_to(
335 uri = link_to(
336 lnk_format,
336 lnk_format,
337 url('changeset_home', repo_name=repo_name,
337 url('changeset_home', repo_name=repo_name,
338 revision=changeset.raw_id),
338 revision=changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
340 class_='tooltip',
340 class_='tooltip',
341 title=tooltip_html
341 title=tooltip_html
342 )
342 )
343
343
344 uri += '\n'
344 uri += '\n'
345 return uri
345 return uri
346 return _url_func
346 return _url_func
347
347
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349
349
350
350
351 def is_following_repo(repo_name, user_id):
351 def is_following_repo(repo_name, user_id):
352 from rhodecode.model.scm import ScmModel
352 from rhodecode.model.scm import ScmModel
353 return ScmModel().is_following_repo(repo_name, user_id)
353 return ScmModel().is_following_repo(repo_name, user_id)
354
354
355 flash = _Flash()
355 flash = _Flash()
356
356
357 #==============================================================================
357 #==============================================================================
358 # SCM FILTERS available via h.
358 # SCM FILTERS available via h.
359 #==============================================================================
359 #==============================================================================
360 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.vcs.utils import author_name, author_email
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 from rhodecode.model.db import User, ChangesetStatus
362 from rhodecode.model.db import User, ChangesetStatus
363
363
364 age = lambda x: _age(x)
364 age = lambda x: _age(x)
365 capitalize = lambda x: x.capitalize()
365 capitalize = lambda x: x.capitalize()
366 email = author_email
366 email = author_email
367 short_id = lambda x: x[:12]
367 short_id = lambda x: x[:12]
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
369
369
370
370
371 def fmt_date(date):
371 def fmt_date(date):
372 if date:
372 if date:
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 return date.strftime(_fmt).decode('utf8')
374 return date.strftime(_fmt).decode('utf8')
375
375
376 return ""
376 return ""
377
377
378
378
379 def is_git(repository):
379 def is_git(repository):
380 if hasattr(repository, 'alias'):
380 if hasattr(repository, 'alias'):
381 _type = repository.alias
381 _type = repository.alias
382 elif hasattr(repository, 'repo_type'):
382 elif hasattr(repository, 'repo_type'):
383 _type = repository.repo_type
383 _type = repository.repo_type
384 else:
384 else:
385 _type = repository
385 _type = repository
386 return _type == 'git'
386 return _type == 'git'
387
387
388
388
389 def is_hg(repository):
389 def is_hg(repository):
390 if hasattr(repository, 'alias'):
390 if hasattr(repository, 'alias'):
391 _type = repository.alias
391 _type = repository.alias
392 elif hasattr(repository, 'repo_type'):
392 elif hasattr(repository, 'repo_type'):
393 _type = repository.repo_type
393 _type = repository.repo_type
394 else:
394 else:
395 _type = repository
395 _type = repository
396 return _type == 'hg'
396 return _type == 'hg'
397
397
398
398
399 def email_or_none(author):
399 def email_or_none(author):
400 # extract email from the commit string
400 # extract email from the commit string
401 _email = email(author)
401 _email = email(author)
402 if _email != '':
402 if _email != '':
403 # check it against RhodeCode database, and use the MAIN email for this
403 # check it against RhodeCode database, and use the MAIN email for this
404 # user
404 # user
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 if user is not None:
406 if user is not None:
407 return user.email
407 return user.email
408 return _email
408 return _email
409
409
410 # See if it contains a username we can get an email from
410 # See if it contains a username we can get an email from
411 user = User.get_by_username(author_name(author), case_insensitive=True,
411 user = User.get_by_username(author_name(author), case_insensitive=True,
412 cache=True)
412 cache=True)
413 if user is not None:
413 if user is not None:
414 return user.email
414 return user.email
415
415
416 # No valid email, not a valid user in the system, none!
416 # No valid email, not a valid user in the system, none!
417 return None
417 return None
418
418
419
419
420 def person(author, show_attr="username_and_name"):
420 def person(author, show_attr="username_and_name"):
421 # attr to return from fetched user
421 # attr to return from fetched user
422 person_getter = lambda usr: getattr(usr, show_attr)
422 person_getter = lambda usr: getattr(usr, show_attr)
423
423
424 # Valid email in the attribute passed, see if they're in the system
424 # Valid email in the attribute passed, see if they're in the system
425 _email = email(author)
425 _email = email(author)
426 if _email != '':
426 if _email != '':
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 if user is not None:
428 if user is not None:
429 return person_getter(user)
429 return person_getter(user)
430 return _email
430 return _email
431
431
432 # Maybe it's a username?
432 # Maybe it's a username?
433 _author = author_name(author)
433 _author = author_name(author)
434 user = User.get_by_username(_author, case_insensitive=True,
434 user = User.get_by_username(_author, case_insensitive=True,
435 cache=True)
435 cache=True)
436 if user is not None:
436 if user is not None:
437 return person_getter(user)
437 return person_getter(user)
438
438
439 # Still nothing? Just pass back the author name then
439 # Still nothing? Just pass back the author name then
440 return _author
440 return _author
441
441
442
442
443 def person_by_id(id_, show_attr="username_and_name"):
443 def person_by_id(id_, show_attr="username_and_name"):
444 # attr to return from fetched user
444 # attr to return from fetched user
445 person_getter = lambda usr: getattr(usr, show_attr)
445 person_getter = lambda usr: getattr(usr, show_attr)
446
446
447 #maybe it's an ID ?
447 #maybe it's an ID ?
448 if str(id_).isdigit() or isinstance(id_, int):
448 if str(id_).isdigit() or isinstance(id_, int):
449 id_ = int(id_)
449 id_ = int(id_)
450 user = User.get(id_)
450 user = User.get(id_)
451 if user is not None:
451 if user is not None:
452 return person_getter(user)
452 return person_getter(user)
453 return id_
453 return id_
454
454
455
455
456 def desc_stylize(value):
456 def desc_stylize(value):
457 """
457 """
458 converts tags from value into html equivalent
458 converts tags from value into html equivalent
459
459
460 :param value:
460 :param value:
461 """
461 """
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
469 '<div class="metatag" tag="lang">\\2</div>', value)
469 '<div class="metatag" tag="lang">\\2</div>', value)
470 value = re.sub(r'\[([a-z]+)\]',
470 value = re.sub(r'\[([a-z]+)\]',
471 '<div class="metatag" tag="\\1">\\1</div>', value)
471 '<div class="metatag" tag="\\1">\\1</div>', value)
472
472
473 return value
473 return value
474
474
475
475
476 def bool2icon(value):
476 def bool2icon(value):
477 """Returns True/False values represented as small html image of true/false
477 """Returns True/False values represented as small html image of true/false
478 icons
478 icons
479
479
480 :param value: bool value
480 :param value: bool value
481 """
481 """
482
482
483 if value is True:
483 if value is True:
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 alt=_('True'))
485 alt=_('True'))
486
486
487 if value is False:
487 if value is False:
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 alt=_('False'))
489 alt=_('False'))
490
490
491 return value
491 return value
492
492
493
493
494 def action_parser(user_log, feed=False, parse_cs=False):
494 def action_parser(user_log, feed=False, parse_cs=False):
495 """
495 """
496 This helper will action_map the specified string action into translated
496 This helper will action_map the specified string action into translated
497 fancy names with icons and links
497 fancy names with icons and links
498
498
499 :param user_log: user log instance
499 :param user_log: user log instance
500 :param feed: use output for feeds (no html and fancy icons)
500 :param feed: use output for feeds (no html and fancy icons)
501 :param parse_cs: parse Changesets into VCS instances
501 :param parse_cs: parse Changesets into VCS instances
502 """
502 """
503
503
504 action = user_log.action
504 action = user_log.action
505 action_params = ' '
505 action_params = ' '
506
506
507 x = action.split(':')
507 x = action.split(':')
508
508
509 if len(x) > 1:
509 if len(x) > 1:
510 action, action_params = x
510 action, action_params = x
511
511
512 def get_cs_links():
512 def get_cs_links():
513 revs_limit = 3 # display this amount always
513 revs_limit = 3 # display this amount always
514 revs_top_limit = 50 # show upto this amount of changesets hidden
514 revs_top_limit = 50 # show upto this amount of changesets hidden
515 revs_ids = action_params.split(',')
515 revs_ids = action_params.split(',')
516 deleted = user_log.repository is None
516 deleted = user_log.repository is None
517 if deleted:
517 if deleted:
518 return ','.join(revs_ids)
518 return ','.join(revs_ids)
519
519
520 repo_name = user_log.repository.repo_name
520 repo_name = user_log.repository.repo_name
521
521
522 def lnk(rev, repo_name):
522 def lnk(rev, repo_name):
523 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
523 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
524 lazy_cs = True
524 lazy_cs = True
525 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
525 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
526 lazy_cs = False
526 lazy_cs = False
527 lbl = '?'
527 lbl = '?'
528 if rev.op == 'delete_branch':
528 if rev.op == 'delete_branch':
529 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
529 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
530 title = ''
530 title = ''
531 elif rev.op == 'tag':
531 elif rev.op == 'tag':
532 lbl = '%s' % _('Created tag: %s') % rev.ref_name
532 lbl = '%s' % _('Created tag: %s') % rev.ref_name
533 title = ''
533 title = ''
534 _url = '#'
534 _url = '#'
535
535
536 else:
536 else:
537 lbl = '%s' % (rev.short_id[:8])
537 lbl = '%s' % (rev.short_id[:8])
538 _url = url('changeset_home', repo_name=repo_name,
538 _url = url('changeset_home', repo_name=repo_name,
539 revision=rev.raw_id)
539 revision=rev.raw_id)
540 title = tooltip(rev.message)
540 title = tooltip(rev.message)
541 else:
541 else:
542 ## changeset cannot be found/striped/removed etc.
542 ## changeset cannot be found/striped/removed etc.
543 lbl = ('%s' % rev)[:12]
543 lbl = ('%s' % rev)[:12]
544 _url = '#'
544 _url = '#'
545 title = _('Changeset not found')
545 title = _('Changeset not found')
546 if parse_cs:
546 if parse_cs:
547 return link_to(lbl, _url, title=title, class_='tooltip')
547 return link_to(lbl, _url, title=title, class_='tooltip')
548 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
548 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
549 class_='lazy-cs' if lazy_cs else '')
549 class_='lazy-cs' if lazy_cs else '')
550
550
551 revs = []
551 revs = []
552 if len(filter(lambda v: v != '', revs_ids)) > 0:
552 if len(filter(lambda v: v != '', revs_ids)) > 0:
553 repo = None
553 repo = None
554 for rev in revs_ids[:revs_top_limit]:
554 for rev in revs_ids[:revs_top_limit]:
555 _op = _name = None
555 _op = _name = None
556 if len(rev.split('=>')) == 2:
556 if len(rev.split('=>')) == 2:
557 _op, _name = rev.split('=>')
557 _op, _name = rev.split('=>')
558
558
559 # we want parsed changesets, or new log store format is bad
559 # we want parsed changesets, or new log store format is bad
560 if parse_cs:
560 if parse_cs:
561 try:
561 try:
562 if repo is None:
562 if repo is None:
563 repo = user_log.repository.scm_instance
563 repo = user_log.repository.scm_instance
564 _rev = repo.get_changeset(rev)
564 _rev = repo.get_changeset(rev)
565 revs.append(_rev)
565 revs.append(_rev)
566 except ChangesetDoesNotExistError:
566 except ChangesetDoesNotExistError:
567 log.error('cannot find revision %s in this repo' % rev)
567 log.error('cannot find revision %s in this repo' % rev)
568 revs.append(rev)
568 revs.append(rev)
569 continue
569 continue
570 else:
570 else:
571 _rev = AttributeDict({
571 _rev = AttributeDict({
572 'short_id': rev[:12],
572 'short_id': rev[:12],
573 'raw_id': rev,
573 'raw_id': rev,
574 'message': '',
574 'message': '',
575 'op': _op,
575 'op': _op,
576 'ref_name': _name
576 'ref_name': _name
577 })
577 })
578 revs.append(_rev)
578 revs.append(_rev)
579 cs_links = []
579 cs_links = []
580 cs_links.append(" " + ', '.join(
580 cs_links.append(" " + ', '.join(
581 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
581 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
582 )
582 )
583 )
583 )
584
584
585 compare_view = (
585 compare_view = (
586 ' <div class="compare_view tooltip" title="%s">'
586 ' <div class="compare_view tooltip" title="%s">'
587 '<a href="%s">%s</a> </div>' % (
587 '<a href="%s">%s</a> </div>' % (
588 _('Show all combined changesets %s->%s') % (
588 _('Show all combined changesets %s->%s') % (
589 revs_ids[0][:12], revs_ids[-1][:12]
589 revs_ids[0][:12], revs_ids[-1][:12]
590 ),
590 ),
591 url('changeset_home', repo_name=repo_name,
591 url('changeset_home', repo_name=repo_name,
592 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
592 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
593 ),
593 ),
594 _('compare view')
594 _('compare view')
595 )
595 )
596 )
596 )
597
597
598 # if we have exactly one more than normally displayed
598 # if we have exactly one more than normally displayed
599 # just display it, takes less space than displaying
599 # just display it, takes less space than displaying
600 # "and 1 more revisions"
600 # "and 1 more revisions"
601 if len(revs_ids) == revs_limit + 1:
601 if len(revs_ids) == revs_limit + 1:
602 rev = revs[revs_limit]
602 rev = revs[revs_limit]
603 cs_links.append(", " + lnk(rev, repo_name))
603 cs_links.append(", " + lnk(rev, repo_name))
604
604
605 # hidden-by-default ones
605 # hidden-by-default ones
606 if len(revs_ids) > revs_limit + 1:
606 if len(revs_ids) > revs_limit + 1:
607 uniq_id = revs_ids[0]
607 uniq_id = revs_ids[0]
608 html_tmpl = (
608 html_tmpl = (
609 '<span> %s <a class="show_more" id="_%s" '
609 '<span> %s <a class="show_more" id="_%s" '
610 'href="#more">%s</a> %s</span>'
610 'href="#more">%s</a> %s</span>'
611 )
611 )
612 if not feed:
612 if not feed:
613 cs_links.append(html_tmpl % (
613 cs_links.append(html_tmpl % (
614 _('and'),
614 _('and'),
615 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
615 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
616 _('revisions')
616 _('revisions')
617 )
617 )
618 )
618 )
619
619
620 if not feed:
620 if not feed:
621 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
621 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
622 else:
622 else:
623 html_tmpl = '<span id="%s"> %s </span>'
623 html_tmpl = '<span id="%s"> %s </span>'
624
624
625 morelinks = ', '.join(
625 morelinks = ', '.join(
626 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
626 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
627 )
627 )
628
628
629 if len(revs_ids) > revs_top_limit:
629 if len(revs_ids) > revs_top_limit:
630 morelinks += ', ...'
630 morelinks += ', ...'
631
631
632 cs_links.append(html_tmpl % (uniq_id, morelinks))
632 cs_links.append(html_tmpl % (uniq_id, morelinks))
633 if len(revs) > 1:
633 if len(revs) > 1:
634 cs_links.append(compare_view)
634 cs_links.append(compare_view)
635 return ''.join(cs_links)
635 return ''.join(cs_links)
636
636
637 def get_fork_name():
637 def get_fork_name():
638 repo_name = action_params
638 repo_name = action_params
639 _url = url('summary_home', repo_name=repo_name)
639 _url = url('summary_home', repo_name=repo_name)
640 return _('fork name %s') % link_to(action_params, _url)
640 return _('fork name %s') % link_to(action_params, _url)
641
641
642 def get_user_name():
642 def get_user_name():
643 user_name = action_params
643 user_name = action_params
644 return user_name
644 return user_name
645
645
646 def get_users_group():
646 def get_users_group():
647 group_name = action_params
647 group_name = action_params
648 return group_name
648 return group_name
649
649
650 def get_pull_request():
650 def get_pull_request():
651 pull_request_id = action_params
651 pull_request_id = action_params
652 repo_name = user_log.repository.repo_name
652 repo_name = user_log.repository.repo_name
653 return link_to(_('Pull request #%s') % pull_request_id,
653 return link_to(_('Pull request #%s') % pull_request_id,
654 url('pullrequest_show', repo_name=repo_name,
654 url('pullrequest_show', repo_name=repo_name,
655 pull_request_id=pull_request_id))
655 pull_request_id=pull_request_id))
656
656
657 # action : translated str, callback(extractor), icon
657 # action : translated str, callback(extractor), icon
658 action_map = {
658 action_map = {
659 'user_deleted_repo': (_('[deleted] repository'),
659 'user_deleted_repo': (_('[deleted] repository'),
660 None, 'database_delete.png'),
660 None, 'database_delete.png'),
661 'user_created_repo': (_('[created] repository'),
661 'user_created_repo': (_('[created] repository'),
662 None, 'database_add.png'),
662 None, 'database_add.png'),
663 'user_created_fork': (_('[created] repository as fork'),
663 'user_created_fork': (_('[created] repository as fork'),
664 None, 'arrow_divide.png'),
664 None, 'arrow_divide.png'),
665 'user_forked_repo': (_('[forked] repository'),
665 'user_forked_repo': (_('[forked] repository'),
666 get_fork_name, 'arrow_divide.png'),
666 get_fork_name, 'arrow_divide.png'),
667 'user_updated_repo': (_('[updated] repository'),
667 'user_updated_repo': (_('[updated] repository'),
668 None, 'database_edit.png'),
668 None, 'database_edit.png'),
669 'admin_deleted_repo': (_('[delete] repository'),
669 'admin_deleted_repo': (_('[delete] repository'),
670 None, 'database_delete.png'),
670 None, 'database_delete.png'),
671 'admin_created_repo': (_('[created] repository'),
671 'admin_created_repo': (_('[created] repository'),
672 None, 'database_add.png'),
672 None, 'database_add.png'),
673 'admin_forked_repo': (_('[forked] repository'),
673 'admin_forked_repo': (_('[forked] repository'),
674 None, 'arrow_divide.png'),
674 None, 'arrow_divide.png'),
675 'admin_updated_repo': (_('[updated] repository'),
675 'admin_updated_repo': (_('[updated] repository'),
676 None, 'database_edit.png'),
676 None, 'database_edit.png'),
677 'admin_created_user': (_('[created] user'),
677 'admin_created_user': (_('[created] user'),
678 get_user_name, 'user_add.png'),
678 get_user_name, 'user_add.png'),
679 'admin_updated_user': (_('[updated] user'),
679 'admin_updated_user': (_('[updated] user'),
680 get_user_name, 'user_edit.png'),
680 get_user_name, 'user_edit.png'),
681 'admin_created_users_group': (_('[created] users group'),
681 'admin_created_users_group': (_('[created] users group'),
682 get_users_group, 'group_add.png'),
682 get_users_group, 'group_add.png'),
683 'admin_updated_users_group': (_('[updated] users group'),
683 'admin_updated_users_group': (_('[updated] users group'),
684 get_users_group, 'group_edit.png'),
684 get_users_group, 'group_edit.png'),
685 'user_commented_revision': (_('[commented] on revision in repository'),
685 'user_commented_revision': (_('[commented] on revision in repository'),
686 get_cs_links, 'comment_add.png'),
686 get_cs_links, 'comment_add.png'),
687 'user_commented_pull_request': (_('[commented] on pull request for'),
687 'user_commented_pull_request': (_('[commented] on pull request for'),
688 get_pull_request, 'comment_add.png'),
688 get_pull_request, 'comment_add.png'),
689 'user_closed_pull_request': (_('[closed] pull request for'),
689 'user_closed_pull_request': (_('[closed] pull request for'),
690 get_pull_request, 'tick.png'),
690 get_pull_request, 'tick.png'),
691 'push': (_('[pushed] into'),
691 'push': (_('[pushed] into'),
692 get_cs_links, 'script_add.png'),
692 get_cs_links, 'script_add.png'),
693 'push_local': (_('[committed via RhodeCode] into repository'),
693 'push_local': (_('[committed via RhodeCode] into repository'),
694 get_cs_links, 'script_edit.png'),
694 get_cs_links, 'script_edit.png'),
695 'push_remote': (_('[pulled from remote] into repository'),
695 'push_remote': (_('[pulled from remote] into repository'),
696 get_cs_links, 'connect.png'),
696 get_cs_links, 'connect.png'),
697 'pull': (_('[pulled] from'),
697 'pull': (_('[pulled] from'),
698 None, 'down_16.png'),
698 None, 'down_16.png'),
699 'started_following_repo': (_('[started following] repository'),
699 'started_following_repo': (_('[started following] repository'),
700 None, 'heart_add.png'),
700 None, 'heart_add.png'),
701 'stopped_following_repo': (_('[stopped following] repository'),
701 'stopped_following_repo': (_('[stopped following] repository'),
702 None, 'heart_delete.png'),
702 None, 'heart_delete.png'),
703 }
703 }
704
704
705 action_str = action_map.get(action, action)
705 action_str = action_map.get(action, action)
706 if feed:
706 if feed:
707 action = action_str[0].replace('[', '').replace(']', '')
707 action = action_str[0].replace('[', '').replace(']', '')
708 else:
708 else:
709 action = action_str[0]\
709 action = action_str[0]\
710 .replace('[', '<span class="journal_highlight">')\
710 .replace('[', '<span class="journal_highlight">')\
711 .replace(']', '</span>')
711 .replace(']', '</span>')
712
712
713 action_params_func = lambda: ""
713 action_params_func = lambda: ""
714
714
715 if callable(action_str[1]):
715 if callable(action_str[1]):
716 action_params_func = action_str[1]
716 action_params_func = action_str[1]
717
717
718 def action_parser_icon():
718 def action_parser_icon():
719 action = user_log.action
719 action = user_log.action
720 action_params = None
720 action_params = None
721 x = action.split(':')
721 x = action.split(':')
722
722
723 if len(x) > 1:
723 if len(x) > 1:
724 action, action_params = x
724 action, action_params = x
725
725
726 tmpl = """<img src="%s%s" alt="%s"/>"""
726 tmpl = """<img src="%s%s" alt="%s"/>"""
727 ico = action_map.get(action, ['', '', ''])[2]
727 ico = action_map.get(action, ['', '', ''])[2]
728 return literal(tmpl % ((url('/images/icons/')), ico, action))
728 return literal(tmpl % ((url('/images/icons/')), ico, action))
729
729
730 # returned callbacks we need to call to get
730 # returned callbacks we need to call to get
731 return [lambda: literal(action), action_params_func, action_parser_icon]
731 return [lambda: literal(action), action_params_func, action_parser_icon]
732
732
733
733
734
734
735 #==============================================================================
735 #==============================================================================
736 # PERMS
736 # PERMS
737 #==============================================================================
737 #==============================================================================
738 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
738 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
739 HasRepoPermissionAny, HasRepoPermissionAll
739 HasRepoPermissionAny, HasRepoPermissionAll
740
740
741
741
742 #==============================================================================
742 #==============================================================================
743 # GRAVATAR URL
743 # GRAVATAR URL
744 #==============================================================================
744 #==============================================================================
745
745
746 def gravatar_url(email_address, size=30):
746 def gravatar_url(email_address, size=30):
747 from pylons import url ## doh, we need to re-import url to mock it later
747 from pylons import url ## doh, we need to re-import url to mock it later
748 if(str2bool(config['app_conf'].get('use_gravatar')) and
748 if(str2bool(config['app_conf'].get('use_gravatar')) and
749 config['app_conf'].get('alternative_gravatar_url')):
749 config['app_conf'].get('alternative_gravatar_url')):
750 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
750 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
751 parsed_url = urlparse.urlparse(url.current(qualified=True))
751 parsed_url = urlparse.urlparse(url.current(qualified=True))
752 tmpl = tmpl.replace('{email}', email_address)\
752 tmpl = tmpl.replace('{email}', email_address)\
753 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
753 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
754 .replace('{netloc}', parsed_url.netloc)\
754 .replace('{netloc}', parsed_url.netloc)\
755 .replace('{scheme}', parsed_url.scheme)\
755 .replace('{scheme}', parsed_url.scheme)\
756 .replace('{size}', str(size))
756 .replace('{size}', str(size))
757 return tmpl
757 return tmpl
758
758
759 if (not str2bool(config['app_conf'].get('use_gravatar')) or
759 if (not str2bool(config['app_conf'].get('use_gravatar')) or
760 not email_address or email_address == 'anonymous@rhodecode.org'):
760 not email_address or email_address == 'anonymous@rhodecode.org'):
761 f = lambda a, l: min(l, key=lambda x: abs(x - a))
761 f = lambda a, l: min(l, key=lambda x: abs(x - a))
762 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
762 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
763
763
764 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
764 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
765 default = 'identicon'
765 default = 'identicon'
766 baseurl_nossl = "http://www.gravatar.com/avatar/"
766 baseurl_nossl = "http://www.gravatar.com/avatar/"
767 baseurl_ssl = "https://secure.gravatar.com/avatar/"
767 baseurl_ssl = "https://secure.gravatar.com/avatar/"
768 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
768 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
769
769
770 if isinstance(email_address, unicode):
770 if isinstance(email_address, unicode):
771 #hashlib crashes on unicode items
771 #hashlib crashes on unicode items
772 email_address = safe_str(email_address)
772 email_address = safe_str(email_address)
773 # construct the url
773 # construct the url
774 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
774 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
775 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
775 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
776
776
777 return gravatar_url
777 return gravatar_url
778
778
779
779
780 #==============================================================================
780 #==============================================================================
781 # REPO PAGER, PAGER FOR REPOSITORY
781 # REPO PAGER, PAGER FOR REPOSITORY
782 #==============================================================================
782 #==============================================================================
783 class RepoPage(Page):
783 class RepoPage(Page):
784
784
785 def __init__(self, collection, page=1, items_per_page=20,
785 def __init__(self, collection, page=1, items_per_page=20,
786 item_count=None, url=None, **kwargs):
786 item_count=None, url=None, **kwargs):
787
787
788 """Create a "RepoPage" instance. special pager for paging
788 """Create a "RepoPage" instance. special pager for paging
789 repository
789 repository
790 """
790 """
791 self._url_generator = url
791 self._url_generator = url
792
792
793 # Safe the kwargs class-wide so they can be used in the pager() method
793 # Safe the kwargs class-wide so they can be used in the pager() method
794 self.kwargs = kwargs
794 self.kwargs = kwargs
795
795
796 # Save a reference to the collection
796 # Save a reference to the collection
797 self.original_collection = collection
797 self.original_collection = collection
798
798
799 self.collection = collection
799 self.collection = collection
800
800
801 # The self.page is the number of the current page.
801 # The self.page is the number of the current page.
802 # The first page has the number 1!
802 # The first page has the number 1!
803 try:
803 try:
804 self.page = int(page) # make it int() if we get it as a string
804 self.page = int(page) # make it int() if we get it as a string
805 except (ValueError, TypeError):
805 except (ValueError, TypeError):
806 self.page = 1
806 self.page = 1
807
807
808 self.items_per_page = items_per_page
808 self.items_per_page = items_per_page
809
809
810 # Unless the user tells us how many items the collections has
810 # Unless the user tells us how many items the collections has
811 # we calculate that ourselves.
811 # we calculate that ourselves.
812 if item_count is not None:
812 if item_count is not None:
813 self.item_count = item_count
813 self.item_count = item_count
814 else:
814 else:
815 self.item_count = len(self.collection)
815 self.item_count = len(self.collection)
816
816
817 # Compute the number of the first and last available page
817 # Compute the number of the first and last available page
818 if self.item_count > 0:
818 if self.item_count > 0:
819 self.first_page = 1
819 self.first_page = 1
820 self.page_count = int(math.ceil(float(self.item_count) /
820 self.page_count = int(math.ceil(float(self.item_count) /
821 self.items_per_page))
821 self.items_per_page))
822 self.last_page = self.first_page + self.page_count - 1
822 self.last_page = self.first_page + self.page_count - 1
823
823
824 # Make sure that the requested page number is the range of
824 # Make sure that the requested page number is the range of
825 # valid pages
825 # valid pages
826 if self.page > self.last_page:
826 if self.page > self.last_page:
827 self.page = self.last_page
827 self.page = self.last_page
828 elif self.page < self.first_page:
828 elif self.page < self.first_page:
829 self.page = self.first_page
829 self.page = self.first_page
830
830
831 # Note: the number of items on this page can be less than
831 # Note: the number of items on this page can be less than
832 # items_per_page if the last page is not full
832 # items_per_page if the last page is not full
833 self.first_item = max(0, (self.item_count) - (self.page *
833 self.first_item = max(0, (self.item_count) - (self.page *
834 items_per_page))
834 items_per_page))
835 self.last_item = ((self.item_count - 1) - items_per_page *
835 self.last_item = ((self.item_count - 1) - items_per_page *
836 (self.page - 1))
836 (self.page - 1))
837
837
838 self.items = list(self.collection[self.first_item:self.last_item + 1])
838 self.items = list(self.collection[self.first_item:self.last_item + 1])
839
839
840 # Links to previous and next page
840 # Links to previous and next page
841 if self.page > self.first_page:
841 if self.page > self.first_page:
842 self.previous_page = self.page - 1
842 self.previous_page = self.page - 1
843 else:
843 else:
844 self.previous_page = None
844 self.previous_page = None
845
845
846 if self.page < self.last_page:
846 if self.page < self.last_page:
847 self.next_page = self.page + 1
847 self.next_page = self.page + 1
848 else:
848 else:
849 self.next_page = None
849 self.next_page = None
850
850
851 # No items available
851 # No items available
852 else:
852 else:
853 self.first_page = None
853 self.first_page = None
854 self.page_count = 0
854 self.page_count = 0
855 self.last_page = None
855 self.last_page = None
856 self.first_item = None
856 self.first_item = None
857 self.last_item = None
857 self.last_item = None
858 self.previous_page = None
858 self.previous_page = None
859 self.next_page = None
859 self.next_page = None
860 self.items = []
860 self.items = []
861
861
862 # This is a subclass of the 'list' type. Initialise the list now.
862 # This is a subclass of the 'list' type. Initialise the list now.
863 list.__init__(self, reversed(self.items))
863 list.__init__(self, reversed(self.items))
864
864
865
865
866 def changed_tooltip(nodes):
866 def changed_tooltip(nodes):
867 """
867 """
868 Generates a html string for changed nodes in changeset page.
868 Generates a html string for changed nodes in changeset page.
869 It limits the output to 30 entries
869 It limits the output to 30 entries
870
870
871 :param nodes: LazyNodesGenerator
871 :param nodes: LazyNodesGenerator
872 """
872 """
873 if nodes:
873 if nodes:
874 pref = ': <br/> '
874 pref = ': <br/> '
875 suf = ''
875 suf = ''
876 if len(nodes) > 30:
876 if len(nodes) > 30:
877 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
877 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
878 return literal(pref + '<br/> '.join([safe_unicode(x.path)
878 return literal(pref + '<br/> '.join([safe_unicode(x.path)
879 for x in nodes[:30]]) + suf)
879 for x in nodes[:30]]) + suf)
880 else:
880 else:
881 return ': ' + _('No Files')
881 return ': ' + _('No Files')
882
882
883
883
884 def repo_link(groups_and_repos):
884 def repo_link(groups_and_repos, last_url=None):
885 """
885 """
886 Makes a breadcrumbs link to repo within a group
886 Makes a breadcrumbs link to repo within a group
887 joins &raquo; on each group to create a fancy link
887 joins &raquo; on each group to create a fancy link
888
888
889 ex::
889 ex::
890 group >> subgroup >> repo
890 group >> subgroup >> repo
891
891
892 :param groups_and_repos:
892 :param groups_and_repos:
893 :param last_url:
893 """
894 """
894 groups, repo_name = groups_and_repos
895 groups, repo_name = groups_and_repos
896 last_link = link_to(repo_name, last_url) if last_url else repo_name
895
897
896 if not groups:
898 if not groups:
899 if last_url:
900 return last_link
897 return repo_name
901 return repo_name
898 else:
902 else:
899 def make_link(group):
903 def make_link(group):
900 return link_to(group.name, url('repos_group_home',
904 return link_to(group.name,
901 group_name=group.group_name))
905 url('repos_group_home', group_name=group.group_name))
902 return literal(' &raquo; '.join(map(make_link, groups)) + \
906 return literal(' &raquo; '.join(map(make_link, groups) + [last_link]))
903 " &raquo; " + repo_name)
904
907
905
908
906 def fancy_file_stats(stats):
909 def fancy_file_stats(stats):
907 """
910 """
908 Displays a fancy two colored bar for number of added/deleted
911 Displays a fancy two colored bar for number of added/deleted
909 lines of code on file
912 lines of code on file
910
913
911 :param stats: two element list of added/deleted lines of code
914 :param stats: two element list of added/deleted lines of code
912 """
915 """
913 def cgen(l_type, a_v, d_v):
916 def cgen(l_type, a_v, d_v):
914 mapping = {'tr': 'top-right-rounded-corner-mid',
917 mapping = {'tr': 'top-right-rounded-corner-mid',
915 'tl': 'top-left-rounded-corner-mid',
918 'tl': 'top-left-rounded-corner-mid',
916 'br': 'bottom-right-rounded-corner-mid',
919 'br': 'bottom-right-rounded-corner-mid',
917 'bl': 'bottom-left-rounded-corner-mid'}
920 'bl': 'bottom-left-rounded-corner-mid'}
918 map_getter = lambda x: mapping[x]
921 map_getter = lambda x: mapping[x]
919
922
920 if l_type == 'a' and d_v:
923 if l_type == 'a' and d_v:
921 #case when added and deleted are present
924 #case when added and deleted are present
922 return ' '.join(map(map_getter, ['tl', 'bl']))
925 return ' '.join(map(map_getter, ['tl', 'bl']))
923
926
924 if l_type == 'a' and not d_v:
927 if l_type == 'a' and not d_v:
925 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
928 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
926
929
927 if l_type == 'd' and a_v:
930 if l_type == 'd' and a_v:
928 return ' '.join(map(map_getter, ['tr', 'br']))
931 return ' '.join(map(map_getter, ['tr', 'br']))
929
932
930 if l_type == 'd' and not a_v:
933 if l_type == 'd' and not a_v:
931 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
934 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
932
935
933 a, d = stats[0], stats[1]
936 a, d = stats[0], stats[1]
934 width = 100
937 width = 100
935
938
936 if a == 'b':
939 if a == 'b':
937 #binary mode
940 #binary mode
938 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
941 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
939 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
942 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
940 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
943 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
941
944
942 t = stats[0] + stats[1]
945 t = stats[0] + stats[1]
943 unit = float(width) / (t or 1)
946 unit = float(width) / (t or 1)
944
947
945 # needs > 9% of width to be visible or 0 to be hidden
948 # needs > 9% of width to be visible or 0 to be hidden
946 a_p = max(9, unit * a) if a > 0 else 0
949 a_p = max(9, unit * a) if a > 0 else 0
947 d_p = max(9, unit * d) if d > 0 else 0
950 d_p = max(9, unit * d) if d > 0 else 0
948 p_sum = a_p + d_p
951 p_sum = a_p + d_p
949
952
950 if p_sum > width:
953 if p_sum > width:
951 #adjust the percentage to be == 100% since we adjusted to 9
954 #adjust the percentage to be == 100% since we adjusted to 9
952 if a_p > d_p:
955 if a_p > d_p:
953 a_p = a_p - (p_sum - width)
956 a_p = a_p - (p_sum - width)
954 else:
957 else:
955 d_p = d_p - (p_sum - width)
958 d_p = d_p - (p_sum - width)
956
959
957 a_v = a if a > 0 else ''
960 a_v = a if a > 0 else ''
958 d_v = d if d > 0 else ''
961 d_v = d if d > 0 else ''
959
962
960 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
963 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
961 cgen('a', a_v, d_v), a_p, a_v
964 cgen('a', a_v, d_v), a_p, a_v
962 )
965 )
963 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
966 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
964 cgen('d', a_v, d_v), d_p, d_v
967 cgen('d', a_v, d_v), d_p, d_v
965 )
968 )
966 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
969 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
967
970
968
971
969 def urlify_text(text_):
972 def urlify_text(text_):
970
973
971 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
974 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
972 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
975 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
973
976
974 def url_func(match_obj):
977 def url_func(match_obj):
975 url_full = match_obj.groups()[0]
978 url_full = match_obj.groups()[0]
976 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
979 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
977
980
978 return literal(url_pat.sub(url_func, text_))
981 return literal(url_pat.sub(url_func, text_))
979
982
980
983
981 def urlify_changesets(text_, repository):
984 def urlify_changesets(text_, repository):
982 """
985 """
983 Extract revision ids from changeset and make link from them
986 Extract revision ids from changeset and make link from them
984
987
985 :param text_:
988 :param text_:
986 :param repository:
989 :param repository:
987 """
990 """
988
991
989 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
992 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
990
993
991 def url_func(match_obj):
994 def url_func(match_obj):
992 rev = match_obj.groups()[0]
995 rev = match_obj.groups()[0]
993 pref = ''
996 pref = ''
994 if match_obj.group().startswith(' '):
997 if match_obj.group().startswith(' '):
995 pref = ' '
998 pref = ' '
996 tmpl = (
999 tmpl = (
997 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1000 '%(pref)s<a class="%(cls)s" href="%(url)s">'
998 '%(rev)s'
1001 '%(rev)s'
999 '</a>'
1002 '</a>'
1000 )
1003 )
1001 return tmpl % {
1004 return tmpl % {
1002 'pref': pref,
1005 'pref': pref,
1003 'cls': 'revision-link',
1006 'cls': 'revision-link',
1004 'url': url('changeset_home', repo_name=repository, revision=rev),
1007 'url': url('changeset_home', repo_name=repository, revision=rev),
1005 'rev': rev,
1008 'rev': rev,
1006 }
1009 }
1007
1010
1008 newtext = URL_PAT.sub(url_func, text_)
1011 newtext = URL_PAT.sub(url_func, text_)
1009
1012
1010 return newtext
1013 return newtext
1011
1014
1012
1015
1013 def urlify_commit(text_, repository=None, link_=None):
1016 def urlify_commit(text_, repository=None, link_=None):
1014 """
1017 """
1015 Parses given text message and makes proper links.
1018 Parses given text message and makes proper links.
1016 issues are linked to given issue-server, and rest is a changeset link
1019 issues are linked to given issue-server, and rest is a changeset link
1017 if link_ is given, in other case it's a plain text
1020 if link_ is given, in other case it's a plain text
1018
1021
1019 :param text_:
1022 :param text_:
1020 :param repository:
1023 :param repository:
1021 :param link_: changeset link
1024 :param link_: changeset link
1022 """
1025 """
1023 import traceback
1026 import traceback
1024
1027
1025 def escaper(string):
1028 def escaper(string):
1026 return string.replace('<', '&lt;').replace('>', '&gt;')
1029 return string.replace('<', '&lt;').replace('>', '&gt;')
1027
1030
1028 def linkify_others(t, l):
1031 def linkify_others(t, l):
1029 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1032 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1030 links = []
1033 links = []
1031 for e in urls.split(t):
1034 for e in urls.split(t):
1032 if not urls.match(e):
1035 if not urls.match(e):
1033 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1036 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1034 else:
1037 else:
1035 links.append(e)
1038 links.append(e)
1036
1039
1037 return ''.join(links)
1040 return ''.join(links)
1038
1041
1039 # urlify changesets - extrac revisions and make link out of them
1042 # urlify changesets - extrac revisions and make link out of them
1040 newtext = urlify_changesets(escaper(text_), repository)
1043 newtext = urlify_changesets(escaper(text_), repository)
1041
1044
1042 try:
1045 try:
1043 conf = config['app_conf']
1046 conf = config['app_conf']
1044
1047
1045 # allow multiple issue servers to be used
1048 # allow multiple issue servers to be used
1046 valid_indices = [
1049 valid_indices = [
1047 x.group(1)
1050 x.group(1)
1048 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1051 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1049 if x and 'issue_server_link%s' % x.group(1) in conf
1052 if x and 'issue_server_link%s' % x.group(1) in conf
1050 and 'issue_prefix%s' % x.group(1) in conf
1053 and 'issue_prefix%s' % x.group(1) in conf
1051 ]
1054 ]
1052
1055
1053 log.debug('found issue server suffixes `%s` during valuation of: %s'
1056 log.debug('found issue server suffixes `%s` during valuation of: %s'
1054 % (','.join(valid_indices), newtext))
1057 % (','.join(valid_indices), newtext))
1055
1058
1056 for pattern_index in valid_indices:
1059 for pattern_index in valid_indices:
1057 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1060 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1058 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1061 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1059 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1062 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1060
1063
1061 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1064 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1062 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1065 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1063 ISSUE_PREFIX))
1066 ISSUE_PREFIX))
1064
1067
1065 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1068 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1066
1069
1067 def url_func(match_obj):
1070 def url_func(match_obj):
1068 pref = ''
1071 pref = ''
1069 if match_obj.group().startswith(' '):
1072 if match_obj.group().startswith(' '):
1070 pref = ' '
1073 pref = ' '
1071
1074
1072 issue_id = ''.join(match_obj.groups())
1075 issue_id = ''.join(match_obj.groups())
1073 tmpl = (
1076 tmpl = (
1074 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1077 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1075 '%(issue-prefix)s%(id-repr)s'
1078 '%(issue-prefix)s%(id-repr)s'
1076 '</a>'
1079 '</a>'
1077 )
1080 )
1078 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1081 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1079 if repository:
1082 if repository:
1080 url = url.replace('{repo}', repository)
1083 url = url.replace('{repo}', repository)
1081 repo_name = repository.split(URL_SEP)[-1]
1084 repo_name = repository.split(URL_SEP)[-1]
1082 url = url.replace('{repo_name}', repo_name)
1085 url = url.replace('{repo_name}', repo_name)
1083
1086
1084 return tmpl % {
1087 return tmpl % {
1085 'pref': pref,
1088 'pref': pref,
1086 'cls': 'issue-tracker-link',
1089 'cls': 'issue-tracker-link',
1087 'url': url,
1090 'url': url,
1088 'id-repr': issue_id,
1091 'id-repr': issue_id,
1089 'issue-prefix': ISSUE_PREFIX,
1092 'issue-prefix': ISSUE_PREFIX,
1090 'serv': ISSUE_SERVER_LNK,
1093 'serv': ISSUE_SERVER_LNK,
1091 }
1094 }
1092 newtext = URL_PAT.sub(url_func, newtext)
1095 newtext = URL_PAT.sub(url_func, newtext)
1093 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1096 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1094
1097
1095 # if we actually did something above
1098 # if we actually did something above
1096 if link_:
1099 if link_:
1097 # wrap not links into final link => link_
1100 # wrap not links into final link => link_
1098 newtext = linkify_others(newtext, link_)
1101 newtext = linkify_others(newtext, link_)
1099 except:
1102 except:
1100 log.error(traceback.format_exc())
1103 log.error(traceback.format_exc())
1101 pass
1104 pass
1102
1105
1103 return literal(newtext)
1106 return literal(newtext)
1104
1107
1105
1108
1106 def rst(source):
1109 def rst(source):
1107 return literal('<div class="rst-block">%s</div>' %
1110 return literal('<div class="rst-block">%s</div>' %
1108 MarkupRenderer.rst(source))
1111 MarkupRenderer.rst(source))
1109
1112
1110
1113
1111 def rst_w_mentions(source):
1114 def rst_w_mentions(source):
1112 """
1115 """
1113 Wrapped rst renderer with @mention highlighting
1116 Wrapped rst renderer with @mention highlighting
1114
1117
1115 :param source:
1118 :param source:
1116 """
1119 """
1117 return literal('<div class="rst-block">%s</div>' %
1120 return literal('<div class="rst-block">%s</div>' %
1118 MarkupRenderer.rst_with_mentions(source))
1121 MarkupRenderer.rst_with_mentions(source))
1119
1122
1120
1123
1121 def changeset_status(repo, revision):
1124 def changeset_status(repo, revision):
1122 return ChangesetStatusModel().get_status(repo, revision)
1125 return ChangesetStatusModel().get_status(repo, revision)
1123
1126
1124
1127
1125 def changeset_status_lbl(changeset_status):
1128 def changeset_status_lbl(changeset_status):
1126 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1129 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1127
1130
1128
1131
1129 def get_permission_name(key):
1132 def get_permission_name(key):
1130 return dict(Permission.PERMS).get(key)
1133 return dict(Permission.PERMS).get(key)
General Comments 0
You need to be logged in to leave comments. Login now