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