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