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