##// END OF EJS Templates
Switch gravatar to always use ssl....
marcink -
r3905:f7bf0ceb beta
parent child Browse files
Show More
@@ -1,1375 +1,1377
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
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 flash = _Flash()
358 flash = _Flash()
359
359
360 #==============================================================================
360 #==============================================================================
361 # SCM FILTERS available via h.
361 # SCM FILTERS available via h.
362 #==============================================================================
362 #==============================================================================
363 from rhodecode.lib.vcs.utils import author_name, author_email
363 from rhodecode.lib.vcs.utils import author_name, author_email
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
365 from rhodecode.model.db import User, ChangesetStatus
365 from rhodecode.model.db import User, ChangesetStatus
366
366
367 age = lambda x, y=False: _age(x, y)
367 age = lambda x, y=False: _age(x, y)
368 capitalize = lambda x: x.capitalize()
368 capitalize = lambda x: x.capitalize()
369 email = author_email
369 email = author_email
370 short_id = lambda x: x[:12]
370 short_id = lambda x: x[:12]
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
372
372
373
373
374 def show_id(cs):
374 def show_id(cs):
375 """
375 """
376 Configurable function that shows ID
376 Configurable function that shows ID
377 by default it's r123:fffeeefffeee
377 by default it's r123:fffeeefffeee
378
378
379 :param cs: changeset instance
379 :param cs: changeset instance
380 """
380 """
381 from rhodecode import CONFIG
381 from rhodecode import CONFIG
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
382 def_len = safe_int(CONFIG.get('show_sha_length', 12))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
383 show_rev = str2bool(CONFIG.get('show_revision_number', True))
384
384
385 raw_id = cs.raw_id[:def_len]
385 raw_id = cs.raw_id[:def_len]
386 if show_rev:
386 if show_rev:
387 return 'r%s:%s' % (cs.revision, raw_id)
387 return 'r%s:%s' % (cs.revision, raw_id)
388 else:
388 else:
389 return '%s' % (raw_id)
389 return '%s' % (raw_id)
390
390
391
391
392 def fmt_date(date):
392 def fmt_date(date):
393 if date:
393 if date:
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
395 return date.strftime(_fmt).decode('utf8')
395 return date.strftime(_fmt).decode('utf8')
396
396
397 return ""
397 return ""
398
398
399
399
400 def is_git(repository):
400 def is_git(repository):
401 if hasattr(repository, 'alias'):
401 if hasattr(repository, 'alias'):
402 _type = repository.alias
402 _type = repository.alias
403 elif hasattr(repository, 'repo_type'):
403 elif hasattr(repository, 'repo_type'):
404 _type = repository.repo_type
404 _type = repository.repo_type
405 else:
405 else:
406 _type = repository
406 _type = repository
407 return _type == 'git'
407 return _type == 'git'
408
408
409
409
410 def is_hg(repository):
410 def is_hg(repository):
411 if hasattr(repository, 'alias'):
411 if hasattr(repository, 'alias'):
412 _type = repository.alias
412 _type = repository.alias
413 elif hasattr(repository, 'repo_type'):
413 elif hasattr(repository, 'repo_type'):
414 _type = repository.repo_type
414 _type = repository.repo_type
415 else:
415 else:
416 _type = repository
416 _type = repository
417 return _type == 'hg'
417 return _type == 'hg'
418
418
419
419
420 def email_or_none(author):
420 def email_or_none(author):
421 # extract email from the commit string
421 # extract email from the commit string
422 _email = email(author)
422 _email = email(author)
423 if _email != '':
423 if _email != '':
424 # check it against RhodeCode database, and use the MAIN email for this
424 # check it against RhodeCode database, and use the MAIN email for this
425 # user
425 # user
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
427 if user is not None:
428 return user.email
428 return user.email
429 return _email
429 return _email
430
430
431 # See if it contains a username we can get an email from
431 # See if it contains a username we can get an email from
432 user = User.get_by_username(author_name(author), case_insensitive=True,
432 user = User.get_by_username(author_name(author), case_insensitive=True,
433 cache=True)
433 cache=True)
434 if user is not None:
434 if user is not None:
435 return user.email
435 return user.email
436
436
437 # No valid email, not a valid user in the system, none!
437 # No valid email, not a valid user in the system, none!
438 return None
438 return None
439
439
440
440
441 def person(author, show_attr="username_and_name"):
441 def person(author, show_attr="username_and_name"):
442 # attr to return from fetched user
442 # attr to return from fetched user
443 person_getter = lambda usr: getattr(usr, show_attr)
443 person_getter = lambda usr: getattr(usr, show_attr)
444
444
445 # Valid email in the attribute passed, see if they're in the system
445 # Valid email in the attribute passed, see if they're in the system
446 _email = email(author)
446 _email = email(author)
447 if _email != '':
447 if _email != '':
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
449 if user is not None:
449 if user is not None:
450 return person_getter(user)
450 return person_getter(user)
451
451
452 # Maybe it's a username?
452 # Maybe it's a username?
453 _author = author_name(author)
453 _author = author_name(author)
454 user = User.get_by_username(_author, case_insensitive=True,
454 user = User.get_by_username(_author, case_insensitive=True,
455 cache=True)
455 cache=True)
456 if user is not None:
456 if user is not None:
457 return person_getter(user)
457 return person_getter(user)
458
458
459 # Still nothing? Just pass back the author name if any, else the email
459 # Still nothing? Just pass back the author name if any, else the email
460 return _author or _email
460 return _author or _email
461
461
462
462
463 def person_by_id(id_, show_attr="username_and_name"):
463 def person_by_id(id_, show_attr="username_and_name"):
464 # attr to return from fetched user
464 # attr to return from fetched user
465 person_getter = lambda usr: getattr(usr, show_attr)
465 person_getter = lambda usr: getattr(usr, show_attr)
466
466
467 #maybe it's an ID ?
467 #maybe it's an ID ?
468 if str(id_).isdigit() or isinstance(id_, int):
468 if str(id_).isdigit() or isinstance(id_, int):
469 id_ = int(id_)
469 id_ = int(id_)
470 user = User.get(id_)
470 user = User.get(id_)
471 if user is not None:
471 if user is not None:
472 return person_getter(user)
472 return person_getter(user)
473 return id_
473 return id_
474
474
475
475
476 def desc_stylize(value):
476 def desc_stylize(value):
477 """
477 """
478 converts tags from value into html equivalent
478 converts tags from value into html equivalent
479
479
480 :param value:
480 :param value:
481 """
481 """
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
482 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
483 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
485 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
486 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
487 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
488 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
489 '<div class="metatag" tag="lang">\\2</div>', value)
489 '<div class="metatag" tag="lang">\\2</div>', value)
490 value = re.sub(r'\[([a-z]+)\]',
490 value = re.sub(r'\[([a-z]+)\]',
491 '<div class="metatag" tag="\\1">\\1</div>', value)
491 '<div class="metatag" tag="\\1">\\1</div>', value)
492
492
493 return value
493 return value
494
494
495
495
496 def boolicon(value):
496 def boolicon(value):
497 """Returns boolean value of a value, represented as small html image of true/false
497 """Returns boolean value of a value, represented as small html image of true/false
498 icons
498 icons
499
499
500 :param value: value
500 :param value: value
501 """
501 """
502
502
503 if value:
503 if value:
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
504 return HTML.tag('img', src=url("/images/icons/accept.png"),
505 alt=_('True'))
505 alt=_('True'))
506 else:
506 else:
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
507 return HTML.tag('img', src=url("/images/icons/cancel.png"),
508 alt=_('False'))
508 alt=_('False'))
509
509
510
510
511 def action_parser(user_log, feed=False, parse_cs=False):
511 def action_parser(user_log, feed=False, parse_cs=False):
512 """
512 """
513 This helper will action_map the specified string action into translated
513 This helper will action_map the specified string action into translated
514 fancy names with icons and links
514 fancy names with icons and links
515
515
516 :param user_log: user log instance
516 :param user_log: user log instance
517 :param feed: use output for feeds (no html and fancy icons)
517 :param feed: use output for feeds (no html and fancy icons)
518 :param parse_cs: parse Changesets into VCS instances
518 :param parse_cs: parse Changesets into VCS instances
519 """
519 """
520
520
521 action = user_log.action
521 action = user_log.action
522 action_params = ' '
522 action_params = ' '
523
523
524 x = action.split(':')
524 x = action.split(':')
525
525
526 if len(x) > 1:
526 if len(x) > 1:
527 action, action_params = x
527 action, action_params = x
528
528
529 def get_cs_links():
529 def get_cs_links():
530 revs_limit = 3 # display this amount always
530 revs_limit = 3 # display this amount always
531 revs_top_limit = 50 # show upto this amount of changesets hidden
531 revs_top_limit = 50 # show upto this amount of changesets hidden
532 revs_ids = action_params.split(',')
532 revs_ids = action_params.split(',')
533 deleted = user_log.repository is None
533 deleted = user_log.repository is None
534 if deleted:
534 if deleted:
535 return ','.join(revs_ids)
535 return ','.join(revs_ids)
536
536
537 repo_name = user_log.repository.repo_name
537 repo_name = user_log.repository.repo_name
538
538
539 def lnk(rev, repo_name):
539 def lnk(rev, repo_name):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
540 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
541 lazy_cs = True
541 lazy_cs = True
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
542 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
543 lazy_cs = False
543 lazy_cs = False
544 lbl = '?'
544 lbl = '?'
545 if rev.op == 'delete_branch':
545 if rev.op == 'delete_branch':
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
546 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
547 title = ''
547 title = ''
548 elif rev.op == 'tag':
548 elif rev.op == 'tag':
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
549 lbl = '%s' % _('Created tag: %s') % rev.ref_name
550 title = ''
550 title = ''
551 _url = '#'
551 _url = '#'
552
552
553 else:
553 else:
554 lbl = '%s' % (rev.short_id[:8])
554 lbl = '%s' % (rev.short_id[:8])
555 _url = url('changeset_home', repo_name=repo_name,
555 _url = url('changeset_home', repo_name=repo_name,
556 revision=rev.raw_id)
556 revision=rev.raw_id)
557 title = tooltip(rev.message)
557 title = tooltip(rev.message)
558 else:
558 else:
559 ## changeset cannot be found/striped/removed etc.
559 ## changeset cannot be found/striped/removed etc.
560 lbl = ('%s' % rev)[:12]
560 lbl = ('%s' % rev)[:12]
561 _url = '#'
561 _url = '#'
562 title = _('Changeset not found')
562 title = _('Changeset not found')
563 if parse_cs:
563 if parse_cs:
564 return link_to(lbl, _url, title=title, class_='tooltip')
564 return link_to(lbl, _url, title=title, class_='tooltip')
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
565 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
566 class_='lazy-cs' if lazy_cs else '')
566 class_='lazy-cs' if lazy_cs else '')
567
567
568 def _get_op(rev_txt):
568 def _get_op(rev_txt):
569 _op = None
569 _op = None
570 _name = rev_txt
570 _name = rev_txt
571 if len(rev_txt.split('=>')) == 2:
571 if len(rev_txt.split('=>')) == 2:
572 _op, _name = rev_txt.split('=>')
572 _op, _name = rev_txt.split('=>')
573 return _op, _name
573 return _op, _name
574
574
575 revs = []
575 revs = []
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
576 if len(filter(lambda v: v != '', revs_ids)) > 0:
577 repo = None
577 repo = None
578 for rev in revs_ids[:revs_top_limit]:
578 for rev in revs_ids[:revs_top_limit]:
579 _op, _name = _get_op(rev)
579 _op, _name = _get_op(rev)
580
580
581 # we want parsed changesets, or new log store format is bad
581 # we want parsed changesets, or new log store format is bad
582 if parse_cs:
582 if parse_cs:
583 try:
583 try:
584 if repo is None:
584 if repo is None:
585 repo = user_log.repository.scm_instance
585 repo = user_log.repository.scm_instance
586 _rev = repo.get_changeset(rev)
586 _rev = repo.get_changeset(rev)
587 revs.append(_rev)
587 revs.append(_rev)
588 except ChangesetDoesNotExistError:
588 except ChangesetDoesNotExistError:
589 log.error('cannot find revision %s in this repo' % rev)
589 log.error('cannot find revision %s in this repo' % rev)
590 revs.append(rev)
590 revs.append(rev)
591 continue
591 continue
592 else:
592 else:
593 _rev = AttributeDict({
593 _rev = AttributeDict({
594 'short_id': rev[:12],
594 'short_id': rev[:12],
595 'raw_id': rev,
595 'raw_id': rev,
596 'message': '',
596 'message': '',
597 'op': _op,
597 'op': _op,
598 'ref_name': _name
598 'ref_name': _name
599 })
599 })
600 revs.append(_rev)
600 revs.append(_rev)
601 cs_links = [" " + ', '.join(
601 cs_links = [" " + ', '.join(
602 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
602 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
603 )]
603 )]
604 _op1, _name1 = _get_op(revs_ids[0])
604 _op1, _name1 = _get_op(revs_ids[0])
605 _op2, _name2 = _get_op(revs_ids[-1])
605 _op2, _name2 = _get_op(revs_ids[-1])
606
606
607 _rev = '%s...%s' % (_name1, _name2)
607 _rev = '%s...%s' % (_name1, _name2)
608
608
609 compare_view = (
609 compare_view = (
610 ' <div class="compare_view tooltip" title="%s">'
610 ' <div class="compare_view tooltip" title="%s">'
611 '<a href="%s">%s</a> </div>' % (
611 '<a href="%s">%s</a> </div>' % (
612 _('Show all combined changesets %s->%s') % (
612 _('Show all combined changesets %s->%s') % (
613 revs_ids[0][:12], revs_ids[-1][:12]
613 revs_ids[0][:12], revs_ids[-1][:12]
614 ),
614 ),
615 url('changeset_home', repo_name=repo_name,
615 url('changeset_home', repo_name=repo_name,
616 revision=_rev
616 revision=_rev
617 ),
617 ),
618 _('compare view')
618 _('compare view')
619 )
619 )
620 )
620 )
621
621
622 # if we have exactly one more than normally displayed
622 # if we have exactly one more than normally displayed
623 # just display it, takes less space than displaying
623 # just display it, takes less space than displaying
624 # "and 1 more revisions"
624 # "and 1 more revisions"
625 if len(revs_ids) == revs_limit + 1:
625 if len(revs_ids) == revs_limit + 1:
626 rev = revs[revs_limit]
626 rev = revs[revs_limit]
627 cs_links.append(", " + lnk(rev, repo_name))
627 cs_links.append(", " + lnk(rev, repo_name))
628
628
629 # hidden-by-default ones
629 # hidden-by-default ones
630 if len(revs_ids) > revs_limit + 1:
630 if len(revs_ids) > revs_limit + 1:
631 uniq_id = revs_ids[0]
631 uniq_id = revs_ids[0]
632 html_tmpl = (
632 html_tmpl = (
633 '<span> %s <a class="show_more" id="_%s" '
633 '<span> %s <a class="show_more" id="_%s" '
634 'href="#more">%s</a> %s</span>'
634 'href="#more">%s</a> %s</span>'
635 )
635 )
636 if not feed:
636 if not feed:
637 cs_links.append(html_tmpl % (
637 cs_links.append(html_tmpl % (
638 _('and'),
638 _('and'),
639 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
639 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
640 _('revisions')
640 _('revisions')
641 )
641 )
642 )
642 )
643
643
644 if not feed:
644 if not feed:
645 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
645 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
646 else:
646 else:
647 html_tmpl = '<span id="%s"> %s </span>'
647 html_tmpl = '<span id="%s"> %s </span>'
648
648
649 morelinks = ', '.join(
649 morelinks = ', '.join(
650 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
650 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
651 )
651 )
652
652
653 if len(revs_ids) > revs_top_limit:
653 if len(revs_ids) > revs_top_limit:
654 morelinks += ', ...'
654 morelinks += ', ...'
655
655
656 cs_links.append(html_tmpl % (uniq_id, morelinks))
656 cs_links.append(html_tmpl % (uniq_id, morelinks))
657 if len(revs) > 1:
657 if len(revs) > 1:
658 cs_links.append(compare_view)
658 cs_links.append(compare_view)
659 return ''.join(cs_links)
659 return ''.join(cs_links)
660
660
661 def get_fork_name():
661 def get_fork_name():
662 repo_name = action_params
662 repo_name = action_params
663 _url = url('summary_home', repo_name=repo_name)
663 _url = url('summary_home', repo_name=repo_name)
664 return _('fork name %s') % link_to(action_params, _url)
664 return _('fork name %s') % link_to(action_params, _url)
665
665
666 def get_user_name():
666 def get_user_name():
667 user_name = action_params
667 user_name = action_params
668 return user_name
668 return user_name
669
669
670 def get_users_group():
670 def get_users_group():
671 group_name = action_params
671 group_name = action_params
672 return group_name
672 return group_name
673
673
674 def get_pull_request():
674 def get_pull_request():
675 pull_request_id = action_params
675 pull_request_id = action_params
676 deleted = user_log.repository is None
676 deleted = user_log.repository is None
677 if deleted:
677 if deleted:
678 repo_name = user_log.repository_name
678 repo_name = user_log.repository_name
679 else:
679 else:
680 repo_name = user_log.repository.repo_name
680 repo_name = user_log.repository.repo_name
681 return link_to(_('Pull request #%s') % pull_request_id,
681 return link_to(_('Pull request #%s') % pull_request_id,
682 url('pullrequest_show', repo_name=repo_name,
682 url('pullrequest_show', repo_name=repo_name,
683 pull_request_id=pull_request_id))
683 pull_request_id=pull_request_id))
684
684
685 def get_archive_name():
685 def get_archive_name():
686 archive_name = action_params
686 archive_name = action_params
687 return archive_name
687 return archive_name
688
688
689 # action : translated str, callback(extractor), icon
689 # action : translated str, callback(extractor), icon
690 action_map = {
690 action_map = {
691 'user_deleted_repo': (_('[deleted] repository'),
691 'user_deleted_repo': (_('[deleted] repository'),
692 None, 'database_delete.png'),
692 None, 'database_delete.png'),
693 'user_created_repo': (_('[created] repository'),
693 'user_created_repo': (_('[created] repository'),
694 None, 'database_add.png'),
694 None, 'database_add.png'),
695 'user_created_fork': (_('[created] repository as fork'),
695 'user_created_fork': (_('[created] repository as fork'),
696 None, 'arrow_divide.png'),
696 None, 'arrow_divide.png'),
697 'user_forked_repo': (_('[forked] repository'),
697 'user_forked_repo': (_('[forked] repository'),
698 get_fork_name, 'arrow_divide.png'),
698 get_fork_name, 'arrow_divide.png'),
699 'user_updated_repo': (_('[updated] repository'),
699 'user_updated_repo': (_('[updated] repository'),
700 None, 'database_edit.png'),
700 None, 'database_edit.png'),
701 'user_downloaded_archive': (_('[downloaded] archive from repository'),
701 'user_downloaded_archive': (_('[downloaded] archive from repository'),
702 get_archive_name, 'page_white_compressed.png'),
702 get_archive_name, 'page_white_compressed.png'),
703 'admin_deleted_repo': (_('[delete] repository'),
703 'admin_deleted_repo': (_('[delete] repository'),
704 None, 'database_delete.png'),
704 None, 'database_delete.png'),
705 'admin_created_repo': (_('[created] repository'),
705 'admin_created_repo': (_('[created] repository'),
706 None, 'database_add.png'),
706 None, 'database_add.png'),
707 'admin_forked_repo': (_('[forked] repository'),
707 'admin_forked_repo': (_('[forked] repository'),
708 None, 'arrow_divide.png'),
708 None, 'arrow_divide.png'),
709 'admin_updated_repo': (_('[updated] repository'),
709 'admin_updated_repo': (_('[updated] repository'),
710 None, 'database_edit.png'),
710 None, 'database_edit.png'),
711 'admin_created_user': (_('[created] user'),
711 'admin_created_user': (_('[created] user'),
712 get_user_name, 'user_add.png'),
712 get_user_name, 'user_add.png'),
713 'admin_updated_user': (_('[updated] user'),
713 'admin_updated_user': (_('[updated] user'),
714 get_user_name, 'user_edit.png'),
714 get_user_name, 'user_edit.png'),
715 'admin_created_users_group': (_('[created] user group'),
715 'admin_created_users_group': (_('[created] user group'),
716 get_users_group, 'group_add.png'),
716 get_users_group, 'group_add.png'),
717 'admin_updated_users_group': (_('[updated] user group'),
717 'admin_updated_users_group': (_('[updated] user group'),
718 get_users_group, 'group_edit.png'),
718 get_users_group, 'group_edit.png'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
720 get_cs_links, 'comment_add.png'),
720 get_cs_links, 'comment_add.png'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
722 get_pull_request, 'comment_add.png'),
722 get_pull_request, 'comment_add.png'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
724 get_pull_request, 'tick.png'),
724 get_pull_request, 'tick.png'),
725 'push': (_('[pushed] into'),
725 'push': (_('[pushed] into'),
726 get_cs_links, 'script_add.png'),
726 get_cs_links, 'script_add.png'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
728 get_cs_links, 'script_edit.png'),
728 get_cs_links, 'script_edit.png'),
729 'push_remote': (_('[pulled from remote] into repository'),
729 'push_remote': (_('[pulled from remote] into repository'),
730 get_cs_links, 'connect.png'),
730 get_cs_links, 'connect.png'),
731 'pull': (_('[pulled] from'),
731 'pull': (_('[pulled] from'),
732 None, 'down_16.png'),
732 None, 'down_16.png'),
733 'started_following_repo': (_('[started following] repository'),
733 'started_following_repo': (_('[started following] repository'),
734 None, 'heart_add.png'),
734 None, 'heart_add.png'),
735 'stopped_following_repo': (_('[stopped following] repository'),
735 'stopped_following_repo': (_('[stopped following] repository'),
736 None, 'heart_delete.png'),
736 None, 'heart_delete.png'),
737 }
737 }
738
738
739 action_str = action_map.get(action, action)
739 action_str = action_map.get(action, action)
740 if feed:
740 if feed:
741 action = action_str[0].replace('[', '').replace(']', '')
741 action = action_str[0].replace('[', '').replace(']', '')
742 else:
742 else:
743 action = action_str[0]\
743 action = action_str[0]\
744 .replace('[', '<span class="journal_highlight">')\
744 .replace('[', '<span class="journal_highlight">')\
745 .replace(']', '</span>')
745 .replace(']', '</span>')
746
746
747 action_params_func = lambda: ""
747 action_params_func = lambda: ""
748
748
749 if callable(action_str[1]):
749 if callable(action_str[1]):
750 action_params_func = action_str[1]
750 action_params_func = action_str[1]
751
751
752 def action_parser_icon():
752 def action_parser_icon():
753 action = user_log.action
753 action = user_log.action
754 action_params = None
754 action_params = None
755 x = action.split(':')
755 x = action.split(':')
756
756
757 if len(x) > 1:
757 if len(x) > 1:
758 action, action_params = x
758 action, action_params = x
759
759
760 tmpl = """<img src="%s%s" alt="%s"/>"""
760 tmpl = """<img src="%s%s" alt="%s"/>"""
761 ico = action_map.get(action, ['', '', ''])[2]
761 ico = action_map.get(action, ['', '', ''])[2]
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
763
763
764 # returned callbacks we need to call to get
764 # returned callbacks we need to call to get
765 return [lambda: literal(action), action_params_func, action_parser_icon]
765 return [lambda: literal(action), action_params_func, action_parser_icon]
766
766
767
767
768
768
769 #==============================================================================
769 #==============================================================================
770 # PERMS
770 # PERMS
771 #==============================================================================
771 #==============================================================================
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
774 HasReposGroupPermissionAny
774 HasReposGroupPermissionAny
775
775
776
776
777 #==============================================================================
777 #==============================================================================
778 # GRAVATAR URL
778 # GRAVATAR URL
779 #==============================================================================
779 #==============================================================================
780
780
781 def gravatar_url(email_address, size=30):
781 def gravatar_url(email_address, size=30, ssl_enabled=True):
782 from pylons import url # doh, we need to re-import url to mock it later
782 from pylons import url # doh, we need to re-import url to mock it later
783 _def = 'anonymous@rhodecode.org'
783 from rhodecode import CONFIG
784 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
784
785 _def = 'anonymous@rhodecode.org' # default gravatar
786 use_gravatar = str2bool(CONFIG.get('use_gravatar'))
787 alternative_gravatar_url = CONFIG.get('alternative_gravatar_url', '')
785 email_address = email_address or _def
788 email_address = email_address or _def
786 if (not use_gravatar or not email_address or email_address == _def):
789 if not use_gravatar or not email_address or email_address == _def:
787 f = lambda a, l: min(l, key=lambda x: abs(x - a))
790 f = lambda a, l: min(l, key=lambda x: abs(x - a))
788 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
791 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
789
792
790 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
793 if use_gravatar and alternative_gravatar_url:
791 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
794 tmpl = alternative_gravatar_url
792 parsed_url = urlparse.urlparse(url.current(qualified=True))
795 parsed_url = urlparse.urlparse(url.current(qualified=True))
793 tmpl = tmpl.replace('{email}', email_address)\
796 tmpl = tmpl.replace('{email}', email_address)\
794 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
797 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
795 .replace('{netloc}', parsed_url.netloc)\
798 .replace('{netloc}', parsed_url.netloc)\
796 .replace('{scheme}', parsed_url.scheme)\
799 .replace('{scheme}', parsed_url.scheme)\
797 .replace('{size}', str(size))
800 .replace('{size}', str(size))
798 return tmpl
801 return tmpl
799
802
800 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
801 default = 'identicon'
803 default = 'identicon'
802 baseurl_nossl = "http://www.gravatar.com/avatar/"
804 baseurl_nossl = "http://www.gravatar.com/avatar/"
803 baseurl_ssl = "https://secure.gravatar.com/avatar/"
805 baseurl_ssl = "https://secure.gravatar.com/avatar/"
804 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
806 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
805
807
806 if isinstance(email_address, unicode):
808 if isinstance(email_address, unicode):
807 #hashlib crashes on unicode items
809 #hashlib crashes on unicode items
808 email_address = safe_str(email_address)
810 email_address = safe_str(email_address)
809 # construct the url
811 # construct the url
810 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
812 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
811 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
813 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
812
814
813 return gravatar_url
815 return gravatar_url
814
816
815
817
816 class Page(_Page):
818 class Page(_Page):
817 """
819 """
818 Custom pager to match rendering style with YUI paginator
820 Custom pager to match rendering style with YUI paginator
819 """
821 """
820
822
821 def _get_pos(self, cur_page, max_page, items):
823 def _get_pos(self, cur_page, max_page, items):
822 edge = (items / 2) + 1
824 edge = (items / 2) + 1
823 if (cur_page <= edge):
825 if (cur_page <= edge):
824 radius = max(items / 2, items - cur_page)
826 radius = max(items / 2, items - cur_page)
825 elif (max_page - cur_page) < edge:
827 elif (max_page - cur_page) < edge:
826 radius = (items - 1) - (max_page - cur_page)
828 radius = (items - 1) - (max_page - cur_page)
827 else:
829 else:
828 radius = items / 2
830 radius = items / 2
829
831
830 left = max(1, (cur_page - (radius)))
832 left = max(1, (cur_page - (radius)))
831 right = min(max_page, cur_page + (radius))
833 right = min(max_page, cur_page + (radius))
832 return left, cur_page, right
834 return left, cur_page, right
833
835
834 def _range(self, regexp_match):
836 def _range(self, regexp_match):
835 """
837 """
836 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
838 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
837
839
838 Arguments:
840 Arguments:
839
841
840 regexp_match
842 regexp_match
841 A "re" (regular expressions) match object containing the
843 A "re" (regular expressions) match object containing the
842 radius of linked pages around the current page in
844 radius of linked pages around the current page in
843 regexp_match.group(1) as a string
845 regexp_match.group(1) as a string
844
846
845 This function is supposed to be called as a callable in
847 This function is supposed to be called as a callable in
846 re.sub.
848 re.sub.
847
849
848 """
850 """
849 radius = int(regexp_match.group(1))
851 radius = int(regexp_match.group(1))
850
852
851 # Compute the first and last page number within the radius
853 # Compute the first and last page number within the radius
852 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
854 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
853 # -> leftmost_page = 5
855 # -> leftmost_page = 5
854 # -> rightmost_page = 9
856 # -> rightmost_page = 9
855 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
857 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
856 self.last_page,
858 self.last_page,
857 (radius * 2) + 1)
859 (radius * 2) + 1)
858 nav_items = []
860 nav_items = []
859
861
860 # Create a link to the first page (unless we are on the first page
862 # Create a link to the first page (unless we are on the first page
861 # or there would be no need to insert '..' spacers)
863 # or there would be no need to insert '..' spacers)
862 if self.page != self.first_page and self.first_page < leftmost_page:
864 if self.page != self.first_page and self.first_page < leftmost_page:
863 nav_items.append(self._pagerlink(self.first_page, self.first_page))
865 nav_items.append(self._pagerlink(self.first_page, self.first_page))
864
866
865 # Insert dots if there are pages between the first page
867 # Insert dots if there are pages between the first page
866 # and the currently displayed page range
868 # and the currently displayed page range
867 if leftmost_page - self.first_page > 1:
869 if leftmost_page - self.first_page > 1:
868 # Wrap in a SPAN tag if nolink_attr is set
870 # Wrap in a SPAN tag if nolink_attr is set
869 text = '..'
871 text = '..'
870 if self.dotdot_attr:
872 if self.dotdot_attr:
871 text = HTML.span(c=text, **self.dotdot_attr)
873 text = HTML.span(c=text, **self.dotdot_attr)
872 nav_items.append(text)
874 nav_items.append(text)
873
875
874 for thispage in xrange(leftmost_page, rightmost_page + 1):
876 for thispage in xrange(leftmost_page, rightmost_page + 1):
875 # Hilight the current page number and do not use a link
877 # Hilight the current page number and do not use a link
876 if thispage == self.page:
878 if thispage == self.page:
877 text = '%s' % (thispage,)
879 text = '%s' % (thispage,)
878 # Wrap in a SPAN tag if nolink_attr is set
880 # Wrap in a SPAN tag if nolink_attr is set
879 if self.curpage_attr:
881 if self.curpage_attr:
880 text = HTML.span(c=text, **self.curpage_attr)
882 text = HTML.span(c=text, **self.curpage_attr)
881 nav_items.append(text)
883 nav_items.append(text)
882 # Otherwise create just a link to that page
884 # Otherwise create just a link to that page
883 else:
885 else:
884 text = '%s' % (thispage,)
886 text = '%s' % (thispage,)
885 nav_items.append(self._pagerlink(thispage, text))
887 nav_items.append(self._pagerlink(thispage, text))
886
888
887 # Insert dots if there are pages between the displayed
889 # Insert dots if there are pages between the displayed
888 # page numbers and the end of the page range
890 # page numbers and the end of the page range
889 if self.last_page - rightmost_page > 1:
891 if self.last_page - rightmost_page > 1:
890 text = '..'
892 text = '..'
891 # Wrap in a SPAN tag if nolink_attr is set
893 # Wrap in a SPAN tag if nolink_attr is set
892 if self.dotdot_attr:
894 if self.dotdot_attr:
893 text = HTML.span(c=text, **self.dotdot_attr)
895 text = HTML.span(c=text, **self.dotdot_attr)
894 nav_items.append(text)
896 nav_items.append(text)
895
897
896 # Create a link to the very last page (unless we are on the last
898 # Create a link to the very last page (unless we are on the last
897 # page or there would be no need to insert '..' spacers)
899 # page or there would be no need to insert '..' spacers)
898 if self.page != self.last_page and rightmost_page < self.last_page:
900 if self.page != self.last_page and rightmost_page < self.last_page:
899 nav_items.append(self._pagerlink(self.last_page, self.last_page))
901 nav_items.append(self._pagerlink(self.last_page, self.last_page))
900
902
901 return self.separator.join(nav_items)
903 return self.separator.join(nav_items)
902
904
903 def pager(self, format='~2~', page_param='page', partial_param='partial',
905 def pager(self, format='~2~', page_param='page', partial_param='partial',
904 show_if_single_page=False, separator=' ', onclick=None,
906 show_if_single_page=False, separator=' ', onclick=None,
905 symbol_first='<<', symbol_last='>>',
907 symbol_first='<<', symbol_last='>>',
906 symbol_previous='<', symbol_next='>',
908 symbol_previous='<', symbol_next='>',
907 link_attr={'class': 'pager_link'},
909 link_attr={'class': 'pager_link'},
908 curpage_attr={'class': 'pager_curpage'},
910 curpage_attr={'class': 'pager_curpage'},
909 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
911 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
910
912
911 self.curpage_attr = curpage_attr
913 self.curpage_attr = curpage_attr
912 self.separator = separator
914 self.separator = separator
913 self.pager_kwargs = kwargs
915 self.pager_kwargs = kwargs
914 self.page_param = page_param
916 self.page_param = page_param
915 self.partial_param = partial_param
917 self.partial_param = partial_param
916 self.onclick = onclick
918 self.onclick = onclick
917 self.link_attr = link_attr
919 self.link_attr = link_attr
918 self.dotdot_attr = dotdot_attr
920 self.dotdot_attr = dotdot_attr
919
921
920 # Don't show navigator if there is no more than one page
922 # Don't show navigator if there is no more than one page
921 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
923 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
922 return ''
924 return ''
923
925
924 from string import Template
926 from string import Template
925 # Replace ~...~ in token format by range of pages
927 # Replace ~...~ in token format by range of pages
926 result = re.sub(r'~(\d+)~', self._range, format)
928 result = re.sub(r'~(\d+)~', self._range, format)
927
929
928 # Interpolate '%' variables
930 # Interpolate '%' variables
929 result = Template(result).safe_substitute({
931 result = Template(result).safe_substitute({
930 'first_page': self.first_page,
932 'first_page': self.first_page,
931 'last_page': self.last_page,
933 'last_page': self.last_page,
932 'page': self.page,
934 'page': self.page,
933 'page_count': self.page_count,
935 'page_count': self.page_count,
934 'items_per_page': self.items_per_page,
936 'items_per_page': self.items_per_page,
935 'first_item': self.first_item,
937 'first_item': self.first_item,
936 'last_item': self.last_item,
938 'last_item': self.last_item,
937 'item_count': self.item_count,
939 'item_count': self.item_count,
938 'link_first': self.page > self.first_page and \
940 'link_first': self.page > self.first_page and \
939 self._pagerlink(self.first_page, symbol_first) or '',
941 self._pagerlink(self.first_page, symbol_first) or '',
940 'link_last': self.page < self.last_page and \
942 'link_last': self.page < self.last_page and \
941 self._pagerlink(self.last_page, symbol_last) or '',
943 self._pagerlink(self.last_page, symbol_last) or '',
942 'link_previous': self.previous_page and \
944 'link_previous': self.previous_page and \
943 self._pagerlink(self.previous_page, symbol_previous) \
945 self._pagerlink(self.previous_page, symbol_previous) \
944 or HTML.span(symbol_previous, class_="yui-pg-previous"),
946 or HTML.span(symbol_previous, class_="yui-pg-previous"),
945 'link_next': self.next_page and \
947 'link_next': self.next_page and \
946 self._pagerlink(self.next_page, symbol_next) \
948 self._pagerlink(self.next_page, symbol_next) \
947 or HTML.span(symbol_next, class_="yui-pg-next")
949 or HTML.span(symbol_next, class_="yui-pg-next")
948 })
950 })
949
951
950 return literal(result)
952 return literal(result)
951
953
952
954
953 #==============================================================================
955 #==============================================================================
954 # REPO PAGER, PAGER FOR REPOSITORY
956 # REPO PAGER, PAGER FOR REPOSITORY
955 #==============================================================================
957 #==============================================================================
956 class RepoPage(Page):
958 class RepoPage(Page):
957
959
958 def __init__(self, collection, page=1, items_per_page=20,
960 def __init__(self, collection, page=1, items_per_page=20,
959 item_count=None, url=None, **kwargs):
961 item_count=None, url=None, **kwargs):
960
962
961 """Create a "RepoPage" instance. special pager for paging
963 """Create a "RepoPage" instance. special pager for paging
962 repository
964 repository
963 """
965 """
964 self._url_generator = url
966 self._url_generator = url
965
967
966 # Safe the kwargs class-wide so they can be used in the pager() method
968 # Safe the kwargs class-wide so they can be used in the pager() method
967 self.kwargs = kwargs
969 self.kwargs = kwargs
968
970
969 # Save a reference to the collection
971 # Save a reference to the collection
970 self.original_collection = collection
972 self.original_collection = collection
971
973
972 self.collection = collection
974 self.collection = collection
973
975
974 # The self.page is the number of the current page.
976 # The self.page is the number of the current page.
975 # The first page has the number 1!
977 # The first page has the number 1!
976 try:
978 try:
977 self.page = int(page) # make it int() if we get it as a string
979 self.page = int(page) # make it int() if we get it as a string
978 except (ValueError, TypeError):
980 except (ValueError, TypeError):
979 self.page = 1
981 self.page = 1
980
982
981 self.items_per_page = items_per_page
983 self.items_per_page = items_per_page
982
984
983 # Unless the user tells us how many items the collections has
985 # Unless the user tells us how many items the collections has
984 # we calculate that ourselves.
986 # we calculate that ourselves.
985 if item_count is not None:
987 if item_count is not None:
986 self.item_count = item_count
988 self.item_count = item_count
987 else:
989 else:
988 self.item_count = len(self.collection)
990 self.item_count = len(self.collection)
989
991
990 # Compute the number of the first and last available page
992 # Compute the number of the first and last available page
991 if self.item_count > 0:
993 if self.item_count > 0:
992 self.first_page = 1
994 self.first_page = 1
993 self.page_count = int(math.ceil(float(self.item_count) /
995 self.page_count = int(math.ceil(float(self.item_count) /
994 self.items_per_page))
996 self.items_per_page))
995 self.last_page = self.first_page + self.page_count - 1
997 self.last_page = self.first_page + self.page_count - 1
996
998
997 # Make sure that the requested page number is the range of
999 # Make sure that the requested page number is the range of
998 # valid pages
1000 # valid pages
999 if self.page > self.last_page:
1001 if self.page > self.last_page:
1000 self.page = self.last_page
1002 self.page = self.last_page
1001 elif self.page < self.first_page:
1003 elif self.page < self.first_page:
1002 self.page = self.first_page
1004 self.page = self.first_page
1003
1005
1004 # Note: the number of items on this page can be less than
1006 # Note: the number of items on this page can be less than
1005 # items_per_page if the last page is not full
1007 # items_per_page if the last page is not full
1006 self.first_item = max(0, (self.item_count) - (self.page *
1008 self.first_item = max(0, (self.item_count) - (self.page *
1007 items_per_page))
1009 items_per_page))
1008 self.last_item = ((self.item_count - 1) - items_per_page *
1010 self.last_item = ((self.item_count - 1) - items_per_page *
1009 (self.page - 1))
1011 (self.page - 1))
1010
1012
1011 self.items = list(self.collection[self.first_item:self.last_item + 1])
1013 self.items = list(self.collection[self.first_item:self.last_item + 1])
1012
1014
1013 # Links to previous and next page
1015 # Links to previous and next page
1014 if self.page > self.first_page:
1016 if self.page > self.first_page:
1015 self.previous_page = self.page - 1
1017 self.previous_page = self.page - 1
1016 else:
1018 else:
1017 self.previous_page = None
1019 self.previous_page = None
1018
1020
1019 if self.page < self.last_page:
1021 if self.page < self.last_page:
1020 self.next_page = self.page + 1
1022 self.next_page = self.page + 1
1021 else:
1023 else:
1022 self.next_page = None
1024 self.next_page = None
1023
1025
1024 # No items available
1026 # No items available
1025 else:
1027 else:
1026 self.first_page = None
1028 self.first_page = None
1027 self.page_count = 0
1029 self.page_count = 0
1028 self.last_page = None
1030 self.last_page = None
1029 self.first_item = None
1031 self.first_item = None
1030 self.last_item = None
1032 self.last_item = None
1031 self.previous_page = None
1033 self.previous_page = None
1032 self.next_page = None
1034 self.next_page = None
1033 self.items = []
1035 self.items = []
1034
1036
1035 # This is a subclass of the 'list' type. Initialise the list now.
1037 # This is a subclass of the 'list' type. Initialise the list now.
1036 list.__init__(self, reversed(self.items))
1038 list.__init__(self, reversed(self.items))
1037
1039
1038
1040
1039 def changed_tooltip(nodes):
1041 def changed_tooltip(nodes):
1040 """
1042 """
1041 Generates a html string for changed nodes in changeset page.
1043 Generates a html string for changed nodes in changeset page.
1042 It limits the output to 30 entries
1044 It limits the output to 30 entries
1043
1045
1044 :param nodes: LazyNodesGenerator
1046 :param nodes: LazyNodesGenerator
1045 """
1047 """
1046 if nodes:
1048 if nodes:
1047 pref = ': <br/> '
1049 pref = ': <br/> '
1048 suf = ''
1050 suf = ''
1049 if len(nodes) > 30:
1051 if len(nodes) > 30:
1050 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1052 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1051 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1053 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1052 for x in nodes[:30]]) + suf)
1054 for x in nodes[:30]]) + suf)
1053 else:
1055 else:
1054 return ': ' + _('No Files')
1056 return ': ' + _('No Files')
1055
1057
1056
1058
1057 def repo_link(groups_and_repos):
1059 def repo_link(groups_and_repos):
1058 """
1060 """
1059 Makes a breadcrumbs link to repo within a group
1061 Makes a breadcrumbs link to repo within a group
1060 joins &raquo; on each group to create a fancy link
1062 joins &raquo; on each group to create a fancy link
1061
1063
1062 ex::
1064 ex::
1063 group >> subgroup >> repo
1065 group >> subgroup >> repo
1064
1066
1065 :param groups_and_repos:
1067 :param groups_and_repos:
1066 :param last_url:
1068 :param last_url:
1067 """
1069 """
1068 groups, just_name, repo_name = groups_and_repos
1070 groups, just_name, repo_name = groups_and_repos
1069 last_url = url('summary_home', repo_name=repo_name)
1071 last_url = url('summary_home', repo_name=repo_name)
1070 last_link = link_to(just_name, last_url)
1072 last_link = link_to(just_name, last_url)
1071
1073
1072 def make_link(group):
1074 def make_link(group):
1073 return link_to(group.name,
1075 return link_to(group.name,
1074 url('repos_group_home', group_name=group.group_name))
1076 url('repos_group_home', group_name=group.group_name))
1075 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1077 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
1076
1078
1077
1079
1078 def fancy_file_stats(stats):
1080 def fancy_file_stats(stats):
1079 """
1081 """
1080 Displays a fancy two colored bar for number of added/deleted
1082 Displays a fancy two colored bar for number of added/deleted
1081 lines of code on file
1083 lines of code on file
1082
1084
1083 :param stats: two element list of added/deleted lines of code
1085 :param stats: two element list of added/deleted lines of code
1084 """
1086 """
1085 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1087 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1086 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1088 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1087
1089
1088 def cgen(l_type, a_v, d_v):
1090 def cgen(l_type, a_v, d_v):
1089 mapping = {'tr': 'top-right-rounded-corner-mid',
1091 mapping = {'tr': 'top-right-rounded-corner-mid',
1090 'tl': 'top-left-rounded-corner-mid',
1092 'tl': 'top-left-rounded-corner-mid',
1091 'br': 'bottom-right-rounded-corner-mid',
1093 'br': 'bottom-right-rounded-corner-mid',
1092 'bl': 'bottom-left-rounded-corner-mid'}
1094 'bl': 'bottom-left-rounded-corner-mid'}
1093 map_getter = lambda x: mapping[x]
1095 map_getter = lambda x: mapping[x]
1094
1096
1095 if l_type == 'a' and d_v:
1097 if l_type == 'a' and d_v:
1096 #case when added and deleted are present
1098 #case when added and deleted are present
1097 return ' '.join(map(map_getter, ['tl', 'bl']))
1099 return ' '.join(map(map_getter, ['tl', 'bl']))
1098
1100
1099 if l_type == 'a' and not d_v:
1101 if l_type == 'a' and not d_v:
1100 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1102 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1101
1103
1102 if l_type == 'd' and a_v:
1104 if l_type == 'd' and a_v:
1103 return ' '.join(map(map_getter, ['tr', 'br']))
1105 return ' '.join(map(map_getter, ['tr', 'br']))
1104
1106
1105 if l_type == 'd' and not a_v:
1107 if l_type == 'd' and not a_v:
1106 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1108 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1107
1109
1108 a, d = stats['added'], stats['deleted']
1110 a, d = stats['added'], stats['deleted']
1109 width = 100
1111 width = 100
1110
1112
1111 if stats['binary']:
1113 if stats['binary']:
1112 #binary mode
1114 #binary mode
1113 lbl = ''
1115 lbl = ''
1114 bin_op = 1
1116 bin_op = 1
1115
1117
1116 if BIN_FILENODE in stats['ops']:
1118 if BIN_FILENODE in stats['ops']:
1117 lbl = 'bin+'
1119 lbl = 'bin+'
1118
1120
1119 if NEW_FILENODE in stats['ops']:
1121 if NEW_FILENODE in stats['ops']:
1120 lbl += _('new file')
1122 lbl += _('new file')
1121 bin_op = NEW_FILENODE
1123 bin_op = NEW_FILENODE
1122 elif MOD_FILENODE in stats['ops']:
1124 elif MOD_FILENODE in stats['ops']:
1123 lbl += _('mod')
1125 lbl += _('mod')
1124 bin_op = MOD_FILENODE
1126 bin_op = MOD_FILENODE
1125 elif DEL_FILENODE in stats['ops']:
1127 elif DEL_FILENODE in stats['ops']:
1126 lbl += _('del')
1128 lbl += _('del')
1127 bin_op = DEL_FILENODE
1129 bin_op = DEL_FILENODE
1128 elif RENAMED_FILENODE in stats['ops']:
1130 elif RENAMED_FILENODE in stats['ops']:
1129 lbl += _('rename')
1131 lbl += _('rename')
1130 bin_op = RENAMED_FILENODE
1132 bin_op = RENAMED_FILENODE
1131
1133
1132 #chmod can go with other operations
1134 #chmod can go with other operations
1133 if CHMOD_FILENODE in stats['ops']:
1135 if CHMOD_FILENODE in stats['ops']:
1134 _org_lbl = _('chmod')
1136 _org_lbl = _('chmod')
1135 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1137 lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
1136
1138
1137 #import ipdb;ipdb.set_trace()
1139 #import ipdb;ipdb.set_trace()
1138 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1140 b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1139 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1141 b_a = '<div class="bin bin1" style="width:0%%"></div>'
1140 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1142 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1141
1143
1142 t = stats['added'] + stats['deleted']
1144 t = stats['added'] + stats['deleted']
1143 unit = float(width) / (t or 1)
1145 unit = float(width) / (t or 1)
1144
1146
1145 # needs > 9% of width to be visible or 0 to be hidden
1147 # needs > 9% of width to be visible or 0 to be hidden
1146 a_p = max(9, unit * a) if a > 0 else 0
1148 a_p = max(9, unit * a) if a > 0 else 0
1147 d_p = max(9, unit * d) if d > 0 else 0
1149 d_p = max(9, unit * d) if d > 0 else 0
1148 p_sum = a_p + d_p
1150 p_sum = a_p + d_p
1149
1151
1150 if p_sum > width:
1152 if p_sum > width:
1151 #adjust the percentage to be == 100% since we adjusted to 9
1153 #adjust the percentage to be == 100% since we adjusted to 9
1152 if a_p > d_p:
1154 if a_p > d_p:
1153 a_p = a_p - (p_sum - width)
1155 a_p = a_p - (p_sum - width)
1154 else:
1156 else:
1155 d_p = d_p - (p_sum - width)
1157 d_p = d_p - (p_sum - width)
1156
1158
1157 a_v = a if a > 0 else ''
1159 a_v = a if a > 0 else ''
1158 d_v = d if d > 0 else ''
1160 d_v = d if d > 0 else ''
1159
1161
1160 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1162 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1161 cgen('a', a_v, d_v), a_p, a_v
1163 cgen('a', a_v, d_v), a_p, a_v
1162 )
1164 )
1163 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1165 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1164 cgen('d', a_v, d_v), d_p, d_v
1166 cgen('d', a_v, d_v), d_p, d_v
1165 )
1167 )
1166 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1168 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1167
1169
1168
1170
1169 def urlify_text(text_, safe=True):
1171 def urlify_text(text_, safe=True):
1170 """
1172 """
1171 Extrac urls from text and make html links out of them
1173 Extrac urls from text and make html links out of them
1172
1174
1173 :param text_:
1175 :param text_:
1174 """
1176 """
1175
1177
1176 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1178 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1177 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1179 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1178
1180
1179 def url_func(match_obj):
1181 def url_func(match_obj):
1180 url_full = match_obj.groups()[0]
1182 url_full = match_obj.groups()[0]
1181 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1183 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1182 _newtext = url_pat.sub(url_func, text_)
1184 _newtext = url_pat.sub(url_func, text_)
1183 if safe:
1185 if safe:
1184 return literal(_newtext)
1186 return literal(_newtext)
1185 return _newtext
1187 return _newtext
1186
1188
1187
1189
1188 def urlify_changesets(text_, repository):
1190 def urlify_changesets(text_, repository):
1189 """
1191 """
1190 Extract revision ids from changeset and make link from them
1192 Extract revision ids from changeset and make link from them
1191
1193
1192 :param text_:
1194 :param text_:
1193 :param repository: repo name to build the URL with
1195 :param repository: repo name to build the URL with
1194 """
1196 """
1195 from pylons import url # doh, we need to re-import url to mock it later
1197 from pylons import url # doh, we need to re-import url to mock it later
1196 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1198 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1197
1199
1198 def url_func(match_obj):
1200 def url_func(match_obj):
1199 rev = match_obj.groups()[1]
1201 rev = match_obj.groups()[1]
1200 pref = match_obj.groups()[0]
1202 pref = match_obj.groups()[0]
1201 suf = match_obj.groups()[2]
1203 suf = match_obj.groups()[2]
1202
1204
1203 tmpl = (
1205 tmpl = (
1204 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1206 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1205 '%(rev)s</a>%(suf)s'
1207 '%(rev)s</a>%(suf)s'
1206 )
1208 )
1207 return tmpl % {
1209 return tmpl % {
1208 'pref': pref,
1210 'pref': pref,
1209 'cls': 'revision-link',
1211 'cls': 'revision-link',
1210 'url': url('changeset_home', repo_name=repository, revision=rev),
1212 'url': url('changeset_home', repo_name=repository, revision=rev),
1211 'rev': rev,
1213 'rev': rev,
1212 'suf': suf
1214 'suf': suf
1213 }
1215 }
1214
1216
1215 newtext = URL_PAT.sub(url_func, text_)
1217 newtext = URL_PAT.sub(url_func, text_)
1216
1218
1217 return newtext
1219 return newtext
1218
1220
1219
1221
1220 def urlify_commit(text_, repository=None, link_=None):
1222 def urlify_commit(text_, repository=None, link_=None):
1221 """
1223 """
1222 Parses given text message and makes proper links.
1224 Parses given text message and makes proper links.
1223 issues are linked to given issue-server, and rest is a changeset link
1225 issues are linked to given issue-server, and rest is a changeset link
1224 if link_ is given, in other case it's a plain text
1226 if link_ is given, in other case it's a plain text
1225
1227
1226 :param text_:
1228 :param text_:
1227 :param repository:
1229 :param repository:
1228 :param link_: changeset link
1230 :param link_: changeset link
1229 """
1231 """
1230 import traceback
1232 import traceback
1231 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
1232
1234
1233 def escaper(string):
1235 def escaper(string):
1234 return string.replace('<', '&lt;').replace('>', '&gt;')
1236 return string.replace('<', '&lt;').replace('>', '&gt;')
1235
1237
1236 def linkify_others(t, l):
1238 def linkify_others(t, l):
1237 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1239 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1238 links = []
1240 links = []
1239 for e in urls.split(t):
1241 for e in urls.split(t):
1240 if not urls.match(e):
1242 if not urls.match(e):
1241 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1243 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1242 else:
1244 else:
1243 links.append(e)
1245 links.append(e)
1244
1246
1245 return ''.join(links)
1247 return ''.join(links)
1246
1248
1247 # urlify changesets - extrac revisions and make link out of them
1249 # urlify changesets - extrac revisions and make link out of them
1248 newtext = urlify_changesets(escaper(text_), repository)
1250 newtext = urlify_changesets(escaper(text_), repository)
1249
1251
1250 # extract http/https links and make them real urls
1252 # extract http/https links and make them real urls
1251 newtext = urlify_text(newtext, safe=False)
1253 newtext = urlify_text(newtext, safe=False)
1252
1254
1253 try:
1255 try:
1254 from rhodecode import CONFIG
1256 from rhodecode import CONFIG
1255 conf = CONFIG
1257 conf = CONFIG
1256
1258
1257 # allow multiple issue servers to be used
1259 # allow multiple issue servers to be used
1258 valid_indices = [
1260 valid_indices = [
1259 x.group(1)
1261 x.group(1)
1260 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1262 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1261 if x and 'issue_server_link%s' % x.group(1) in conf
1263 if x and 'issue_server_link%s' % x.group(1) in conf
1262 and 'issue_prefix%s' % x.group(1) in conf
1264 and 'issue_prefix%s' % x.group(1) in conf
1263 ]
1265 ]
1264
1266
1265 log.debug('found issue server suffixes `%s` during valuation of: %s'
1267 log.debug('found issue server suffixes `%s` during valuation of: %s'
1266 % (','.join(valid_indices), newtext))
1268 % (','.join(valid_indices), newtext))
1267
1269
1268 for pattern_index in valid_indices:
1270 for pattern_index in valid_indices:
1269 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1271 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1270 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1272 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1271 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1273 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1272
1274
1273 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1275 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1274 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1276 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1275 ISSUE_PREFIX))
1277 ISSUE_PREFIX))
1276
1278
1277 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1279 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1278
1280
1279 def url_func(match_obj):
1281 def url_func(match_obj):
1280 pref = ''
1282 pref = ''
1281 if match_obj.group().startswith(' '):
1283 if match_obj.group().startswith(' '):
1282 pref = ' '
1284 pref = ' '
1283
1285
1284 issue_id = ''.join(match_obj.groups())
1286 issue_id = ''.join(match_obj.groups())
1285 tmpl = (
1287 tmpl = (
1286 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1288 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1287 '%(issue-prefix)s%(id-repr)s'
1289 '%(issue-prefix)s%(id-repr)s'
1288 '</a>'
1290 '</a>'
1289 )
1291 )
1290 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1292 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1291 if repository:
1293 if repository:
1292 url = url.replace('{repo}', repository)
1294 url = url.replace('{repo}', repository)
1293 repo_name = repository.split(URL_SEP)[-1]
1295 repo_name = repository.split(URL_SEP)[-1]
1294 url = url.replace('{repo_name}', repo_name)
1296 url = url.replace('{repo_name}', repo_name)
1295
1297
1296 return tmpl % {
1298 return tmpl % {
1297 'pref': pref,
1299 'pref': pref,
1298 'cls': 'issue-tracker-link',
1300 'cls': 'issue-tracker-link',
1299 'url': url,
1301 'url': url,
1300 'id-repr': issue_id,
1302 'id-repr': issue_id,
1301 'issue-prefix': ISSUE_PREFIX,
1303 'issue-prefix': ISSUE_PREFIX,
1302 'serv': ISSUE_SERVER_LNK,
1304 'serv': ISSUE_SERVER_LNK,
1303 }
1305 }
1304 newtext = URL_PAT.sub(url_func, newtext)
1306 newtext = URL_PAT.sub(url_func, newtext)
1305 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1307 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1306
1308
1307 # if we actually did something above
1309 # if we actually did something above
1308 if link_:
1310 if link_:
1309 # wrap not links into final link => link_
1311 # wrap not links into final link => link_
1310 newtext = linkify_others(newtext, link_)
1312 newtext = linkify_others(newtext, link_)
1311 except Exception:
1313 except Exception:
1312 log.error(traceback.format_exc())
1314 log.error(traceback.format_exc())
1313 pass
1315 pass
1314
1316
1315 return literal(newtext)
1317 return literal(newtext)
1316
1318
1317
1319
1318 def rst(source):
1320 def rst(source):
1319 return literal('<div class="rst-block">%s</div>' %
1321 return literal('<div class="rst-block">%s</div>' %
1320 MarkupRenderer.rst(source))
1322 MarkupRenderer.rst(source))
1321
1323
1322
1324
1323 def rst_w_mentions(source):
1325 def rst_w_mentions(source):
1324 """
1326 """
1325 Wrapped rst renderer with @mention highlighting
1327 Wrapped rst renderer with @mention highlighting
1326
1328
1327 :param source:
1329 :param source:
1328 """
1330 """
1329 return literal('<div class="rst-block">%s</div>' %
1331 return literal('<div class="rst-block">%s</div>' %
1330 MarkupRenderer.rst_with_mentions(source))
1332 MarkupRenderer.rst_with_mentions(source))
1331
1333
1332
1334
1333 def changeset_status(repo, revision):
1335 def changeset_status(repo, revision):
1334 return ChangesetStatusModel().get_status(repo, revision)
1336 return ChangesetStatusModel().get_status(repo, revision)
1335
1337
1336
1338
1337 def changeset_status_lbl(changeset_status):
1339 def changeset_status_lbl(changeset_status):
1338 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1340 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1339
1341
1340
1342
1341 def get_permission_name(key):
1343 def get_permission_name(key):
1342 return dict(Permission.PERMS).get(key)
1344 return dict(Permission.PERMS).get(key)
1343
1345
1344
1346
1345 def journal_filter_help():
1347 def journal_filter_help():
1346 return _(textwrap.dedent('''
1348 return _(textwrap.dedent('''
1347 Example filter terms:
1349 Example filter terms:
1348 repository:vcs
1350 repository:vcs
1349 username:marcin
1351 username:marcin
1350 action:*push*
1352 action:*push*
1351 ip:127.0.0.1
1353 ip:127.0.0.1
1352 date:20120101
1354 date:20120101
1353 date:[20120101100000 TO 20120102]
1355 date:[20120101100000 TO 20120102]
1354
1356
1355 Generate wildcards using '*' character:
1357 Generate wildcards using '*' character:
1356 "repositroy:vcs*" - search everything starting with 'vcs'
1358 "repositroy:vcs*" - search everything starting with 'vcs'
1357 "repository:*vcs*" - search for repository containing 'vcs'
1359 "repository:*vcs*" - search for repository containing 'vcs'
1358
1360
1359 Optional AND / OR operators in queries
1361 Optional AND / OR operators in queries
1360 "repository:vcs OR repository:test"
1362 "repository:vcs OR repository:test"
1361 "username:test AND repository:test*"
1363 "username:test AND repository:test*"
1362 '''))
1364 '''))
1363
1365
1364
1366
1365 def not_mapped_error(repo_name):
1367 def not_mapped_error(repo_name):
1366 flash(_('%s repository is not mapped to db perhaps'
1368 flash(_('%s repository is not mapped to db perhaps'
1367 ' it was created or renamed from the filesystem'
1369 ' it was created or renamed from the filesystem'
1368 ' please run the application again'
1370 ' please run the application again'
1369 ' in order to rescan repositories') % repo_name, category='error')
1371 ' in order to rescan repositories') % repo_name, category='error')
1370
1372
1371
1373
1372 def ip_range(ip_addr):
1374 def ip_range(ip_addr):
1373 from rhodecode.model.db import UserIpMap
1375 from rhodecode.model.db import UserIpMap
1374 s, e = UserIpMap._get_ip_range(ip_addr)
1376 s, e = UserIpMap._get_ip_range(ip_addr)
1375 return '%s - %s' % (s, e)
1377 return '%s - %s' % (s, e)
General Comments 0
You need to be logged in to leave comments. Login now