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