##// END OF EJS Templates
fixed urlify changesets regex + tests
marcink -
r3405:a9adca4b beta
parent child Browse files
Show More
@@ -1,1191 +1,1186 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_, safe=True):
980 def urlify_text(text_, safe=True):
981 """
981 """
982 Extrac urls from text and make html links out of them
982 Extrac urls from text and make html links out of them
983
983
984 :param text_:
984 :param text_:
985 """
985 """
986
986
987 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]|[$-_@.&+]'''
988 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
988 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
989
989
990 def url_func(match_obj):
990 def url_func(match_obj):
991 url_full = match_obj.groups()[0]
991 url_full = match_obj.groups()[0]
992 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
992 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
993 _newtext = url_pat.sub(url_func, text_)
993 _newtext = url_pat.sub(url_func, text_)
994 if safe:
994 if safe:
995 return literal(_newtext)
995 return literal(_newtext)
996 return _newtext
996 return _newtext
997
997
998
998
999 def urlify_changesets(text_, repository):
999 def urlify_changesets(text_, repository):
1000 """
1000 """
1001 Extract revision ids from changeset and make link from them
1001 Extract revision ids from changeset and make link from them
1002
1002
1003 :param text_:
1003 :param text_:
1004 :param repository: repo name to build the URL with
1004 :param repository: repo name to build the URL with
1005 """
1005 """
1006 from pylons import url # doh, we need to re-import url to mock it later
1006 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)')
1007 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1008
1008
1009 def url_func(match_obj):
1009 def url_func(match_obj):
1010 rev = match_obj.groups()[0]
1010 rev = match_obj.groups()[1]
1011 pref = ''
1011 pref = match_obj.groups()[0]
1012 suf = ''
1012 suf = match_obj.groups()[2]
1013 if match_obj.group().startswith(' '):
1013
1014 pref = ' '
1015 if match_obj.group().endswith(' '):
1016 suf = ' '
1017 tmpl = (
1014 tmpl = (
1018 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1015 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1019 '%(rev)s'
1016 '%(rev)s</a>%(suf)s'
1020 '</a>'
1021 '%(suf)s'
1022 )
1017 )
1023 return tmpl % {
1018 return tmpl % {
1024 'pref': pref,
1019 'pref': pref,
1025 'cls': 'revision-link',
1020 'cls': 'revision-link',
1026 'url': url('changeset_home', repo_name=repository, revision=rev),
1021 'url': url('changeset_home', repo_name=repository, revision=rev),
1027 'rev': rev,
1022 'rev': rev,
1028 'suf': suf
1023 'suf': suf
1029 }
1024 }
1030
1025
1031 newtext = URL_PAT.sub(url_func, text_)
1026 newtext = URL_PAT.sub(url_func, text_)
1032
1027
1033 return newtext
1028 return newtext
1034
1029
1035
1030
1036 def urlify_commit(text_, repository=None, link_=None):
1031 def urlify_commit(text_, repository=None, link_=None):
1037 """
1032 """
1038 Parses given text message and makes proper links.
1033 Parses given text message and makes proper links.
1039 issues are linked to given issue-server, and rest is a changeset link
1034 issues are linked to given issue-server, and rest is a changeset link
1040 if link_ is given, in other case it's a plain text
1035 if link_ is given, in other case it's a plain text
1041
1036
1042 :param text_:
1037 :param text_:
1043 :param repository:
1038 :param repository:
1044 :param link_: changeset link
1039 :param link_: changeset link
1045 """
1040 """
1046 import traceback
1041 import traceback
1047 from pylons import url # doh, we need to re-import url to mock it later
1042 from pylons import url # doh, we need to re-import url to mock it later
1048
1043
1049 def escaper(string):
1044 def escaper(string):
1050 return string.replace('<', '&lt;').replace('>', '&gt;')
1045 return string.replace('<', '&lt;').replace('>', '&gt;')
1051
1046
1052 def linkify_others(t, l):
1047 def linkify_others(t, l):
1053 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1048 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1054 links = []
1049 links = []
1055 for e in urls.split(t):
1050 for e in urls.split(t):
1056 if not urls.match(e):
1051 if not urls.match(e):
1057 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1052 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1058 else:
1053 else:
1059 links.append(e)
1054 links.append(e)
1060
1055
1061 return ''.join(links)
1056 return ''.join(links)
1062
1057
1063 # urlify changesets - extrac revisions and make link out of them
1058 # urlify changesets - extrac revisions and make link out of them
1064 newtext = urlify_changesets(escaper(text_), repository)
1059 newtext = urlify_changesets(escaper(text_), repository)
1065
1060
1066 # extract http/https links and make them real urls
1061 # extract http/https links and make them real urls
1067 newtext = urlify_text(newtext, safe=False)
1062 newtext = urlify_text(newtext, safe=False)
1068
1063
1069 try:
1064 try:
1070 from rhodecode import CONFIG
1065 from rhodecode import CONFIG
1071 conf = CONFIG
1066 conf = CONFIG
1072
1067
1073 # allow multiple issue servers to be used
1068 # allow multiple issue servers to be used
1074 valid_indices = [
1069 valid_indices = [
1075 x.group(1)
1070 x.group(1)
1076 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1071 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1077 if x and 'issue_server_link%s' % x.group(1) in conf
1072 if x and 'issue_server_link%s' % x.group(1) in conf
1078 and 'issue_prefix%s' % x.group(1) in conf
1073 and 'issue_prefix%s' % x.group(1) in conf
1079 ]
1074 ]
1080
1075
1081 log.debug('found issue server suffixes `%s` during valuation of: %s'
1076 log.debug('found issue server suffixes `%s` during valuation of: %s'
1082 % (','.join(valid_indices), newtext))
1077 % (','.join(valid_indices), newtext))
1083
1078
1084 for pattern_index in valid_indices:
1079 for pattern_index in valid_indices:
1085 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1080 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1086 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1081 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1087 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1082 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1088
1083
1089 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1084 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1090 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1085 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1091 ISSUE_PREFIX))
1086 ISSUE_PREFIX))
1092
1087
1093 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1088 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1094
1089
1095 def url_func(match_obj):
1090 def url_func(match_obj):
1096 pref = ''
1091 pref = ''
1097 if match_obj.group().startswith(' '):
1092 if match_obj.group().startswith(' '):
1098 pref = ' '
1093 pref = ' '
1099
1094
1100 issue_id = ''.join(match_obj.groups())
1095 issue_id = ''.join(match_obj.groups())
1101 tmpl = (
1096 tmpl = (
1102 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1097 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1103 '%(issue-prefix)s%(id-repr)s'
1098 '%(issue-prefix)s%(id-repr)s'
1104 '</a>'
1099 '</a>'
1105 )
1100 )
1106 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1101 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1107 if repository:
1102 if repository:
1108 url = url.replace('{repo}', repository)
1103 url = url.replace('{repo}', repository)
1109 repo_name = repository.split(URL_SEP)[-1]
1104 repo_name = repository.split(URL_SEP)[-1]
1110 url = url.replace('{repo_name}', repo_name)
1105 url = url.replace('{repo_name}', repo_name)
1111
1106
1112 return tmpl % {
1107 return tmpl % {
1113 'pref': pref,
1108 'pref': pref,
1114 'cls': 'issue-tracker-link',
1109 'cls': 'issue-tracker-link',
1115 'url': url,
1110 'url': url,
1116 'id-repr': issue_id,
1111 'id-repr': issue_id,
1117 'issue-prefix': ISSUE_PREFIX,
1112 'issue-prefix': ISSUE_PREFIX,
1118 'serv': ISSUE_SERVER_LNK,
1113 'serv': ISSUE_SERVER_LNK,
1119 }
1114 }
1120 newtext = URL_PAT.sub(url_func, newtext)
1115 newtext = URL_PAT.sub(url_func, newtext)
1121 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1116 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1122
1117
1123 # if we actually did something above
1118 # if we actually did something above
1124 if link_:
1119 if link_:
1125 # wrap not links into final link => link_
1120 # wrap not links into final link => link_
1126 newtext = linkify_others(newtext, link_)
1121 newtext = linkify_others(newtext, link_)
1127 except:
1122 except:
1128 log.error(traceback.format_exc())
1123 log.error(traceback.format_exc())
1129 pass
1124 pass
1130
1125
1131 return literal(newtext)
1126 return literal(newtext)
1132
1127
1133
1128
1134 def rst(source):
1129 def rst(source):
1135 return literal('<div class="rst-block">%s</div>' %
1130 return literal('<div class="rst-block">%s</div>' %
1136 MarkupRenderer.rst(source))
1131 MarkupRenderer.rst(source))
1137
1132
1138
1133
1139 def rst_w_mentions(source):
1134 def rst_w_mentions(source):
1140 """
1135 """
1141 Wrapped rst renderer with @mention highlighting
1136 Wrapped rst renderer with @mention highlighting
1142
1137
1143 :param source:
1138 :param source:
1144 """
1139 """
1145 return literal('<div class="rst-block">%s</div>' %
1140 return literal('<div class="rst-block">%s</div>' %
1146 MarkupRenderer.rst_with_mentions(source))
1141 MarkupRenderer.rst_with_mentions(source))
1147
1142
1148
1143
1149 def changeset_status(repo, revision):
1144 def changeset_status(repo, revision):
1150 return ChangesetStatusModel().get_status(repo, revision)
1145 return ChangesetStatusModel().get_status(repo, revision)
1151
1146
1152
1147
1153 def changeset_status_lbl(changeset_status):
1148 def changeset_status_lbl(changeset_status):
1154 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1149 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1155
1150
1156
1151
1157 def get_permission_name(key):
1152 def get_permission_name(key):
1158 return dict(Permission.PERMS).get(key)
1153 return dict(Permission.PERMS).get(key)
1159
1154
1160
1155
1161 def journal_filter_help():
1156 def journal_filter_help():
1162 return _(textwrap.dedent('''
1157 return _(textwrap.dedent('''
1163 Example filter terms:
1158 Example filter terms:
1164 repository:vcs
1159 repository:vcs
1165 username:marcin
1160 username:marcin
1166 action:*push*
1161 action:*push*
1167 ip:127.0.0.1
1162 ip:127.0.0.1
1168 date:20120101
1163 date:20120101
1169 date:[20120101100000 TO 20120102]
1164 date:[20120101100000 TO 20120102]
1170
1165
1171 Generate wildcards using '*' character:
1166 Generate wildcards using '*' character:
1172 "repositroy:vcs*" - search everything starting with 'vcs'
1167 "repositroy:vcs*" - search everything starting with 'vcs'
1173 "repository:*vcs*" - search for repository containing 'vcs'
1168 "repository:*vcs*" - search for repository containing 'vcs'
1174
1169
1175 Optional AND / OR operators in queries
1170 Optional AND / OR operators in queries
1176 "repository:vcs OR repository:test"
1171 "repository:vcs OR repository:test"
1177 "username:test AND repository:test*"
1172 "username:test AND repository:test*"
1178 '''))
1173 '''))
1179
1174
1180
1175
1181 def not_mapped_error(repo_name):
1176 def not_mapped_error(repo_name):
1182 flash(_('%s repository is not mapped to db perhaps'
1177 flash(_('%s repository is not mapped to db perhaps'
1183 ' it was created or renamed from the filesystem'
1178 ' it was created or renamed from the filesystem'
1184 ' please run the application again'
1179 ' please run the application again'
1185 ' in order to rescan repositories') % repo_name, category='error')
1180 ' in order to rescan repositories') % repo_name, category='error')
1186
1181
1187
1182
1188 def ip_range(ip_addr):
1183 def ip_range(ip_addr):
1189 from rhodecode.model.db import UserIpMap
1184 from rhodecode.model.db import UserIpMap
1190 s, e = UserIpMap._get_ip_range(ip_addr)
1185 s, e = UserIpMap._get_ip_range(ip_addr)
1191 return '%s - %s' % (s, e)
1186 return '%s - %s' % (s, e)
@@ -1,254 +1,290 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.tests.test_libs
3 rhodecode.tests.test_libs
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6
6
7 Package for testing various lib/helper functions in rhodecode
7 Package for testing various lib/helper functions in rhodecode
8
8
9 :created_on: Jun 9, 2011
9 :created_on: Jun 9, 2011
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import unittest
26 import unittest
27 import datetime
27 import datetime
28 import hashlib
28 import hashlib
29 import mock
29 import mock
30 from rhodecode.tests import *
30 from rhodecode.tests import *
31
31
32 proto = 'http'
32 proto = 'http'
33 TEST_URLS = [
33 TEST_URLS = [
34 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
34 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
35 '%s://127.0.0.1' % proto),
35 '%s://127.0.0.1' % proto),
36 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
36 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
37 '%s://127.0.0.1' % proto),
37 '%s://127.0.0.1' % proto),
38 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
38 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
39 '%s://127.0.0.1' % proto),
39 '%s://127.0.0.1' % proto),
40 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
40 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
41 '%s://127.0.0.1:8080' % proto),
41 '%s://127.0.0.1:8080' % proto),
42 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
42 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
43 '%s://domain.org' % proto),
43 '%s://domain.org' % proto),
44 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
44 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
45 '8080'],
45 '8080'],
46 '%s://domain.org:8080' % proto),
46 '%s://domain.org:8080' % proto),
47 ]
47 ]
48
48
49 proto = 'https'
49 proto = 'https'
50 TEST_URLS += [
50 TEST_URLS += [
51 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
51 ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
52 '%s://127.0.0.1' % proto),
52 '%s://127.0.0.1' % proto),
53 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
53 ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
54 '%s://127.0.0.1' % proto),
54 '%s://127.0.0.1' % proto),
55 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
55 ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
56 '%s://127.0.0.1' % proto),
56 '%s://127.0.0.1' % proto),
57 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
57 ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
58 '%s://127.0.0.1:8080' % proto),
58 '%s://127.0.0.1:8080' % proto),
59 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
59 ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
60 '%s://domain.org' % proto),
60 '%s://domain.org' % proto),
61 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
61 ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
62 '8080'],
62 '8080'],
63 '%s://domain.org:8080' % proto),
63 '%s://domain.org:8080' % proto),
64 ]
64 ]
65
65
66
66
67 class TestLibs(unittest.TestCase):
67 class TestLibs(unittest.TestCase):
68
68
69 @parameterized.expand(TEST_URLS)
69 @parameterized.expand(TEST_URLS)
70 def test_uri_filter(self, test_url, expected, expected_creds):
70 def test_uri_filter(self, test_url, expected, expected_creds):
71 from rhodecode.lib.utils2 import uri_filter
71 from rhodecode.lib.utils2 import uri_filter
72 self.assertEqual(uri_filter(test_url), expected)
72 self.assertEqual(uri_filter(test_url), expected)
73
73
74 @parameterized.expand(TEST_URLS)
74 @parameterized.expand(TEST_URLS)
75 def test_credentials_filter(self, test_url, expected, expected_creds):
75 def test_credentials_filter(self, test_url, expected, expected_creds):
76 from rhodecode.lib.utils2 import credentials_filter
76 from rhodecode.lib.utils2 import credentials_filter
77 self.assertEqual(credentials_filter(test_url), expected_creds)
77 self.assertEqual(credentials_filter(test_url), expected_creds)
78
78
79 @parameterized.expand([('t', True),
79 @parameterized.expand([('t', True),
80 ('true', True),
80 ('true', True),
81 ('y', True),
81 ('y', True),
82 ('yes', True),
82 ('yes', True),
83 ('on', True),
83 ('on', True),
84 ('1', True),
84 ('1', True),
85 ('Y', True),
85 ('Y', True),
86 ('yeS', True),
86 ('yeS', True),
87 ('Y', True),
87 ('Y', True),
88 ('TRUE', True),
88 ('TRUE', True),
89 ('T', True),
89 ('T', True),
90 ('False', False),
90 ('False', False),
91 ('F', False),
91 ('F', False),
92 ('FALSE', False),
92 ('FALSE', False),
93 ('0', False),
93 ('0', False),
94 ('-1', False),
94 ('-1', False),
95 ('', False)
95 ('', False)
96 ])
96 ])
97 def test_str2bool(self, str_bool, expected):
97 def test_str2bool(self, str_bool, expected):
98 from rhodecode.lib.utils2 import str2bool
98 from rhodecode.lib.utils2 import str2bool
99 self.assertEqual(str2bool(str_bool), expected)
99 self.assertEqual(str2bool(str_bool), expected)
100
100
101 def test_mention_extractor(self):
101 def test_mention_extractor(self):
102 from rhodecode.lib.utils2 import extract_mentioned_users
102 from rhodecode.lib.utils2 import extract_mentioned_users
103 sample = (
103 sample = (
104 "@first hi there @marcink here's my email marcin@email.com "
104 "@first hi there @marcink here's my email marcin@email.com "
105 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
105 "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
106 "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl "
106 "@MARCIN @maRCiN @2one_more22 @john please see this http://org.pl "
107 "@marian.user just do it @marco-polo and next extract @marco_polo "
107 "@marian.user just do it @marco-polo and next extract @marco_polo "
108 "user.dot hej ! not-needed maril@domain.org"
108 "user.dot hej ! not-needed maril@domain.org"
109 )
109 )
110
110
111 s = sorted([
111 s = sorted([
112 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
112 'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
113 'marian.user', 'marco-polo', 'marco_polo'
113 'marian.user', 'marco-polo', 'marco_polo'
114 ], key=lambda k: k.lower())
114 ], key=lambda k: k.lower())
115 self.assertEqual(s, extract_mentioned_users(sample))
115 self.assertEqual(s, extract_mentioned_users(sample))
116
116
117 def test_age(self):
117 def test_age(self):
118 from rhodecode.lib.utils2 import age
118 from rhodecode.lib.utils2 import age
119 from dateutil import relativedelta
119 from dateutil import relativedelta
120 n = datetime.datetime.now()
120 n = datetime.datetime.now()
121 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
121 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
122
122
123 self.assertEqual(age(n), u'just now')
123 self.assertEqual(age(n), u'just now')
124 self.assertEqual(age(n + delt(seconds=-1)), u'1 second ago')
124 self.assertEqual(age(n + delt(seconds=-1)), u'1 second ago')
125 self.assertEqual(age(n + delt(seconds=-60 * 2)), u'2 minutes ago')
125 self.assertEqual(age(n + delt(seconds=-60 * 2)), u'2 minutes ago')
126 self.assertEqual(age(n + delt(hours=-1)), u'1 hour ago')
126 self.assertEqual(age(n + delt(hours=-1)), u'1 hour ago')
127 self.assertEqual(age(n + delt(hours=-24)), u'1 day ago')
127 self.assertEqual(age(n + delt(hours=-24)), u'1 day ago')
128 self.assertEqual(age(n + delt(hours=-24 * 5)), u'5 days ago')
128 self.assertEqual(age(n + delt(hours=-24 * 5)), u'5 days ago')
129 self.assertEqual(age(n + delt(months=-1)), u'1 month ago')
129 self.assertEqual(age(n + delt(months=-1)), u'1 month ago')
130 self.assertEqual(age(n + delt(months=-1, days=-2)), u'1 month and 2 days ago')
130 self.assertEqual(age(n + delt(months=-1, days=-2)), u'1 month and 2 days ago')
131 self.assertEqual(age(n + delt(years=-1, months=-1)), u'1 year and 1 month ago')
131 self.assertEqual(age(n + delt(years=-1, months=-1)), u'1 year and 1 month ago')
132
132
133 def test_age_in_future(self):
133 def test_age_in_future(self):
134 from rhodecode.lib.utils2 import age
134 from rhodecode.lib.utils2 import age
135 from dateutil import relativedelta
135 from dateutil import relativedelta
136 n = datetime.datetime.now()
136 n = datetime.datetime.now()
137 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
137 delt = lambda *args, **kwargs: relativedelta.relativedelta(*args, **kwargs)
138
138
139 self.assertEqual(age(n), u'just now')
139 self.assertEqual(age(n), u'just now')
140 self.assertEqual(age(n + delt(seconds=1)), u'in 1 second')
140 self.assertEqual(age(n + delt(seconds=1)), u'in 1 second')
141 self.assertEqual(age(n + delt(seconds=60 * 2)), u'in 2 minutes')
141 self.assertEqual(age(n + delt(seconds=60 * 2)), u'in 2 minutes')
142 self.assertEqual(age(n + delt(hours=1)), u'in 1 hour')
142 self.assertEqual(age(n + delt(hours=1)), u'in 1 hour')
143 self.assertEqual(age(n + delt(hours=24)), u'in 1 day')
143 self.assertEqual(age(n + delt(hours=24)), u'in 1 day')
144 self.assertEqual(age(n + delt(hours=24 * 5)), u'in 5 days')
144 self.assertEqual(age(n + delt(hours=24 * 5)), u'in 5 days')
145 self.assertEqual(age(n + delt(months=1)), u'in 1 month')
145 self.assertEqual(age(n + delt(months=1)), u'in 1 month')
146 self.assertEqual(age(n + delt(months=1, days=1)), u'in 1 month and 1 day')
146 self.assertEqual(age(n + delt(months=1, days=1)), u'in 1 month and 1 day')
147 self.assertEqual(age(n + delt(years=1, months=1)), u'in 1 year and 1 month')
147 self.assertEqual(age(n + delt(years=1, months=1)), u'in 1 year and 1 month')
148
148
149 def test_tag_exctrator(self):
149 def test_tag_exctrator(self):
150 sample = (
150 sample = (
151 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
151 "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
152 "[requires] [stale] [see<>=>] [see => http://url.com]"
152 "[requires] [stale] [see<>=>] [see => http://url.com]"
153 "[requires => url] [lang => python] [just a tag]"
153 "[requires => url] [lang => python] [just a tag]"
154 "[,d] [ => ULR ] [obsolete] [desc]]"
154 "[,d] [ => ULR ] [obsolete] [desc]]"
155 )
155 )
156 from rhodecode.lib.helpers import desc_stylize
156 from rhodecode.lib.helpers import desc_stylize
157 res = desc_stylize(sample)
157 res = desc_stylize(sample)
158 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
158 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
159 self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
159 self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
160 self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
160 self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
161 self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
161 self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
162 self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
162 self.assertTrue('<div class="metatag" tag="requires">requires =&gt; <a href="/url">url</a></div>' in res)
163 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
163 self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
164
164
165 def test_alternative_gravatar(self):
165 def test_alternative_gravatar(self):
166 from rhodecode.lib.helpers import gravatar_url
166 from rhodecode.lib.helpers import gravatar_url
167 _md5 = lambda s: hashlib.md5(s).hexdigest()
167 _md5 = lambda s: hashlib.md5(s).hexdigest()
168
168
169 def fake_conf(**kwargs):
169 def fake_conf(**kwargs):
170 from pylons import config
170 from pylons import config
171 config['app_conf'] = {}
171 config['app_conf'] = {}
172 config['app_conf']['use_gravatar'] = True
172 config['app_conf']['use_gravatar'] = True
173 config['app_conf'].update(kwargs)
173 config['app_conf'].update(kwargs)
174 return config
174 return config
175
175
176 class fake_url():
176 class fake_url():
177 @classmethod
177 @classmethod
178 def current(cls, *args, **kwargs):
178 def current(cls, *args, **kwargs):
179 return 'https://server.com'
179 return 'https://server.com'
180
180
181 with mock.patch('pylons.url', fake_url):
181 with mock.patch('pylons.url', fake_url):
182 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
182 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
183 with mock.patch('pylons.config', fake):
183 with mock.patch('pylons.config', fake):
184 from pylons import url
184 from pylons import url
185 assert url.current() == 'https://server.com'
185 assert url.current() == 'https://server.com'
186 grav = gravatar_url(email_address='test@foo.com', size=24)
186 grav = gravatar_url(email_address='test@foo.com', size=24)
187 assert grav == 'http://test.com/test@foo.com'
187 assert grav == 'http://test.com/test@foo.com'
188
188
189 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
189 fake = fake_conf(alternative_gravatar_url='http://test.com/{email}')
190 with mock.patch('pylons.config', fake):
190 with mock.patch('pylons.config', fake):
191 grav = gravatar_url(email_address='test@foo.com', size=24)
191 grav = gravatar_url(email_address='test@foo.com', size=24)
192 assert grav == 'http://test.com/test@foo.com'
192 assert grav == 'http://test.com/test@foo.com'
193
193
194 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}')
194 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}')
195 with mock.patch('pylons.config', fake):
195 with mock.patch('pylons.config', fake):
196 em = 'test@foo.com'
196 em = 'test@foo.com'
197 grav = gravatar_url(email_address=em, size=24)
197 grav = gravatar_url(email_address=em, size=24)
198 assert grav == 'http://test.com/%s' % (_md5(em))
198 assert grav == 'http://test.com/%s' % (_md5(em))
199
199
200 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}')
200 fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}')
201 with mock.patch('pylons.config', fake):
201 with mock.patch('pylons.config', fake):
202 em = 'test@foo.com'
202 em = 'test@foo.com'
203 grav = gravatar_url(email_address=em, size=24)
203 grav = gravatar_url(email_address=em, size=24)
204 assert grav == 'http://test.com/%s/%s' % (_md5(em), 24)
204 assert grav == 'http://test.com/%s/%s' % (_md5(em), 24)
205
205
206 fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}')
206 fake = fake_conf(alternative_gravatar_url='{scheme}://{netloc}/{md5email}/{size}')
207 with mock.patch('pylons.config', fake):
207 with mock.patch('pylons.config', fake):
208 em = 'test@foo.com'
208 em = 'test@foo.com'
209 grav = gravatar_url(email_address=em, size=24)
209 grav = gravatar_url(email_address=em, size=24)
210 assert grav == 'https://server.com/%s/%s' % (_md5(em), 24)
210 assert grav == 'https://server.com/%s/%s' % (_md5(em), 24)
211
211
212 def _quick_url(self, text, tmpl="""<a class="revision-link" href="%s">%s</a>""", url_=None):
213 """
214 Changes `some text url[foo]` => `some text <a href="/">foo</a>
215
216 :param text:
217 """
218 import re
219 #quickly change expected url[] into a link
220 URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
221
222 def url_func(match_obj):
223 _url = match_obj.groups()[0]
224 return tmpl % (url_ or '/some-url', _url)
225 return URL_PAT.sub(url_func, text)
226
212 @parameterized.expand([
227 @parameterized.expand([
213 ("",
228 ("",
214 ""),
229 ""),
215 ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
230 ("git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68",
216 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
231 "git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1441655 13f79535-47bb-0310-9956-ffa450edef68"),
217 ("from rev 000000000000",
232 ("from rev 000000000000",
218 "from rev url[000000000000]"),
233 "from rev url[000000000000]"),
219 ("from rev 000000000000123123 also rev 000000000000",
234 ("from rev 000000000000123123 also rev 000000000000",
220 "from rev url[000000000000123123] also rev url[000000000000]"),
235 "from rev url[000000000000123123] also rev url[000000000000]"),
221 ("this should-000 00",
236 ("this should-000 00",
222 "this should-000 00"),
237 "this should-000 00"),
223 ("longtextffffffffff rev 123123123123",
238 ("longtextffffffffff rev 123123123123",
224 "longtextffffffffff rev url[123123123123]"),
239 "longtextffffffffff rev url[123123123123]"),
225 ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
240 ("rev ffffffffffffffffffffffffffffffffffffffffffffffffff",
226 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
241 "rev ffffffffffffffffffffffffffffffffffffffffffffffffff"),
227 ("ffffffffffff some text traalaa",
242 ("ffffffffffff some text traalaa",
228 "url[ffffffffffff] some text traalaa"),
243 "url[ffffffffffff] some text traalaa"),
229 ("""Multi line
244 ("""Multi line
230 123123123123
245 123123123123
231 some text 123123123123""",
246 some text 123123123123
247 sometimes !
248 """,
232 """Multi line
249 """Multi line
233 url[123123123123]
250 url[123123123123]
234 some text url[123123123123]""")
251 some text url[123123123123]
252 sometimes !
253 """)
235 ])
254 ])
236 def test_urlify_changesets(self, sample, expected):
255 def test_urlify_changesets(self, sample, expected):
237 import re
238
239 def fake_url(self, *args, **kwargs):
256 def fake_url(self, *args, **kwargs):
240 return '/some-url'
257 return '/some-url'
241
258
242 #quickly change expected url[] into a link
259 expected = self._quick_url(expected)
243 URL_PAT = re.compile(r'(?:url\[)(.+?)(?:\])')
244
245 def url_func(match_obj):
246 _url = match_obj.groups()[0]
247 tmpl = """<a class="revision-link" href="/some-url">%s</a>"""
248 return tmpl % _url
249
250 expected = URL_PAT.sub(url_func, expected)
251
260
252 with mock.patch('pylons.url', fake_url):
261 with mock.patch('pylons.url', fake_url):
253 from rhodecode.lib.helpers import urlify_changesets
262 from rhodecode.lib.helpers import urlify_changesets
254 self.assertEqual(urlify_changesets(sample, 'repo_name'), expected)
263 self.assertEqual(urlify_changesets(sample, 'repo_name'), expected)
264
265 @parameterized.expand([
266 ("",
267 "",
268 ""),
269 ("https://svn.apache.org/repos",
270 "url[https://svn.apache.org/repos]",
271 "https://svn.apache.org/repos"),
272 ("http://svn.apache.org/repos",
273 "url[http://svn.apache.org/repos]",
274 "http://svn.apache.org/repos"),
275 ("from rev a also rev http://google.com",
276 "from rev a also rev url[http://google.com]",
277 "http://google.com"),
278 ("""Multi line
279 https://foo.bar.com
280 some text lalala""",
281 """Multi line
282 url[https://foo.bar.com]
283 some text lalala""",
284 "https://foo.bar.com")
285 ])
286 def test_urlify_test(self, sample, expected, url_):
287 from rhodecode.lib.helpers import urlify_text
288 expected = self._quick_url(expected,
289 tmpl="""<a href="%s">%s</a>""", url_=url_)
290 self.assertEqual(urlify_text(sample), expected)
General Comments 0
You need to be logged in to leave comments. Login now