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