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