##// END OF EJS Templates
removed colorsys import
marcink -
r1461:e7d9d543 beta
parent child Browse files
Show More
@@ -1,673 +1,688
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
11
12 from datetime import datetime
12 from datetime import datetime
13 from pygments.formatters import HtmlFormatter
13 from pygments.formatters import HtmlFormatter
14 from pygments import highlight as code_highlight
14 from pygments import highlight as code_highlight
15 from pylons import url, request, config
15 from pylons import url, request, config
16 from pylons.i18n.translation import _, ungettext
16 from pylons.i18n.translation import _, ungettext
17
17
18 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html import literal, HTML, escape
19 from webhelpers.html.tools import *
19 from webhelpers.html.tools import *
20 from webhelpers.html.builder import make_tag
20 from webhelpers.html.builder import make_tag
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 password, textarea, title, ul, xml_declaration, radio
24 password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 mail_to, strip_links, strip_tags, tag_re
26 mail_to, strip_links, strip_tags, tag_re
27 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.number import format_byte_size, format_bit_size
28 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib import Flash as _Flash
29 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.pylonslib.secure_form import secure_form
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
32 replace_whitespace, urlify, truncate, wrap_paragraphs
32 replace_whitespace, urlify, truncate, wrap_paragraphs
33 from webhelpers.date import time_ago_in_words
33 from webhelpers.date import time_ago_in_words
34 from webhelpers.paginate import Page
34 from webhelpers.paginate import Page
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 convert_boolean_attrs, NotGiven
36 convert_boolean_attrs, NotGiven
37
37
38 from vcs.utils.annotate import annotate_highlight
38 from vcs.utils.annotate import annotate_highlight
39 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib import str2bool, safe_unicode, safe_str
40 from rhodecode.lib import str2bool, safe_unicode, safe_str
41
41
42 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
43 """
43 """
44 Reset button
44 Reset button
45 """
45 """
46 _set_input_attrs(attrs, type, name, value)
46 _set_input_attrs(attrs, type, name, value)
47 _set_id_attr(attrs, id, name)
47 _set_id_attr(attrs, id, name)
48 convert_boolean_attrs(attrs, ["disabled"])
48 convert_boolean_attrs(attrs, ["disabled"])
49 return HTML.input(**attrs)
49 return HTML.input(**attrs)
50
50
51 reset = _reset
51 reset = _reset
52
52
53
53
54 def get_token():
54 def get_token():
55 """Return the current authentication token, creating one if one doesn't
55 """Return the current authentication token, creating one if one doesn't
56 already exist.
56 already exist.
57 """
57 """
58 token_key = "_authentication_token"
58 token_key = "_authentication_token"
59 from pylons import session
59 from pylons import session
60 if not token_key in session:
60 if not token_key in session:
61 try:
61 try:
62 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
63 except AttributeError: # Python < 2.4
63 except AttributeError: # Python < 2.4
64 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
65 session[token_key] = token
65 session[token_key] = token
66 if hasattr(session, 'save'):
66 if hasattr(session, 'save'):
67 session.save()
67 session.save()
68 return session[token_key]
68 return session[token_key]
69
69
70 class _GetError(object):
70 class _GetError(object):
71 """Get error from form_errors, and represent it as span wrapped error
71 """Get error from form_errors, and represent it as span wrapped error
72 message
72 message
73
73
74 :param field_name: field to fetch errors for
74 :param field_name: field to fetch errors for
75 :param form_errors: form errors dict
75 :param form_errors: form errors dict
76 """
76 """
77
77
78 def __call__(self, field_name, form_errors):
78 def __call__(self, field_name, form_errors):
79 tmpl = """<span class="error_msg">%s</span>"""
79 tmpl = """<span class="error_msg">%s</span>"""
80 if form_errors and form_errors.has_key(field_name):
80 if form_errors and form_errors.has_key(field_name):
81 return literal(tmpl % form_errors.get(field_name))
81 return literal(tmpl % form_errors.get(field_name))
82
82
83 get_error = _GetError()
83 get_error = _GetError()
84
84
85 class _ToolTip(object):
85 class _ToolTip(object):
86
86
87 def __call__(self, tooltip_title, trim_at=50):
87 def __call__(self, tooltip_title, trim_at=50):
88 """Special function just to wrap our text into nice formatted
88 """Special function just to wrap our text into nice formatted
89 autowrapped text
89 autowrapped text
90
90
91 :param tooltip_title:
91 :param tooltip_title:
92 """
92 """
93 return escape(tooltip_title)
93 return escape(tooltip_title)
94 tooltip = _ToolTip()
94 tooltip = _ToolTip()
95
95
96 class _FilesBreadCrumbs(object):
96 class _FilesBreadCrumbs(object):
97
97
98 def __call__(self, repo_name, rev, paths):
98 def __call__(self, repo_name, rev, paths):
99 if isinstance(paths, str):
99 if isinstance(paths, str):
100 paths = safe_unicode(paths)
100 paths = safe_unicode(paths)
101 url_l = [link_to(repo_name, url('files_home',
101 url_l = [link_to(repo_name, url('files_home',
102 repo_name=repo_name,
102 repo_name=repo_name,
103 revision=rev, f_path=''))]
103 revision=rev, f_path=''))]
104 paths_l = paths.split('/')
104 paths_l = paths.split('/')
105 for cnt, p in enumerate(paths_l):
105 for cnt, p in enumerate(paths_l):
106 if p != '':
106 if p != '':
107 url_l.append(link_to(p, url('files_home',
107 url_l.append(link_to(p, url('files_home',
108 repo_name=repo_name,
108 repo_name=repo_name,
109 revision=rev,
109 revision=rev,
110 f_path='/'.join(paths_l[:cnt + 1]))))
110 f_path='/'.join(paths_l[:cnt + 1]))))
111
111
112 return literal('/'.join(url_l))
112 return literal('/'.join(url_l))
113
113
114 files_breadcrumbs = _FilesBreadCrumbs()
114 files_breadcrumbs = _FilesBreadCrumbs()
115
115
116 class CodeHtmlFormatter(HtmlFormatter):
116 class CodeHtmlFormatter(HtmlFormatter):
117 """My code Html Formatter for source codes
117 """My code Html Formatter for source codes
118 """
118 """
119
119
120 def wrap(self, source, outfile):
120 def wrap(self, source, outfile):
121 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
121 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
122
122
123 def _wrap_code(self, source):
123 def _wrap_code(self, source):
124 for cnt, it in enumerate(source):
124 for cnt, it in enumerate(source):
125 i, t = it
125 i, t = it
126 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
126 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
127 yield i, t
127 yield i, t
128
128
129 def _wrap_tablelinenos(self, inner):
129 def _wrap_tablelinenos(self, inner):
130 dummyoutfile = StringIO.StringIO()
130 dummyoutfile = StringIO.StringIO()
131 lncount = 0
131 lncount = 0
132 for t, line in inner:
132 for t, line in inner:
133 if t:
133 if t:
134 lncount += 1
134 lncount += 1
135 dummyoutfile.write(line)
135 dummyoutfile.write(line)
136
136
137 fl = self.linenostart
137 fl = self.linenostart
138 mw = len(str(lncount + fl - 1))
138 mw = len(str(lncount + fl - 1))
139 sp = self.linenospecial
139 sp = self.linenospecial
140 st = self.linenostep
140 st = self.linenostep
141 la = self.lineanchors
141 la = self.lineanchors
142 aln = self.anchorlinenos
142 aln = self.anchorlinenos
143 nocls = self.noclasses
143 nocls = self.noclasses
144 if sp:
144 if sp:
145 lines = []
145 lines = []
146
146
147 for i in range(fl, fl + lncount):
147 for i in range(fl, fl + lncount):
148 if i % st == 0:
148 if i % st == 0:
149 if i % sp == 0:
149 if i % sp == 0:
150 if aln:
150 if aln:
151 lines.append('<a href="#%s%d" class="special">%*d</a>' %
151 lines.append('<a href="#%s%d" class="special">%*d</a>' %
152 (la, i, mw, i))
152 (la, i, mw, i))
153 else:
153 else:
154 lines.append('<span class="special">%*d</span>' % (mw, i))
154 lines.append('<span class="special">%*d</span>' % (mw, i))
155 else:
155 else:
156 if aln:
156 if aln:
157 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
157 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
158 else:
158 else:
159 lines.append('%*d' % (mw, i))
159 lines.append('%*d' % (mw, i))
160 else:
160 else:
161 lines.append('')
161 lines.append('')
162 ls = '\n'.join(lines)
162 ls = '\n'.join(lines)
163 else:
163 else:
164 lines = []
164 lines = []
165 for i in range(fl, fl + lncount):
165 for i in range(fl, fl + lncount):
166 if i % st == 0:
166 if i % st == 0:
167 if aln:
167 if aln:
168 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
168 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
169 else:
169 else:
170 lines.append('%*d' % (mw, i))
170 lines.append('%*d' % (mw, i))
171 else:
171 else:
172 lines.append('')
172 lines.append('')
173 ls = '\n'.join(lines)
173 ls = '\n'.join(lines)
174
174
175 # in case you wonder about the seemingly redundant <div> here: since the
175 # in case you wonder about the seemingly redundant <div> here: since the
176 # content in the other cell also is wrapped in a div, some browsers in
176 # content in the other cell also is wrapped in a div, some browsers in
177 # some configurations seem to mess up the formatting...
177 # some configurations seem to mess up the formatting...
178 if nocls:
178 if nocls:
179 yield 0, ('<table class="%stable">' % self.cssclass +
179 yield 0, ('<table class="%stable">' % self.cssclass +
180 '<tr><td><div class="linenodiv" '
180 '<tr><td><div class="linenodiv" '
181 'style="background-color: #f0f0f0; padding-right: 10px">'
181 'style="background-color: #f0f0f0; padding-right: 10px">'
182 '<pre style="line-height: 125%">' +
182 '<pre style="line-height: 125%">' +
183 ls + '</pre></div></td><td id="hlcode" class="code">')
183 ls + '</pre></div></td><td id="hlcode" class="code">')
184 else:
184 else:
185 yield 0, ('<table class="%stable">' % self.cssclass +
185 yield 0, ('<table class="%stable">' % self.cssclass +
186 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
186 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
187 ls + '</pre></div></td><td id="hlcode" class="code">')
187 ls + '</pre></div></td><td id="hlcode" class="code">')
188 yield 0, dummyoutfile.getvalue()
188 yield 0, dummyoutfile.getvalue()
189 yield 0, '</td></tr></table>'
189 yield 0, '</td></tr></table>'
190
190
191
191
192 def pygmentize(filenode, **kwargs):
192 def pygmentize(filenode, **kwargs):
193 """pygmentize function using pygments
193 """pygmentize function using pygments
194
194
195 :param filenode:
195 :param filenode:
196 """
196 """
197
197
198 return literal(code_highlight(filenode.content,
198 return literal(code_highlight(filenode.content,
199 filenode.lexer, CodeHtmlFormatter(**kwargs)))
199 filenode.lexer, CodeHtmlFormatter(**kwargs)))
200
200
201 def pygmentize_annotation(repo_name, filenode, **kwargs):
201 def pygmentize_annotation(repo_name, filenode, **kwargs):
202 """pygmentize function for annotation
202 """pygmentize function for annotation
203
203
204 :param filenode:
204 :param filenode:
205 """
205 """
206
206
207 color_dict = {}
207 color_dict = {}
208 def gen_color(n=10000):
208 def gen_color(n=10000):
209 """generator for getting n of evenly distributed colors using
209 """generator for getting n of evenly distributed colors using
210 hsv color and golden ratio. It always return same order of colors
210 hsv color and golden ratio. It always return same order of colors
211
211
212 :returns: RGB tuple
212 :returns: RGB tuple
213 """
213 """
214 import colorsys
214
215 def hsv_to_rgb(h, s, v):
216 if s == 0.0: return v, v, v
217 i = int(h * 6.0) # XXX assume int() truncates!
218 f = (h * 6.0) - i
219 p = v * (1.0 - s)
220 q = v * (1.0 - s * f)
221 t = v * (1.0 - s * (1.0 - f))
222 i = i % 6
223 if i == 0: return v, t, p
224 if i == 1: return q, v, p
225 if i == 2: return p, v, t
226 if i == 3: return p, q, v
227 if i == 4: return t, p, v
228 if i == 5: return v, p, q
229
215 golden_ratio = 0.618033988749895
230 golden_ratio = 0.618033988749895
216 h = 0.22717784590367374
231 h = 0.22717784590367374
217
232
218 for _ in xrange(n):
233 for _ in xrange(n):
219 h += golden_ratio
234 h += golden_ratio
220 h %= 1
235 h %= 1
221 HSV_tuple = [h, 0.95, 0.95]
236 HSV_tuple = [h, 0.95, 0.95]
222 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
237 RGB_tuple = hsv_to_rgb(*HSV_tuple)
223 yield map(lambda x:str(int(x * 256)), RGB_tuple)
238 yield map(lambda x:str(int(x * 256)), RGB_tuple)
224
239
225 cgenerator = gen_color()
240 cgenerator = gen_color()
226
241
227 def get_color_string(cs):
242 def get_color_string(cs):
228 if color_dict.has_key(cs):
243 if color_dict.has_key(cs):
229 col = color_dict[cs]
244 col = color_dict[cs]
230 else:
245 else:
231 col = color_dict[cs] = cgenerator.next()
246 col = color_dict[cs] = cgenerator.next()
232 return "color: rgb(%s)! important;" % (', '.join(col))
247 return "color: rgb(%s)! important;" % (', '.join(col))
233
248
234 def url_func(repo_name):
249 def url_func(repo_name):
235
250
236 def _url_func(changeset):
251 def _url_func(changeset):
237 author = changeset.author
252 author = changeset.author
238 date = changeset.date
253 date = changeset.date
239 message = tooltip(changeset.message)
254 message = tooltip(changeset.message)
240
255
241 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
256 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
242 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
257 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
243 "</b> %s<br/></div>")
258 "</b> %s<br/></div>")
244
259
245 tooltip_html = tooltip_html % (author, date, message)
260 tooltip_html = tooltip_html % (author, date, message)
246 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
261 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
247 short_id(changeset.raw_id))
262 short_id(changeset.raw_id))
248 uri = link_to(
263 uri = link_to(
249 lnk_format,
264 lnk_format,
250 url('changeset_home', repo_name=repo_name,
265 url('changeset_home', repo_name=repo_name,
251 revision=changeset.raw_id),
266 revision=changeset.raw_id),
252 style=get_color_string(changeset.raw_id),
267 style=get_color_string(changeset.raw_id),
253 class_='tooltip',
268 class_='tooltip',
254 title=tooltip_html
269 title=tooltip_html
255 )
270 )
256
271
257 uri += '\n'
272 uri += '\n'
258 return uri
273 return uri
259 return _url_func
274 return _url_func
260
275
261 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
276 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
262
277
263 def get_changeset_safe(repo, rev):
278 def get_changeset_safe(repo, rev):
264 from vcs.backends.base import BaseRepository
279 from vcs.backends.base import BaseRepository
265 from vcs.exceptions import RepositoryError
280 from vcs.exceptions import RepositoryError
266 if not isinstance(repo, BaseRepository):
281 if not isinstance(repo, BaseRepository):
267 raise Exception('You must pass an Repository '
282 raise Exception('You must pass an Repository '
268 'object as first argument got %s', type(repo))
283 'object as first argument got %s', type(repo))
269
284
270 try:
285 try:
271 cs = repo.get_changeset(rev)
286 cs = repo.get_changeset(rev)
272 except RepositoryError:
287 except RepositoryError:
273 from rhodecode.lib.utils import EmptyChangeset
288 from rhodecode.lib.utils import EmptyChangeset
274 cs = EmptyChangeset()
289 cs = EmptyChangeset()
275 return cs
290 return cs
276
291
277
292
278 def is_following_repo(repo_name, user_id):
293 def is_following_repo(repo_name, user_id):
279 from rhodecode.model.scm import ScmModel
294 from rhodecode.model.scm import ScmModel
280 return ScmModel().is_following_repo(repo_name, user_id)
295 return ScmModel().is_following_repo(repo_name, user_id)
281
296
282 flash = _Flash()
297 flash = _Flash()
283
298
284 #==============================================================================
299 #==============================================================================
285 # SCM FILTERS available via h.
300 # SCM FILTERS available via h.
286 #==============================================================================
301 #==============================================================================
287 from vcs.utils import author_name, author_email
302 from vcs.utils import author_name, author_email
288 from rhodecode.lib import credentials_filter, age as _age
303 from rhodecode.lib import credentials_filter, age as _age
289
304
290 age = lambda x:_age(x)
305 age = lambda x:_age(x)
291 capitalize = lambda x: x.capitalize()
306 capitalize = lambda x: x.capitalize()
292 email = author_email
307 email = author_email
293 email_or_none = lambda x: email(x) if email(x) != x else None
308 email_or_none = lambda x: email(x) if email(x) != x else None
294 person = lambda x: author_name(x)
309 person = lambda x: author_name(x)
295 short_id = lambda x: x[:12]
310 short_id = lambda x: x[:12]
296 hide_credentials = lambda x: ''.join(credentials_filter(x))
311 hide_credentials = lambda x: ''.join(credentials_filter(x))
297
312
298 def bool2icon(value):
313 def bool2icon(value):
299 """Returns True/False values represented as small html image of true/false
314 """Returns True/False values represented as small html image of true/false
300 icons
315 icons
301
316
302 :param value: bool value
317 :param value: bool value
303 """
318 """
304
319
305 if value is True:
320 if value is True:
306 return HTML.tag('img', src=url("/images/icons/accept.png"),
321 return HTML.tag('img', src=url("/images/icons/accept.png"),
307 alt=_('True'))
322 alt=_('True'))
308
323
309 if value is False:
324 if value is False:
310 return HTML.tag('img', src=url("/images/icons/cancel.png"),
325 return HTML.tag('img', src=url("/images/icons/cancel.png"),
311 alt=_('False'))
326 alt=_('False'))
312
327
313 return value
328 return value
314
329
315
330
316 def action_parser(user_log, feed=False):
331 def action_parser(user_log, feed=False):
317 """This helper will action_map the specified string action into translated
332 """This helper will action_map the specified string action into translated
318 fancy names with icons and links
333 fancy names with icons and links
319
334
320 :param user_log: user log instance
335 :param user_log: user log instance
321 :param feed: use output for feeds (no html and fancy icons)
336 :param feed: use output for feeds (no html and fancy icons)
322 """
337 """
323
338
324 action = user_log.action
339 action = user_log.action
325 action_params = ' '
340 action_params = ' '
326
341
327 x = action.split(':')
342 x = action.split(':')
328
343
329 if len(x) > 1:
344 if len(x) > 1:
330 action, action_params = x
345 action, action_params = x
331
346
332 def get_cs_links():
347 def get_cs_links():
333 revs_limit = 3 #display this amount always
348 revs_limit = 3 #display this amount always
334 revs_top_limit = 50 #show upto this amount of changesets hidden
349 revs_top_limit = 50 #show upto this amount of changesets hidden
335 revs = action_params.split(',')
350 revs = action_params.split(',')
336 repo_name = user_log.repository.repo_name
351 repo_name = user_log.repository.repo_name
337
352
338 from rhodecode.model.scm import ScmModel
353 from rhodecode.model.scm import ScmModel
339 repo = user_log.repository.scm_instance
354 repo = user_log.repository.scm_instance
340
355
341 message = lambda rev: get_changeset_safe(repo, rev).message
356 message = lambda rev: get_changeset_safe(repo, rev).message
342 cs_links = []
357 cs_links = []
343 cs_links.append(" " + ', '.join ([link_to(rev,
358 cs_links.append(" " + ', '.join ([link_to(rev,
344 url('changeset_home',
359 url('changeset_home',
345 repo_name=repo_name,
360 repo_name=repo_name,
346 revision=rev), title=tooltip(message(rev)),
361 revision=rev), title=tooltip(message(rev)),
347 class_='tooltip') for rev in revs[:revs_limit] ]))
362 class_='tooltip') for rev in revs[:revs_limit] ]))
348
363
349 compare_view = (' <div class="compare_view tooltip" title="%s">'
364 compare_view = (' <div class="compare_view tooltip" title="%s">'
350 '<a href="%s">%s</a> '
365 '<a href="%s">%s</a> '
351 '</div>' % (_('Show all combined changesets %s->%s' \
366 '</div>' % (_('Show all combined changesets %s->%s' \
352 % (revs[0], revs[-1])),
367 % (revs[0], revs[-1])),
353 url('changeset_home', repo_name=repo_name,
368 url('changeset_home', repo_name=repo_name,
354 revision='%s...%s' % (revs[0], revs[-1])
369 revision='%s...%s' % (revs[0], revs[-1])
355 ),
370 ),
356 _('compare view'))
371 _('compare view'))
357 )
372 )
358
373
359 if len(revs) > revs_limit:
374 if len(revs) > revs_limit:
360 uniq_id = revs[0]
375 uniq_id = revs[0]
361 html_tmpl = ('<span> %s '
376 html_tmpl = ('<span> %s '
362 '<a class="show_more" id="_%s" href="#more">%s</a> '
377 '<a class="show_more" id="_%s" href="#more">%s</a> '
363 '%s</span>')
378 '%s</span>')
364 if not feed:
379 if not feed:
365 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
380 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
366 % (len(revs) - revs_limit),
381 % (len(revs) - revs_limit),
367 _('revisions')))
382 _('revisions')))
368
383
369 if not feed:
384 if not feed:
370 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
385 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
371 else:
386 else:
372 html_tmpl = '<span id="%s"> %s </span>'
387 html_tmpl = '<span id="%s"> %s </span>'
373
388
374 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
389 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
375 url('changeset_home',
390 url('changeset_home',
376 repo_name=repo_name, revision=rev),
391 repo_name=repo_name, revision=rev),
377 title=message(rev), class_='tooltip')
392 title=message(rev), class_='tooltip')
378 for rev in revs[revs_limit:revs_top_limit]])))
393 for rev in revs[revs_limit:revs_top_limit]])))
379 if len(revs) > 1:
394 if len(revs) > 1:
380 cs_links.append(compare_view)
395 cs_links.append(compare_view)
381 return ''.join(cs_links)
396 return ''.join(cs_links)
382
397
383 def get_fork_name():
398 def get_fork_name():
384 repo_name = action_params
399 repo_name = action_params
385 return _('fork name ') + str(link_to(action_params, url('summary_home',
400 return _('fork name ') + str(link_to(action_params, url('summary_home',
386 repo_name=repo_name,)))
401 repo_name=repo_name,)))
387
402
388 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
403 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
389 'user_created_repo':(_('[created] repository'), None),
404 'user_created_repo':(_('[created] repository'), None),
390 'user_forked_repo':(_('[forked] repository'), get_fork_name),
405 'user_forked_repo':(_('[forked] repository'), get_fork_name),
391 'user_updated_repo':(_('[updated] repository'), None),
406 'user_updated_repo':(_('[updated] repository'), None),
392 'admin_deleted_repo':(_('[delete] repository'), None),
407 'admin_deleted_repo':(_('[delete] repository'), None),
393 'admin_created_repo':(_('[created] repository'), None),
408 'admin_created_repo':(_('[created] repository'), None),
394 'admin_forked_repo':(_('[forked] repository'), None),
409 'admin_forked_repo':(_('[forked] repository'), None),
395 'admin_updated_repo':(_('[updated] repository'), None),
410 'admin_updated_repo':(_('[updated] repository'), None),
396 'push':(_('[pushed] into'), get_cs_links),
411 'push':(_('[pushed] into'), get_cs_links),
397 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
412 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
398 'push_remote':(_('[pulled from remote] into'), get_cs_links),
413 'push_remote':(_('[pulled from remote] into'), get_cs_links),
399 'pull':(_('[pulled] from'), None),
414 'pull':(_('[pulled] from'), None),
400 'started_following_repo':(_('[started following] repository'), None),
415 'started_following_repo':(_('[started following] repository'), None),
401 'stopped_following_repo':(_('[stopped following] repository'), None),
416 'stopped_following_repo':(_('[stopped following] repository'), None),
402 }
417 }
403
418
404 action_str = action_map.get(action, action)
419 action_str = action_map.get(action, action)
405 if feed:
420 if feed:
406 action = action_str[0].replace('[', '').replace(']', '')
421 action = action_str[0].replace('[', '').replace(']', '')
407 else:
422 else:
408 action = action_str[0].replace('[', '<span class="journal_highlight">')\
423 action = action_str[0].replace('[', '<span class="journal_highlight">')\
409 .replace(']', '</span>')
424 .replace(']', '</span>')
410
425
411 action_params_func = lambda :""
426 action_params_func = lambda :""
412
427
413 if callable(action_str[1]):
428 if callable(action_str[1]):
414 action_params_func = action_str[1]
429 action_params_func = action_str[1]
415
430
416 return [literal(action), action_params_func]
431 return [literal(action), action_params_func]
417
432
418 def action_parser_icon(user_log):
433 def action_parser_icon(user_log):
419 action = user_log.action
434 action = user_log.action
420 action_params = None
435 action_params = None
421 x = action.split(':')
436 x = action.split(':')
422
437
423 if len(x) > 1:
438 if len(x) > 1:
424 action, action_params = x
439 action, action_params = x
425
440
426 tmpl = """<img src="%s%s" alt="%s"/>"""
441 tmpl = """<img src="%s%s" alt="%s"/>"""
427 map = {'user_deleted_repo':'database_delete.png',
442 map = {'user_deleted_repo':'database_delete.png',
428 'user_created_repo':'database_add.png',
443 'user_created_repo':'database_add.png',
429 'user_forked_repo':'arrow_divide.png',
444 'user_forked_repo':'arrow_divide.png',
430 'user_updated_repo':'database_edit.png',
445 'user_updated_repo':'database_edit.png',
431 'admin_deleted_repo':'database_delete.png',
446 'admin_deleted_repo':'database_delete.png',
432 'admin_created_repo':'database_add.png',
447 'admin_created_repo':'database_add.png',
433 'admin_forked_repo':'arrow_divide.png',
448 'admin_forked_repo':'arrow_divide.png',
434 'admin_updated_repo':'database_edit.png',
449 'admin_updated_repo':'database_edit.png',
435 'push':'script_add.png',
450 'push':'script_add.png',
436 'push_local':'script_edit.png',
451 'push_local':'script_edit.png',
437 'push_remote':'connect.png',
452 'push_remote':'connect.png',
438 'pull':'down_16.png',
453 'pull':'down_16.png',
439 'started_following_repo':'heart_add.png',
454 'started_following_repo':'heart_add.png',
440 'stopped_following_repo':'heart_delete.png',
455 'stopped_following_repo':'heart_delete.png',
441 }
456 }
442 return literal(tmpl % ((url('/images/icons/')),
457 return literal(tmpl % ((url('/images/icons/')),
443 map.get(action, action), action))
458 map.get(action, action), action))
444
459
445
460
446 #==============================================================================
461 #==============================================================================
447 # PERMS
462 # PERMS
448 #==============================================================================
463 #==============================================================================
449 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
464 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
450 HasRepoPermissionAny, HasRepoPermissionAll
465 HasRepoPermissionAny, HasRepoPermissionAll
451
466
452 #==============================================================================
467 #==============================================================================
453 # GRAVATAR URL
468 # GRAVATAR URL
454 #==============================================================================
469 #==============================================================================
455
470
456 def gravatar_url(email_address, size=30):
471 def gravatar_url(email_address, size=30):
457 if not str2bool(config['app_conf'].get('use_gravatar')) or \
472 if not str2bool(config['app_conf'].get('use_gravatar')) or \
458 email_address == 'anonymous@rhodecode.org':
473 email_address == 'anonymous@rhodecode.org':
459 return "/images/user%s.png" % size
474 return "/images/user%s.png" % size
460
475
461 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
476 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
462 default = 'identicon'
477 default = 'identicon'
463 baseurl_nossl = "http://www.gravatar.com/avatar/"
478 baseurl_nossl = "http://www.gravatar.com/avatar/"
464 baseurl_ssl = "https://secure.gravatar.com/avatar/"
479 baseurl_ssl = "https://secure.gravatar.com/avatar/"
465 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
480 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
466
481
467 if isinstance(email_address, unicode):
482 if isinstance(email_address, unicode):
468 #hashlib crashes on unicode items
483 #hashlib crashes on unicode items
469 email_address = safe_str(email_address)
484 email_address = safe_str(email_address)
470 # construct the url
485 # construct the url
471 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
486 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
472 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
487 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
473
488
474 return gravatar_url
489 return gravatar_url
475
490
476
491
477 #==============================================================================
492 #==============================================================================
478 # REPO PAGER, PAGER FOR REPOSITORY
493 # REPO PAGER, PAGER FOR REPOSITORY
479 #==============================================================================
494 #==============================================================================
480 class RepoPage(Page):
495 class RepoPage(Page):
481
496
482 def __init__(self, collection, page=1, items_per_page=20,
497 def __init__(self, collection, page=1, items_per_page=20,
483 item_count=None, url=None, branch_name=None, **kwargs):
498 item_count=None, url=None, branch_name=None, **kwargs):
484
499
485 """Create a "RepoPage" instance. special pager for paging
500 """Create a "RepoPage" instance. special pager for paging
486 repository
501 repository
487 """
502 """
488 self._url_generator = url
503 self._url_generator = url
489
504
490 # Safe the kwargs class-wide so they can be used in the pager() method
505 # Safe the kwargs class-wide so they can be used in the pager() method
491 self.kwargs = kwargs
506 self.kwargs = kwargs
492
507
493 # Save a reference to the collection
508 # Save a reference to the collection
494 self.original_collection = collection
509 self.original_collection = collection
495
510
496 self.collection = collection
511 self.collection = collection
497
512
498 # The self.page is the number of the current page.
513 # The self.page is the number of the current page.
499 # The first page has the number 1!
514 # The first page has the number 1!
500 try:
515 try:
501 self.page = int(page) # make it int() if we get it as a string
516 self.page = int(page) # make it int() if we get it as a string
502 except (ValueError, TypeError):
517 except (ValueError, TypeError):
503 self.page = 1
518 self.page = 1
504
519
505 self.items_per_page = items_per_page
520 self.items_per_page = items_per_page
506
521
507 # Unless the user tells us how many items the collections has
522 # Unless the user tells us how many items the collections has
508 # we calculate that ourselves.
523 # we calculate that ourselves.
509 if item_count is not None:
524 if item_count is not None:
510 self.item_count = item_count
525 self.item_count = item_count
511 else:
526 else:
512 self.item_count = len(self.collection)
527 self.item_count = len(self.collection)
513
528
514 # Compute the number of the first and last available page
529 # Compute the number of the first and last available page
515 if self.item_count > 0:
530 if self.item_count > 0:
516 self.first_page = 1
531 self.first_page = 1
517 self.page_count = int(math.ceil(float(self.item_count) /
532 self.page_count = int(math.ceil(float(self.item_count) /
518 self.items_per_page))
533 self.items_per_page))
519 self.last_page = self.first_page + self.page_count - 1
534 self.last_page = self.first_page + self.page_count - 1
520
535
521 # Make sure that the requested page number is the range of valid pages
536 # Make sure that the requested page number is the range of valid pages
522 if self.page > self.last_page:
537 if self.page > self.last_page:
523 self.page = self.last_page
538 self.page = self.last_page
524 elif self.page < self.first_page:
539 elif self.page < self.first_page:
525 self.page = self.first_page
540 self.page = self.first_page
526
541
527 # Note: the number of items on this page can be less than
542 # Note: the number of items on this page can be less than
528 # items_per_page if the last page is not full
543 # items_per_page if the last page is not full
529 self.first_item = max(0, (self.item_count) - (self.page *
544 self.first_item = max(0, (self.item_count) - (self.page *
530 items_per_page))
545 items_per_page))
531 self.last_item = ((self.item_count - 1) - items_per_page *
546 self.last_item = ((self.item_count - 1) - items_per_page *
532 (self.page - 1))
547 (self.page - 1))
533
548
534 iterator = self.collection.get_changesets(start=self.first_item,
549 iterator = self.collection.get_changesets(start=self.first_item,
535 end=self.last_item,
550 end=self.last_item,
536 reverse=True,
551 reverse=True,
537 branch_name=branch_name)
552 branch_name=branch_name)
538 self.items = list(iterator)
553 self.items = list(iterator)
539
554
540 # Links to previous and next page
555 # Links to previous and next page
541 if self.page > self.first_page:
556 if self.page > self.first_page:
542 self.previous_page = self.page - 1
557 self.previous_page = self.page - 1
543 else:
558 else:
544 self.previous_page = None
559 self.previous_page = None
545
560
546 if self.page < self.last_page:
561 if self.page < self.last_page:
547 self.next_page = self.page + 1
562 self.next_page = self.page + 1
548 else:
563 else:
549 self.next_page = None
564 self.next_page = None
550
565
551 # No items available
566 # No items available
552 else:
567 else:
553 self.first_page = None
568 self.first_page = None
554 self.page_count = 0
569 self.page_count = 0
555 self.last_page = None
570 self.last_page = None
556 self.first_item = None
571 self.first_item = None
557 self.last_item = None
572 self.last_item = None
558 self.previous_page = None
573 self.previous_page = None
559 self.next_page = None
574 self.next_page = None
560 self.items = []
575 self.items = []
561
576
562 # This is a subclass of the 'list' type. Initialise the list now.
577 # This is a subclass of the 'list' type. Initialise the list now.
563 list.__init__(self, self.items)
578 list.__init__(self, self.items)
564
579
565
580
566 def changed_tooltip(nodes):
581 def changed_tooltip(nodes):
567 """
582 """
568 Generates a html string for changed nodes in changeset page.
583 Generates a html string for changed nodes in changeset page.
569 It limits the output to 30 entries
584 It limits the output to 30 entries
570
585
571 :param nodes: LazyNodesGenerator
586 :param nodes: LazyNodesGenerator
572 """
587 """
573 if nodes:
588 if nodes:
574 pref = ': <br/> '
589 pref = ': <br/> '
575 suf = ''
590 suf = ''
576 if len(nodes) > 30:
591 if len(nodes) > 30:
577 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
592 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
578 return literal(pref + '<br/> '.join([safe_unicode(x.path)
593 return literal(pref + '<br/> '.join([safe_unicode(x.path)
579 for x in nodes[:30]]) + suf)
594 for x in nodes[:30]]) + suf)
580 else:
595 else:
581 return ': ' + _('No Files')
596 return ': ' + _('No Files')
582
597
583
598
584
599
585 def repo_link(groups_and_repos):
600 def repo_link(groups_and_repos):
586 """
601 """
587 Makes a breadcrumbs link to repo within a group
602 Makes a breadcrumbs link to repo within a group
588 joins &raquo; on each group to create a fancy link
603 joins &raquo; on each group to create a fancy link
589
604
590 ex::
605 ex::
591 group >> subgroup >> repo
606 group >> subgroup >> repo
592
607
593 :param groups_and_repos:
608 :param groups_and_repos:
594 """
609 """
595 groups, repo_name = groups_and_repos
610 groups, repo_name = groups_and_repos
596
611
597 if not groups:
612 if not groups:
598 return repo_name
613 return repo_name
599 else:
614 else:
600 def make_link(group):
615 def make_link(group):
601 return link_to(group.group_name, url('repos_group',
616 return link_to(group.group_name, url('repos_group',
602 id=group.group_id))
617 id=group.group_id))
603 return literal(' &raquo; '.join(map(make_link, groups)) + \
618 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 " &raquo; " + repo_name)
619 " &raquo; " + repo_name)
605
620
606
621
607 def fancy_file_stats(stats):
622 def fancy_file_stats(stats):
608 """
623 """
609 Displays a fancy two colored bar for number of added/deleted
624 Displays a fancy two colored bar for number of added/deleted
610 lines of code on file
625 lines of code on file
611
626
612 :param stats: two element list of added/deleted lines of code
627 :param stats: two element list of added/deleted lines of code
613 """
628 """
614
629
615 a, d, t = stats[0], stats[1], stats[0] + stats[1]
630 a, d, t = stats[0], stats[1], stats[0] + stats[1]
616 width = 100
631 width = 100
617 unit = float(width) / (t or 1)
632 unit = float(width) / (t or 1)
618
633
619 # needs > 9% of width to be visible or 0 to be hidden
634 # needs > 9% of width to be visible or 0 to be hidden
620 a_p = max(9, unit * a) if a > 0 else 0
635 a_p = max(9, unit * a) if a > 0 else 0
621 d_p = max(9, unit * d) if d > 0 else 0
636 d_p = max(9, unit * d) if d > 0 else 0
622 p_sum = a_p + d_p
637 p_sum = a_p + d_p
623
638
624 if p_sum > width:
639 if p_sum > width:
625 #adjust the percentage to be == 100% since we adjusted to 9
640 #adjust the percentage to be == 100% since we adjusted to 9
626 if a_p > d_p:
641 if a_p > d_p:
627 a_p = a_p - (p_sum - width)
642 a_p = a_p - (p_sum - width)
628 else:
643 else:
629 d_p = d_p - (p_sum - width)
644 d_p = d_p - (p_sum - width)
630
645
631 a_v = a if a > 0 else ''
646 a_v = a if a > 0 else ''
632 d_v = d if d > 0 else ''
647 d_v = d if d > 0 else ''
633
648
634
649
635 def cgen(l_type):
650 def cgen(l_type):
636 mapping = {'tr':'top-right-rounded-corner',
651 mapping = {'tr':'top-right-rounded-corner',
637 'tl':'top-left-rounded-corner',
652 'tl':'top-left-rounded-corner',
638 'br':'bottom-right-rounded-corner',
653 'br':'bottom-right-rounded-corner',
639 'bl':'bottom-left-rounded-corner'}
654 'bl':'bottom-left-rounded-corner'}
640 map_getter = lambda x:mapping[x]
655 map_getter = lambda x:mapping[x]
641
656
642 if l_type == 'a' and d_v:
657 if l_type == 'a' and d_v:
643 #case when added and deleted are present
658 #case when added and deleted are present
644 return ' '.join(map(map_getter, ['tl', 'bl']))
659 return ' '.join(map(map_getter, ['tl', 'bl']))
645
660
646 if l_type == 'a' and not d_v:
661 if l_type == 'a' and not d_v:
647 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
662 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
648
663
649 if l_type == 'd' and a_v:
664 if l_type == 'd' and a_v:
650 return ' '.join(map(map_getter, ['tr', 'br']))
665 return ' '.join(map(map_getter, ['tr', 'br']))
651
666
652 if l_type == 'd' and not a_v:
667 if l_type == 'd' and not a_v:
653 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
668 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
654
669
655
670
656
671
657 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
672 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
658 a_p, a_v)
673 a_p, a_v)
659 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
674 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
660 d_p, d_v)
675 d_p, d_v)
661 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
676 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
662
677
663
678
664 def urlify_text(text):
679 def urlify_text(text):
665 import re
680 import re
666
681
667 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
682 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
668
683
669 def url_func(match_obj):
684 def url_func(match_obj):
670 url_full = match_obj.groups()[0]
685 url_full = match_obj.groups()[0]
671 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
686 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
672
687
673 return literal(url_pat.sub(url_func, text))
688 return literal(url_pat.sub(url_func, text))
General Comments 0
You need to be logged in to leave comments. Login now