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