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