##// END OF EJS Templates
fixed issue with h.person() function returned prematurly giving only email info from changeset metadata. Thanks to Stevan Radaković
marcink -
r3740:ea2c948a beta
parent child Browse files
Show More
@@ -1,1207 +1,1206 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.get('show_sha_length', 12))
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
383 show_rev = str2bool(CONFIG.get('show_revision_number', 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
452
451
453 # Maybe it's a username?
452 # Maybe it's a username?
454 _author = author_name(author)
453 _author = author_name(author)
455 user = User.get_by_username(_author, case_insensitive=True,
454 user = User.get_by_username(_author, case_insensitive=True,
456 cache=True)
455 cache=True)
457 if user is not None:
456 if user is not None:
458 return person_getter(user)
457 return person_getter(user)
459
458
460 # Still nothing? Just pass back the author name then
459 # Still nothing? Just pass back the author name then
461 return _author
460 return _author
462
461
463
462
464 def person_by_id(id_, show_attr="username_and_name"):
463 def person_by_id(id_, show_attr="username_and_name"):
465 # attr to return from fetched user
464 # attr to return from fetched user
466 person_getter = lambda usr: getattr(usr, show_attr)
465 person_getter = lambda usr: getattr(usr, show_attr)
467
466
468 #maybe it's an ID ?
467 #maybe it's an ID ?
469 if str(id_).isdigit() or isinstance(id_, int):
468 if str(id_).isdigit() or isinstance(id_, int):
470 id_ = int(id_)
469 id_ = int(id_)
471 user = User.get(id_)
470 user = User.get(id_)
472 if user is not None:
471 if user is not None:
473 return person_getter(user)
472 return person_getter(user)
474 return id_
473 return id_
475
474
476
475
477 def desc_stylize(value):
476 def desc_stylize(value):
478 """
477 """
479 converts tags from value into html equivalent
478 converts tags from value into html equivalent
480
479
481 :param value:
480 :param value:
482 """
481 """
483 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
485 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 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)
485 '<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\-\/]*)\]',
486 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)
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
489 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
490 '<div class="metatag" tag="lang">\\2</div>', value)
489 '<div class="metatag" tag="lang">\\2</div>', value)
491 value = re.sub(r'\[([a-z]+)\]',
490 value = re.sub(r'\[([a-z]+)\]',
492 '<div class="metatag" tag="\\1">\\1</div>', value)
491 '<div class="metatag" tag="\\1">\\1</div>', value)
493
492
494 return value
493 return value
495
494
496
495
497 def boolicon(value):
496 def boolicon(value):
498 """Returns boolean value of a value, represented as small html image of true/false
497 """Returns boolean value of a value, represented as small html image of true/false
499 icons
498 icons
500
499
501 :param value: value
500 :param value: value
502 """
501 """
503
502
504 if value:
503 if value:
505 return HTML.tag('img', src=url("/images/icons/accept.png"),
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
506 alt=_('True'))
505 alt=_('True'))
507 else:
506 else:
508 return HTML.tag('img', src=url("/images/icons/cancel.png"),
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
509 alt=_('False'))
508 alt=_('False'))
510
509
511
510
512 def action_parser(user_log, feed=False, parse_cs=False):
511 def action_parser(user_log, feed=False, parse_cs=False):
513 """
512 """
514 This helper will action_map the specified string action into translated
513 This helper will action_map the specified string action into translated
515 fancy names with icons and links
514 fancy names with icons and links
516
515
517 :param user_log: user log instance
516 :param user_log: user log instance
518 :param feed: use output for feeds (no html and fancy icons)
517 :param feed: use output for feeds (no html and fancy icons)
519 :param parse_cs: parse Changesets into VCS instances
518 :param parse_cs: parse Changesets into VCS instances
520 """
519 """
521
520
522 action = user_log.action
521 action = user_log.action
523 action_params = ' '
522 action_params = ' '
524
523
525 x = action.split(':')
524 x = action.split(':')
526
525
527 if len(x) > 1:
526 if len(x) > 1:
528 action, action_params = x
527 action, action_params = x
529
528
530 def get_cs_links():
529 def get_cs_links():
531 revs_limit = 3 # display this amount always
530 revs_limit = 3 # display this amount always
532 revs_top_limit = 50 # show upto this amount of changesets hidden
531 revs_top_limit = 50 # show upto this amount of changesets hidden
533 revs_ids = action_params.split(',')
532 revs_ids = action_params.split(',')
534 deleted = user_log.repository is None
533 deleted = user_log.repository is None
535 if deleted:
534 if deleted:
536 return ','.join(revs_ids)
535 return ','.join(revs_ids)
537
536
538 repo_name = user_log.repository.repo_name
537 repo_name = user_log.repository.repo_name
539
538
540 def lnk(rev, repo_name):
539 def lnk(rev, repo_name):
541 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
542 lazy_cs = True
541 lazy_cs = True
543 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
544 lazy_cs = False
543 lazy_cs = False
545 lbl = '?'
544 lbl = '?'
546 if rev.op == 'delete_branch':
545 if rev.op == 'delete_branch':
547 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
548 title = ''
547 title = ''
549 elif rev.op == 'tag':
548 elif rev.op == 'tag':
550 lbl = '%s' % _('Created tag: %s') % rev.ref_name
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
551 title = ''
550 title = ''
552 _url = '#'
551 _url = '#'
553
552
554 else:
553 else:
555 lbl = '%s' % (rev.short_id[:8])
554 lbl = '%s' % (rev.short_id[:8])
556 _url = url('changeset_home', repo_name=repo_name,
555 _url = url('changeset_home', repo_name=repo_name,
557 revision=rev.raw_id)
556 revision=rev.raw_id)
558 title = tooltip(rev.message)
557 title = tooltip(rev.message)
559 else:
558 else:
560 ## changeset cannot be found/striped/removed etc.
559 ## changeset cannot be found/striped/removed etc.
561 lbl = ('%s' % rev)[:12]
560 lbl = ('%s' % rev)[:12]
562 _url = '#'
561 _url = '#'
563 title = _('Changeset not found')
562 title = _('Changeset not found')
564 if parse_cs:
563 if parse_cs:
565 return link_to(lbl, _url, title=title, class_='tooltip')
564 return link_to(lbl, _url, title=title, class_='tooltip')
566 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
567 class_='lazy-cs' if lazy_cs else '')
566 class_='lazy-cs' if lazy_cs else '')
568
567
569 def _get_op(rev_txt):
568 def _get_op(rev_txt):
570 _op = None
569 _op = None
571 _name = rev_txt
570 _name = rev_txt
572 if len(rev_txt.split('=>')) == 2:
571 if len(rev_txt.split('=>')) == 2:
573 _op, _name = rev_txt.split('=>')
572 _op, _name = rev_txt.split('=>')
574 return _op, _name
573 return _op, _name
575
574
576 revs = []
575 revs = []
577 if len(filter(lambda v: v != '', revs_ids)) > 0:
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
578 repo = None
577 repo = None
579 for rev in revs_ids[:revs_top_limit]:
578 for rev in revs_ids[:revs_top_limit]:
580 _op, _name = _get_op(rev)
579 _op, _name = _get_op(rev)
581
580
582 # we want parsed changesets, or new log store format is bad
581 # we want parsed changesets, or new log store format is bad
583 if parse_cs:
582 if parse_cs:
584 try:
583 try:
585 if repo is None:
584 if repo is None:
586 repo = user_log.repository.scm_instance
585 repo = user_log.repository.scm_instance
587 _rev = repo.get_changeset(rev)
586 _rev = repo.get_changeset(rev)
588 revs.append(_rev)
587 revs.append(_rev)
589 except ChangesetDoesNotExistError:
588 except ChangesetDoesNotExistError:
590 log.error('cannot find revision %s in this repo' % rev)
589 log.error('cannot find revision %s in this repo' % rev)
591 revs.append(rev)
590 revs.append(rev)
592 continue
591 continue
593 else:
592 else:
594 _rev = AttributeDict({
593 _rev = AttributeDict({
595 'short_id': rev[:12],
594 'short_id': rev[:12],
596 'raw_id': rev,
595 'raw_id': rev,
597 'message': '',
596 'message': '',
598 'op': _op,
597 'op': _op,
599 'ref_name': _name
598 'ref_name': _name
600 })
599 })
601 revs.append(_rev)
600 revs.append(_rev)
602 cs_links = []
601 cs_links = []
603 cs_links.append(" " + ', '.join(
602 cs_links.append(" " + ', '.join(
604 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
603 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
605 )
604 )
606 )
605 )
607 _op1, _name1 = _get_op(revs_ids[0])
606 _op1, _name1 = _get_op(revs_ids[0])
608 _op2, _name2 = _get_op(revs_ids[-1])
607 _op2, _name2 = _get_op(revs_ids[-1])
609
608
610 _rev = '%s...%s' % (_name1, _name2)
609 _rev = '%s...%s' % (_name1, _name2)
611
610
612 compare_view = (
611 compare_view = (
613 ' <div class="compare_view tooltip" title="%s">'
612 ' <div class="compare_view tooltip" title="%s">'
614 '<a href="%s">%s</a> </div>' % (
613 '<a href="%s">%s</a> </div>' % (
615 _('Show all combined changesets %s->%s') % (
614 _('Show all combined changesets %s->%s') % (
616 revs_ids[0][:12], revs_ids[-1][:12]
615 revs_ids[0][:12], revs_ids[-1][:12]
617 ),
616 ),
618 url('changeset_home', repo_name=repo_name,
617 url('changeset_home', repo_name=repo_name,
619 revision=_rev
618 revision=_rev
620 ),
619 ),
621 _('compare view')
620 _('compare view')
622 )
621 )
623 )
622 )
624
623
625 # if we have exactly one more than normally displayed
624 # if we have exactly one more than normally displayed
626 # just display it, takes less space than displaying
625 # just display it, takes less space than displaying
627 # "and 1 more revisions"
626 # "and 1 more revisions"
628 if len(revs_ids) == revs_limit + 1:
627 if len(revs_ids) == revs_limit + 1:
629 rev = revs[revs_limit]
628 rev = revs[revs_limit]
630 cs_links.append(", " + lnk(rev, repo_name))
629 cs_links.append(", " + lnk(rev, repo_name))
631
630
632 # hidden-by-default ones
631 # hidden-by-default ones
633 if len(revs_ids) > revs_limit + 1:
632 if len(revs_ids) > revs_limit + 1:
634 uniq_id = revs_ids[0]
633 uniq_id = revs_ids[0]
635 html_tmpl = (
634 html_tmpl = (
636 '<span> %s <a class="show_more" id="_%s" '
635 '<span> %s <a class="show_more" id="_%s" '
637 'href="#more">%s</a> %s</span>'
636 'href="#more">%s</a> %s</span>'
638 )
637 )
639 if not feed:
638 if not feed:
640 cs_links.append(html_tmpl % (
639 cs_links.append(html_tmpl % (
641 _('and'),
640 _('and'),
642 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
641 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
643 _('revisions')
642 _('revisions')
644 )
643 )
645 )
644 )
646
645
647 if not feed:
646 if not feed:
648 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
647 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
649 else:
648 else:
650 html_tmpl = '<span id="%s"> %s </span>'
649 html_tmpl = '<span id="%s"> %s </span>'
651
650
652 morelinks = ', '.join(
651 morelinks = ', '.join(
653 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
652 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
654 )
653 )
655
654
656 if len(revs_ids) > revs_top_limit:
655 if len(revs_ids) > revs_top_limit:
657 morelinks += ', ...'
656 morelinks += ', ...'
658
657
659 cs_links.append(html_tmpl % (uniq_id, morelinks))
658 cs_links.append(html_tmpl % (uniq_id, morelinks))
660 if len(revs) > 1:
659 if len(revs) > 1:
661 cs_links.append(compare_view)
660 cs_links.append(compare_view)
662 return ''.join(cs_links)
661 return ''.join(cs_links)
663
662
664 def get_fork_name():
663 def get_fork_name():
665 repo_name = action_params
664 repo_name = action_params
666 _url = url('summary_home', repo_name=repo_name)
665 _url = url('summary_home', repo_name=repo_name)
667 return _('fork name %s') % link_to(action_params, _url)
666 return _('fork name %s') % link_to(action_params, _url)
668
667
669 def get_user_name():
668 def get_user_name():
670 user_name = action_params
669 user_name = action_params
671 return user_name
670 return user_name
672
671
673 def get_users_group():
672 def get_users_group():
674 group_name = action_params
673 group_name = action_params
675 return group_name
674 return group_name
676
675
677 def get_pull_request():
676 def get_pull_request():
678 pull_request_id = action_params
677 pull_request_id = action_params
679 deleted = user_log.repository is None
678 deleted = user_log.repository is None
680 if deleted:
679 if deleted:
681 repo_name = user_log.repository_name
680 repo_name = user_log.repository_name
682 else:
681 else:
683 repo_name = user_log.repository.repo_name
682 repo_name = user_log.repository.repo_name
684 return link_to(_('Pull request #%s') % pull_request_id,
683 return link_to(_('Pull request #%s') % pull_request_id,
685 url('pullrequest_show', repo_name=repo_name,
684 url('pullrequest_show', repo_name=repo_name,
686 pull_request_id=pull_request_id))
685 pull_request_id=pull_request_id))
687
686
688 # action : translated str, callback(extractor), icon
687 # action : translated str, callback(extractor), icon
689 action_map = {
688 action_map = {
690 'user_deleted_repo': (_('[deleted] repository'),
689 'user_deleted_repo': (_('[deleted] repository'),
691 None, 'database_delete.png'),
690 None, 'database_delete.png'),
692 'user_created_repo': (_('[created] repository'),
691 'user_created_repo': (_('[created] repository'),
693 None, 'database_add.png'),
692 None, 'database_add.png'),
694 'user_created_fork': (_('[created] repository as fork'),
693 'user_created_fork': (_('[created] repository as fork'),
695 None, 'arrow_divide.png'),
694 None, 'arrow_divide.png'),
696 'user_forked_repo': (_('[forked] repository'),
695 'user_forked_repo': (_('[forked] repository'),
697 get_fork_name, 'arrow_divide.png'),
696 get_fork_name, 'arrow_divide.png'),
698 'user_updated_repo': (_('[updated] repository'),
697 'user_updated_repo': (_('[updated] repository'),
699 None, 'database_edit.png'),
698 None, 'database_edit.png'),
700 'admin_deleted_repo': (_('[delete] repository'),
699 'admin_deleted_repo': (_('[delete] repository'),
701 None, 'database_delete.png'),
700 None, 'database_delete.png'),
702 'admin_created_repo': (_('[created] repository'),
701 'admin_created_repo': (_('[created] repository'),
703 None, 'database_add.png'),
702 None, 'database_add.png'),
704 'admin_forked_repo': (_('[forked] repository'),
703 'admin_forked_repo': (_('[forked] repository'),
705 None, 'arrow_divide.png'),
704 None, 'arrow_divide.png'),
706 'admin_updated_repo': (_('[updated] repository'),
705 'admin_updated_repo': (_('[updated] repository'),
707 None, 'database_edit.png'),
706 None, 'database_edit.png'),
708 'admin_created_user': (_('[created] user'),
707 'admin_created_user': (_('[created] user'),
709 get_user_name, 'user_add.png'),
708 get_user_name, 'user_add.png'),
710 'admin_updated_user': (_('[updated] user'),
709 'admin_updated_user': (_('[updated] user'),
711 get_user_name, 'user_edit.png'),
710 get_user_name, 'user_edit.png'),
712 'admin_created_users_group': (_('[created] user group'),
711 'admin_created_users_group': (_('[created] user group'),
713 get_users_group, 'group_add.png'),
712 get_users_group, 'group_add.png'),
714 'admin_updated_users_group': (_('[updated] user group'),
713 'admin_updated_users_group': (_('[updated] user group'),
715 get_users_group, 'group_edit.png'),
714 get_users_group, 'group_edit.png'),
716 'user_commented_revision': (_('[commented] on revision in repository'),
715 'user_commented_revision': (_('[commented] on revision in repository'),
717 get_cs_links, 'comment_add.png'),
716 get_cs_links, 'comment_add.png'),
718 'user_commented_pull_request': (_('[commented] on pull request for'),
717 'user_commented_pull_request': (_('[commented] on pull request for'),
719 get_pull_request, 'comment_add.png'),
718 get_pull_request, 'comment_add.png'),
720 'user_closed_pull_request': (_('[closed] pull request for'),
719 'user_closed_pull_request': (_('[closed] pull request for'),
721 get_pull_request, 'tick.png'),
720 get_pull_request, 'tick.png'),
722 'push': (_('[pushed] into'),
721 'push': (_('[pushed] into'),
723 get_cs_links, 'script_add.png'),
722 get_cs_links, 'script_add.png'),
724 'push_local': (_('[committed via RhodeCode] into repository'),
723 'push_local': (_('[committed via RhodeCode] into repository'),
725 get_cs_links, 'script_edit.png'),
724 get_cs_links, 'script_edit.png'),
726 'push_remote': (_('[pulled from remote] into repository'),
725 'push_remote': (_('[pulled from remote] into repository'),
727 get_cs_links, 'connect.png'),
726 get_cs_links, 'connect.png'),
728 'pull': (_('[pulled] from'),
727 'pull': (_('[pulled] from'),
729 None, 'down_16.png'),
728 None, 'down_16.png'),
730 'started_following_repo': (_('[started following] repository'),
729 'started_following_repo': (_('[started following] repository'),
731 None, 'heart_add.png'),
730 None, 'heart_add.png'),
732 'stopped_following_repo': (_('[stopped following] repository'),
731 'stopped_following_repo': (_('[stopped following] repository'),
733 None, 'heart_delete.png'),
732 None, 'heart_delete.png'),
734 }
733 }
735
734
736 action_str = action_map.get(action, action)
735 action_str = action_map.get(action, action)
737 if feed:
736 if feed:
738 action = action_str[0].replace('[', '').replace(']', '')
737 action = action_str[0].replace('[', '').replace(']', '')
739 else:
738 else:
740 action = action_str[0]\
739 action = action_str[0]\
741 .replace('[', '<span class="journal_highlight">')\
740 .replace('[', '<span class="journal_highlight">')\
742 .replace(']', '</span>')
741 .replace(']', '</span>')
743
742
744 action_params_func = lambda: ""
743 action_params_func = lambda: ""
745
744
746 if callable(action_str[1]):
745 if callable(action_str[1]):
747 action_params_func = action_str[1]
746 action_params_func = action_str[1]
748
747
749 def action_parser_icon():
748 def action_parser_icon():
750 action = user_log.action
749 action = user_log.action
751 action_params = None
750 action_params = None
752 x = action.split(':')
751 x = action.split(':')
753
752
754 if len(x) > 1:
753 if len(x) > 1:
755 action, action_params = x
754 action, action_params = x
756
755
757 tmpl = """<img src="%s%s" alt="%s"/>"""
756 tmpl = """<img src="%s%s" alt="%s"/>"""
758 ico = action_map.get(action, ['', '', ''])[2]
757 ico = action_map.get(action, ['', '', ''])[2]
759 return literal(tmpl % ((url('/images/icons/')), ico, action))
758 return literal(tmpl % ((url('/images/icons/')), ico, action))
760
759
761 # returned callbacks we need to call to get
760 # returned callbacks we need to call to get
762 return [lambda: literal(action), action_params_func, action_parser_icon]
761 return [lambda: literal(action), action_params_func, action_parser_icon]
763
762
764
763
765
764
766 #==============================================================================
765 #==============================================================================
767 # PERMS
766 # PERMS
768 #==============================================================================
767 #==============================================================================
769 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
768 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
770 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
769 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
771 HasReposGroupPermissionAny
770 HasReposGroupPermissionAny
772
771
773
772
774 #==============================================================================
773 #==============================================================================
775 # GRAVATAR URL
774 # GRAVATAR URL
776 #==============================================================================
775 #==============================================================================
777
776
778 def gravatar_url(email_address, size=30):
777 def gravatar_url(email_address, size=30):
779 from pylons import url # doh, we need to re-import url to mock it later
778 from pylons import url # doh, we need to re-import url to mock it later
780 _def = 'anonymous@rhodecode.org'
779 _def = 'anonymous@rhodecode.org'
781 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
780 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
782 email_address = email_address or _def
781 email_address = email_address or _def
783 if (not use_gravatar or not email_address or email_address == _def):
782 if (not use_gravatar or not email_address or email_address == _def):
784 f = lambda a, l: min(l, key=lambda x: abs(x - a))
783 f = lambda a, l: min(l, key=lambda x: abs(x - a))
785 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
784 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
786
785
787 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
786 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
788 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
787 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
789 parsed_url = urlparse.urlparse(url.current(qualified=True))
788 parsed_url = urlparse.urlparse(url.current(qualified=True))
790 tmpl = tmpl.replace('{email}', email_address)\
789 tmpl = tmpl.replace('{email}', email_address)\
791 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
790 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
792 .replace('{netloc}', parsed_url.netloc)\
791 .replace('{netloc}', parsed_url.netloc)\
793 .replace('{scheme}', parsed_url.scheme)\
792 .replace('{scheme}', parsed_url.scheme)\
794 .replace('{size}', str(size))
793 .replace('{size}', str(size))
795 return tmpl
794 return tmpl
796
795
797 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
796 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
798 default = 'identicon'
797 default = 'identicon'
799 baseurl_nossl = "http://www.gravatar.com/avatar/"
798 baseurl_nossl = "http://www.gravatar.com/avatar/"
800 baseurl_ssl = "https://secure.gravatar.com/avatar/"
799 baseurl_ssl = "https://secure.gravatar.com/avatar/"
801 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
800 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
802
801
803 if isinstance(email_address, unicode):
802 if isinstance(email_address, unicode):
804 #hashlib crashes on unicode items
803 #hashlib crashes on unicode items
805 email_address = safe_str(email_address)
804 email_address = safe_str(email_address)
806 # construct the url
805 # construct the url
807 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
806 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
808 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
807 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
809
808
810 return gravatar_url
809 return gravatar_url
811
810
812
811
813 #==============================================================================
812 #==============================================================================
814 # REPO PAGER, PAGER FOR REPOSITORY
813 # REPO PAGER, PAGER FOR REPOSITORY
815 #==============================================================================
814 #==============================================================================
816 class RepoPage(Page):
815 class RepoPage(Page):
817
816
818 def __init__(self, collection, page=1, items_per_page=20,
817 def __init__(self, collection, page=1, items_per_page=20,
819 item_count=None, url=None, **kwargs):
818 item_count=None, url=None, **kwargs):
820
819
821 """Create a "RepoPage" instance. special pager for paging
820 """Create a "RepoPage" instance. special pager for paging
822 repository
821 repository
823 """
822 """
824 self._url_generator = url
823 self._url_generator = url
825
824
826 # Safe the kwargs class-wide so they can be used in the pager() method
825 # Safe the kwargs class-wide so they can be used in the pager() method
827 self.kwargs = kwargs
826 self.kwargs = kwargs
828
827
829 # Save a reference to the collection
828 # Save a reference to the collection
830 self.original_collection = collection
829 self.original_collection = collection
831
830
832 self.collection = collection
831 self.collection = collection
833
832
834 # The self.page is the number of the current page.
833 # The self.page is the number of the current page.
835 # The first page has the number 1!
834 # The first page has the number 1!
836 try:
835 try:
837 self.page = int(page) # make it int() if we get it as a string
836 self.page = int(page) # make it int() if we get it as a string
838 except (ValueError, TypeError):
837 except (ValueError, TypeError):
839 self.page = 1
838 self.page = 1
840
839
841 self.items_per_page = items_per_page
840 self.items_per_page = items_per_page
842
841
843 # Unless the user tells us how many items the collections has
842 # Unless the user tells us how many items the collections has
844 # we calculate that ourselves.
843 # we calculate that ourselves.
845 if item_count is not None:
844 if item_count is not None:
846 self.item_count = item_count
845 self.item_count = item_count
847 else:
846 else:
848 self.item_count = len(self.collection)
847 self.item_count = len(self.collection)
849
848
850 # Compute the number of the first and last available page
849 # Compute the number of the first and last available page
851 if self.item_count > 0:
850 if self.item_count > 0:
852 self.first_page = 1
851 self.first_page = 1
853 self.page_count = int(math.ceil(float(self.item_count) /
852 self.page_count = int(math.ceil(float(self.item_count) /
854 self.items_per_page))
853 self.items_per_page))
855 self.last_page = self.first_page + self.page_count - 1
854 self.last_page = self.first_page + self.page_count - 1
856
855
857 # Make sure that the requested page number is the range of
856 # Make sure that the requested page number is the range of
858 # valid pages
857 # valid pages
859 if self.page > self.last_page:
858 if self.page > self.last_page:
860 self.page = self.last_page
859 self.page = self.last_page
861 elif self.page < self.first_page:
860 elif self.page < self.first_page:
862 self.page = self.first_page
861 self.page = self.first_page
863
862
864 # Note: the number of items on this page can be less than
863 # Note: the number of items on this page can be less than
865 # items_per_page if the last page is not full
864 # items_per_page if the last page is not full
866 self.first_item = max(0, (self.item_count) - (self.page *
865 self.first_item = max(0, (self.item_count) - (self.page *
867 items_per_page))
866 items_per_page))
868 self.last_item = ((self.item_count - 1) - items_per_page *
867 self.last_item = ((self.item_count - 1) - items_per_page *
869 (self.page - 1))
868 (self.page - 1))
870
869
871 self.items = list(self.collection[self.first_item:self.last_item + 1])
870 self.items = list(self.collection[self.first_item:self.last_item + 1])
872
871
873 # Links to previous and next page
872 # Links to previous and next page
874 if self.page > self.first_page:
873 if self.page > self.first_page:
875 self.previous_page = self.page - 1
874 self.previous_page = self.page - 1
876 else:
875 else:
877 self.previous_page = None
876 self.previous_page = None
878
877
879 if self.page < self.last_page:
878 if self.page < self.last_page:
880 self.next_page = self.page + 1
879 self.next_page = self.page + 1
881 else:
880 else:
882 self.next_page = None
881 self.next_page = None
883
882
884 # No items available
883 # No items available
885 else:
884 else:
886 self.first_page = None
885 self.first_page = None
887 self.page_count = 0
886 self.page_count = 0
888 self.last_page = None
887 self.last_page = None
889 self.first_item = None
888 self.first_item = None
890 self.last_item = None
889 self.last_item = None
891 self.previous_page = None
890 self.previous_page = None
892 self.next_page = None
891 self.next_page = None
893 self.items = []
892 self.items = []
894
893
895 # This is a subclass of the 'list' type. Initialise the list now.
894 # This is a subclass of the 'list' type. Initialise the list now.
896 list.__init__(self, reversed(self.items))
895 list.__init__(self, reversed(self.items))
897
896
898
897
899 def changed_tooltip(nodes):
898 def changed_tooltip(nodes):
900 """
899 """
901 Generates a html string for changed nodes in changeset page.
900 Generates a html string for changed nodes in changeset page.
902 It limits the output to 30 entries
901 It limits the output to 30 entries
903
902
904 :param nodes: LazyNodesGenerator
903 :param nodes: LazyNodesGenerator
905 """
904 """
906 if nodes:
905 if nodes:
907 pref = ': <br/> '
906 pref = ': <br/> '
908 suf = ''
907 suf = ''
909 if len(nodes) > 30:
908 if len(nodes) > 30:
910 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
909 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
911 return literal(pref + '<br/> '.join([safe_unicode(x.path)
910 return literal(pref + '<br/> '.join([safe_unicode(x.path)
912 for x in nodes[:30]]) + suf)
911 for x in nodes[:30]]) + suf)
913 else:
912 else:
914 return ': ' + _('No Files')
913 return ': ' + _('No Files')
915
914
916
915
917 def repo_link(groups_and_repos):
916 def repo_link(groups_and_repos):
918 """
917 """
919 Makes a breadcrumbs link to repo within a group
918 Makes a breadcrumbs link to repo within a group
920 joins &raquo; on each group to create a fancy link
919 joins &raquo; on each group to create a fancy link
921
920
922 ex::
921 ex::
923 group >> subgroup >> repo
922 group >> subgroup >> repo
924
923
925 :param groups_and_repos:
924 :param groups_and_repos:
926 :param last_url:
925 :param last_url:
927 """
926 """
928 groups, just_name, repo_name = groups_and_repos
927 groups, just_name, repo_name = groups_and_repos
929 last_url = url('summary_home', repo_name=repo_name)
928 last_url = url('summary_home', repo_name=repo_name)
930 last_link = link_to(just_name, last_url)
929 last_link = link_to(just_name, last_url)
931
930
932 def make_link(group):
931 def make_link(group):
933 return link_to(group.name,
932 return link_to(group.name,
934 url('repos_group_home', group_name=group.group_name))
933 url('repos_group_home', group_name=group.group_name))
935 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
934 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
936
935
937
936
938 def fancy_file_stats(stats):
937 def fancy_file_stats(stats):
939 """
938 """
940 Displays a fancy two colored bar for number of added/deleted
939 Displays a fancy two colored bar for number of added/deleted
941 lines of code on file
940 lines of code on file
942
941
943 :param stats: two element list of added/deleted lines of code
942 :param stats: two element list of added/deleted lines of code
944 """
943 """
945 def cgen(l_type, a_v, d_v):
944 def cgen(l_type, a_v, d_v):
946 mapping = {'tr': 'top-right-rounded-corner-mid',
945 mapping = {'tr': 'top-right-rounded-corner-mid',
947 'tl': 'top-left-rounded-corner-mid',
946 'tl': 'top-left-rounded-corner-mid',
948 'br': 'bottom-right-rounded-corner-mid',
947 'br': 'bottom-right-rounded-corner-mid',
949 'bl': 'bottom-left-rounded-corner-mid'}
948 'bl': 'bottom-left-rounded-corner-mid'}
950 map_getter = lambda x: mapping[x]
949 map_getter = lambda x: mapping[x]
951
950
952 if l_type == 'a' and d_v:
951 if l_type == 'a' and d_v:
953 #case when added and deleted are present
952 #case when added and deleted are present
954 return ' '.join(map(map_getter, ['tl', 'bl']))
953 return ' '.join(map(map_getter, ['tl', 'bl']))
955
954
956 if l_type == 'a' and not d_v:
955 if l_type == 'a' and not d_v:
957 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
956 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
958
957
959 if l_type == 'd' and a_v:
958 if l_type == 'd' and a_v:
960 return ' '.join(map(map_getter, ['tr', 'br']))
959 return ' '.join(map(map_getter, ['tr', 'br']))
961
960
962 if l_type == 'd' and not a_v:
961 if l_type == 'd' and not a_v:
963 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
962 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
964
963
965 a, d = stats[0], stats[1]
964 a, d = stats[0], stats[1]
966 width = 100
965 width = 100
967
966
968 if a == 'b':
967 if a == 'b':
969 #binary mode
968 #binary mode
970 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
969 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
971 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
970 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
972 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
971 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
973
972
974 t = stats[0] + stats[1]
973 t = stats[0] + stats[1]
975 unit = float(width) / (t or 1)
974 unit = float(width) / (t or 1)
976
975
977 # needs > 9% of width to be visible or 0 to be hidden
976 # needs > 9% of width to be visible or 0 to be hidden
978 a_p = max(9, unit * a) if a > 0 else 0
977 a_p = max(9, unit * a) if a > 0 else 0
979 d_p = max(9, unit * d) if d > 0 else 0
978 d_p = max(9, unit * d) if d > 0 else 0
980 p_sum = a_p + d_p
979 p_sum = a_p + d_p
981
980
982 if p_sum > width:
981 if p_sum > width:
983 #adjust the percentage to be == 100% since we adjusted to 9
982 #adjust the percentage to be == 100% since we adjusted to 9
984 if a_p > d_p:
983 if a_p > d_p:
985 a_p = a_p - (p_sum - width)
984 a_p = a_p - (p_sum - width)
986 else:
985 else:
987 d_p = d_p - (p_sum - width)
986 d_p = d_p - (p_sum - width)
988
987
989 a_v = a if a > 0 else ''
988 a_v = a if a > 0 else ''
990 d_v = d if d > 0 else ''
989 d_v = d if d > 0 else ''
991
990
992 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
991 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
993 cgen('a', a_v, d_v), a_p, a_v
992 cgen('a', a_v, d_v), a_p, a_v
994 )
993 )
995 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
994 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
996 cgen('d', a_v, d_v), d_p, d_v
995 cgen('d', a_v, d_v), d_p, d_v
997 )
996 )
998 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
997 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
999
998
1000
999
1001 def urlify_text(text_, safe=True):
1000 def urlify_text(text_, safe=True):
1002 """
1001 """
1003 Extrac urls from text and make html links out of them
1002 Extrac urls from text and make html links out of them
1004
1003
1005 :param text_:
1004 :param text_:
1006 """
1005 """
1007
1006
1008 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1007 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1009 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1008 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1010
1009
1011 def url_func(match_obj):
1010 def url_func(match_obj):
1012 url_full = match_obj.groups()[0]
1011 url_full = match_obj.groups()[0]
1013 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1012 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1014 _newtext = url_pat.sub(url_func, text_)
1013 _newtext = url_pat.sub(url_func, text_)
1015 if safe:
1014 if safe:
1016 return literal(_newtext)
1015 return literal(_newtext)
1017 return _newtext
1016 return _newtext
1018
1017
1019
1018
1020 def urlify_changesets(text_, repository):
1019 def urlify_changesets(text_, repository):
1021 """
1020 """
1022 Extract revision ids from changeset and make link from them
1021 Extract revision ids from changeset and make link from them
1023
1022
1024 :param text_:
1023 :param text_:
1025 :param repository: repo name to build the URL with
1024 :param repository: repo name to build the URL with
1026 """
1025 """
1027 from pylons import url # doh, we need to re-import url to mock it later
1026 from pylons import url # doh, we need to re-import url to mock it later
1028 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1027 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1029
1028
1030 def url_func(match_obj):
1029 def url_func(match_obj):
1031 rev = match_obj.groups()[1]
1030 rev = match_obj.groups()[1]
1032 pref = match_obj.groups()[0]
1031 pref = match_obj.groups()[0]
1033 suf = match_obj.groups()[2]
1032 suf = match_obj.groups()[2]
1034
1033
1035 tmpl = (
1034 tmpl = (
1036 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1035 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1037 '%(rev)s</a>%(suf)s'
1036 '%(rev)s</a>%(suf)s'
1038 )
1037 )
1039 return tmpl % {
1038 return tmpl % {
1040 'pref': pref,
1039 'pref': pref,
1041 'cls': 'revision-link',
1040 'cls': 'revision-link',
1042 'url': url('changeset_home', repo_name=repository, revision=rev),
1041 'url': url('changeset_home', repo_name=repository, revision=rev),
1043 'rev': rev,
1042 'rev': rev,
1044 'suf': suf
1043 'suf': suf
1045 }
1044 }
1046
1045
1047 newtext = URL_PAT.sub(url_func, text_)
1046 newtext = URL_PAT.sub(url_func, text_)
1048
1047
1049 return newtext
1048 return newtext
1050
1049
1051
1050
1052 def urlify_commit(text_, repository=None, link_=None):
1051 def urlify_commit(text_, repository=None, link_=None):
1053 """
1052 """
1054 Parses given text message and makes proper links.
1053 Parses given text message and makes proper links.
1055 issues are linked to given issue-server, and rest is a changeset link
1054 issues are linked to given issue-server, and rest is a changeset link
1056 if link_ is given, in other case it's a plain text
1055 if link_ is given, in other case it's a plain text
1057
1056
1058 :param text_:
1057 :param text_:
1059 :param repository:
1058 :param repository:
1060 :param link_: changeset link
1059 :param link_: changeset link
1061 """
1060 """
1062 import traceback
1061 import traceback
1063 from pylons import url # doh, we need to re-import url to mock it later
1062 from pylons import url # doh, we need to re-import url to mock it later
1064
1063
1065 def escaper(string):
1064 def escaper(string):
1066 return string.replace('<', '&lt;').replace('>', '&gt;')
1065 return string.replace('<', '&lt;').replace('>', '&gt;')
1067
1066
1068 def linkify_others(t, l):
1067 def linkify_others(t, l):
1069 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1068 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1070 links = []
1069 links = []
1071 for e in urls.split(t):
1070 for e in urls.split(t):
1072 if not urls.match(e):
1071 if not urls.match(e):
1073 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1072 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1074 else:
1073 else:
1075 links.append(e)
1074 links.append(e)
1076
1075
1077 return ''.join(links)
1076 return ''.join(links)
1078
1077
1079 # urlify changesets - extrac revisions and make link out of them
1078 # urlify changesets - extrac revisions and make link out of them
1080 newtext = urlify_changesets(escaper(text_), repository)
1079 newtext = urlify_changesets(escaper(text_), repository)
1081
1080
1082 # extract http/https links and make them real urls
1081 # extract http/https links and make them real urls
1083 newtext = urlify_text(newtext, safe=False)
1082 newtext = urlify_text(newtext, safe=False)
1084
1083
1085 try:
1084 try:
1086 from rhodecode import CONFIG
1085 from rhodecode import CONFIG
1087 conf = CONFIG
1086 conf = CONFIG
1088
1087
1089 # allow multiple issue servers to be used
1088 # allow multiple issue servers to be used
1090 valid_indices = [
1089 valid_indices = [
1091 x.group(1)
1090 x.group(1)
1092 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1091 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1093 if x and 'issue_server_link%s' % x.group(1) in conf
1092 if x and 'issue_server_link%s' % x.group(1) in conf
1094 and 'issue_prefix%s' % x.group(1) in conf
1093 and 'issue_prefix%s' % x.group(1) in conf
1095 ]
1094 ]
1096
1095
1097 log.debug('found issue server suffixes `%s` during valuation of: %s'
1096 log.debug('found issue server suffixes `%s` during valuation of: %s'
1098 % (','.join(valid_indices), newtext))
1097 % (','.join(valid_indices), newtext))
1099
1098
1100 for pattern_index in valid_indices:
1099 for pattern_index in valid_indices:
1101 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1100 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1102 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1101 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1103 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1102 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1104
1103
1105 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1104 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1106 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1105 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1107 ISSUE_PREFIX))
1106 ISSUE_PREFIX))
1108
1107
1109 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1108 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1110
1109
1111 def url_func(match_obj):
1110 def url_func(match_obj):
1112 pref = ''
1111 pref = ''
1113 if match_obj.group().startswith(' '):
1112 if match_obj.group().startswith(' '):
1114 pref = ' '
1113 pref = ' '
1115
1114
1116 issue_id = ''.join(match_obj.groups())
1115 issue_id = ''.join(match_obj.groups())
1117 tmpl = (
1116 tmpl = (
1118 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1117 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1119 '%(issue-prefix)s%(id-repr)s'
1118 '%(issue-prefix)s%(id-repr)s'
1120 '</a>'
1119 '</a>'
1121 )
1120 )
1122 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1121 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1123 if repository:
1122 if repository:
1124 url = url.replace('{repo}', repository)
1123 url = url.replace('{repo}', repository)
1125 repo_name = repository.split(URL_SEP)[-1]
1124 repo_name = repository.split(URL_SEP)[-1]
1126 url = url.replace('{repo_name}', repo_name)
1125 url = url.replace('{repo_name}', repo_name)
1127
1126
1128 return tmpl % {
1127 return tmpl % {
1129 'pref': pref,
1128 'pref': pref,
1130 'cls': 'issue-tracker-link',
1129 'cls': 'issue-tracker-link',
1131 'url': url,
1130 'url': url,
1132 'id-repr': issue_id,
1131 'id-repr': issue_id,
1133 'issue-prefix': ISSUE_PREFIX,
1132 'issue-prefix': ISSUE_PREFIX,
1134 'serv': ISSUE_SERVER_LNK,
1133 'serv': ISSUE_SERVER_LNK,
1135 }
1134 }
1136 newtext = URL_PAT.sub(url_func, newtext)
1135 newtext = URL_PAT.sub(url_func, newtext)
1137 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1136 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1138
1137
1139 # if we actually did something above
1138 # if we actually did something above
1140 if link_:
1139 if link_:
1141 # wrap not links into final link => link_
1140 # wrap not links into final link => link_
1142 newtext = linkify_others(newtext, link_)
1141 newtext = linkify_others(newtext, link_)
1143 except Exception:
1142 except Exception:
1144 log.error(traceback.format_exc())
1143 log.error(traceback.format_exc())
1145 pass
1144 pass
1146
1145
1147 return literal(newtext)
1146 return literal(newtext)
1148
1147
1149
1148
1150 def rst(source):
1149 def rst(source):
1151 return literal('<div class="rst-block">%s</div>' %
1150 return literal('<div class="rst-block">%s</div>' %
1152 MarkupRenderer.rst(source))
1151 MarkupRenderer.rst(source))
1153
1152
1154
1153
1155 def rst_w_mentions(source):
1154 def rst_w_mentions(source):
1156 """
1155 """
1157 Wrapped rst renderer with @mention highlighting
1156 Wrapped rst renderer with @mention highlighting
1158
1157
1159 :param source:
1158 :param source:
1160 """
1159 """
1161 return literal('<div class="rst-block">%s</div>' %
1160 return literal('<div class="rst-block">%s</div>' %
1162 MarkupRenderer.rst_with_mentions(source))
1161 MarkupRenderer.rst_with_mentions(source))
1163
1162
1164
1163
1165 def changeset_status(repo, revision):
1164 def changeset_status(repo, revision):
1166 return ChangesetStatusModel().get_status(repo, revision)
1165 return ChangesetStatusModel().get_status(repo, revision)
1167
1166
1168
1167
1169 def changeset_status_lbl(changeset_status):
1168 def changeset_status_lbl(changeset_status):
1170 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1169 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1171
1170
1172
1171
1173 def get_permission_name(key):
1172 def get_permission_name(key):
1174 return dict(Permission.PERMS).get(key)
1173 return dict(Permission.PERMS).get(key)
1175
1174
1176
1175
1177 def journal_filter_help():
1176 def journal_filter_help():
1178 return _(textwrap.dedent('''
1177 return _(textwrap.dedent('''
1179 Example filter terms:
1178 Example filter terms:
1180 repository:vcs
1179 repository:vcs
1181 username:marcin
1180 username:marcin
1182 action:*push*
1181 action:*push*
1183 ip:127.0.0.1
1182 ip:127.0.0.1
1184 date:20120101
1183 date:20120101
1185 date:[20120101100000 TO 20120102]
1184 date:[20120101100000 TO 20120102]
1186
1185
1187 Generate wildcards using '*' character:
1186 Generate wildcards using '*' character:
1188 "repositroy:vcs*" - search everything starting with 'vcs'
1187 "repositroy:vcs*" - search everything starting with 'vcs'
1189 "repository:*vcs*" - search for repository containing 'vcs'
1188 "repository:*vcs*" - search for repository containing 'vcs'
1190
1189
1191 Optional AND / OR operators in queries
1190 Optional AND / OR operators in queries
1192 "repository:vcs OR repository:test"
1191 "repository:vcs OR repository:test"
1193 "username:test AND repository:test*"
1192 "username:test AND repository:test*"
1194 '''))
1193 '''))
1195
1194
1196
1195
1197 def not_mapped_error(repo_name):
1196 def not_mapped_error(repo_name):
1198 flash(_('%s repository is not mapped to db perhaps'
1197 flash(_('%s repository is not mapped to db perhaps'
1199 ' it was created or renamed from the filesystem'
1198 ' it was created or renamed from the filesystem'
1200 ' please run the application again'
1199 ' please run the application again'
1201 ' in order to rescan repositories') % repo_name, category='error')
1200 ' in order to rescan repositories') % repo_name, category='error')
1202
1201
1203
1202
1204 def ip_range(ip_addr):
1203 def ip_range(ip_addr):
1205 from rhodecode.model.db import UserIpMap
1204 from rhodecode.model.db import UserIpMap
1206 s, e = UserIpMap._get_ip_range(ip_addr)
1205 s, e = UserIpMap._get_ip_range(ip_addr)
1207 return '%s - %s' % (s, e)
1206 return '%s - %s' % (s, e)
General Comments 0
You need to be logged in to leave comments. Login now