##// END OF EJS Templates
hidden compare view link for single revision push
marcink -
r1024:22c14772 beta
parent child Browse files
Show More
@@ -1,590 +1,591
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 from pygments.formatters import HtmlFormatter
9 from pygments.formatters import HtmlFormatter
10 from pygments import highlight as code_highlight
10 from pygments import highlight as code_highlight
11 from pylons import url
11 from pylons import url
12 from pylons.i18n.translation import _, ungettext
12 from pylons.i18n.translation import _, ungettext
13 from vcs.utils.annotate import annotate_highlight
13 from vcs.utils.annotate import annotate_highlight
14 from rhodecode.lib.utils import repo_name_slug
14 from rhodecode.lib.utils import repo_name_slug
15
15
16 from webhelpers.html import literal, HTML, escape
16 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html.tools import *
17 from webhelpers.html.tools import *
18 from webhelpers.html.builder import make_tag
18 from webhelpers.html.builder import make_tag
19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
19 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
20 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
21 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 password, textarea, title, ul, xml_declaration, radio
22 password, textarea, title, ul, xml_declaration, radio
23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
23 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 mail_to, strip_links, strip_tags, tag_re
24 mail_to, strip_links, strip_tags, tag_re
25 from webhelpers.number import format_byte_size, format_bit_size
25 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.pylonslib import Flash as _Flash
26 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib.secure_form import secure_form
27 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
28 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
29 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 replace_whitespace, urlify, truncate, wrap_paragraphs
30 replace_whitespace, urlify, truncate, wrap_paragraphs
31 from webhelpers.date import time_ago_in_words
31 from webhelpers.date import time_ago_in_words
32
32
33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
33 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 convert_boolean_attrs, NotGiven
34 convert_boolean_attrs, NotGiven
35
35
36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
36 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
37 """Reset button
37 """Reset button
38 """
38 """
39 _set_input_attrs(attrs, type, name, value)
39 _set_input_attrs(attrs, type, name, value)
40 _set_id_attr(attrs, id, name)
40 _set_id_attr(attrs, id, name)
41 convert_boolean_attrs(attrs, ["disabled"])
41 convert_boolean_attrs(attrs, ["disabled"])
42 return HTML.input(**attrs)
42 return HTML.input(**attrs)
43
43
44 reset = _reset
44 reset = _reset
45
45
46
46
47 def get_token():
47 def get_token():
48 """Return the current authentication token, creating one if one doesn't
48 """Return the current authentication token, creating one if one doesn't
49 already exist.
49 already exist.
50 """
50 """
51 token_key = "_authentication_token"
51 token_key = "_authentication_token"
52 from pylons import session
52 from pylons import session
53 if not token_key in session:
53 if not token_key in session:
54 try:
54 try:
55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
55 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
56 except AttributeError: # Python < 2.4
56 except AttributeError: # Python < 2.4
57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
57 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
58 session[token_key] = token
58 session[token_key] = token
59 if hasattr(session, 'save'):
59 if hasattr(session, 'save'):
60 session.save()
60 session.save()
61 return session[token_key]
61 return session[token_key]
62
62
63 class _GetError(object):
63 class _GetError(object):
64 """Get error from form_errors, and represent it as span wrapped error
64 """Get error from form_errors, and represent it as span wrapped error
65 message
65 message
66
66
67 :param field_name: field to fetch errors for
67 :param field_name: field to fetch errors for
68 :param form_errors: form errors dict
68 :param form_errors: form errors dict
69 """
69 """
70
70
71 def __call__(self, field_name, form_errors):
71 def __call__(self, field_name, form_errors):
72 tmpl = """<span class="error_msg">%s</span>"""
72 tmpl = """<span class="error_msg">%s</span>"""
73 if form_errors and form_errors.has_key(field_name):
73 if form_errors and form_errors.has_key(field_name):
74 return literal(tmpl % form_errors.get(field_name))
74 return literal(tmpl % form_errors.get(field_name))
75
75
76 get_error = _GetError()
76 get_error = _GetError()
77
77
78 class _ToolTip(object):
78 class _ToolTip(object):
79
79
80 def __call__(self, tooltip_title, trim_at=50):
80 def __call__(self, tooltip_title, trim_at=50):
81 """Special function just to wrap our text into nice formatted
81 """Special function just to wrap our text into nice formatted
82 autowrapped text
82 autowrapped text
83
83
84 :param tooltip_title:
84 :param tooltip_title:
85 """
85 """
86
86
87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
87 return wrap_paragraphs(escape(tooltip_title), trim_at)\
88 .replace('\n', '<br/>')
88 .replace('\n', '<br/>')
89
89
90 def activate(self):
90 def activate(self):
91 """Adds tooltip mechanism to the given Html all tooltips have to have
91 """Adds tooltip mechanism to the given Html all tooltips have to have
92 set class `tooltip` and set attribute `tooltip_title`.
92 set class `tooltip` and set attribute `tooltip_title`.
93 Then a tooltip will be generated based on that. All with yui js tooltip
93 Then a tooltip will be generated based on that. All with yui js tooltip
94 """
94 """
95
95
96 js = '''
96 js = '''
97 YAHOO.util.Event.onDOMReady(function(){
97 YAHOO.util.Event.onDOMReady(function(){
98 function toolTipsId(){
98 function toolTipsId(){
99 var ids = [];
99 var ids = [];
100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
100 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
101
101
102 for (var i = 0; i < tts.length; i++) {
102 for (var i = 0; i < tts.length; i++) {
103 //if element doesn't not have and id autogenerate one for tooltip
103 //if element doesn't not have and id autogenerate one for tooltip
104
104
105 if (!tts[i].id){
105 if (!tts[i].id){
106 tts[i].id='tt'+i*100;
106 tts[i].id='tt'+i*100;
107 }
107 }
108 ids.push(tts[i].id);
108 ids.push(tts[i].id);
109 }
109 }
110 return ids
110 return ids
111 };
111 };
112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
112 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
113 context: toolTipsId(),
113 context: toolTipsId(),
114 monitorresize:false,
114 monitorresize:false,
115 xyoffset :[0,0],
115 xyoffset :[0,0],
116 autodismissdelay:300000,
116 autodismissdelay:300000,
117 hidedelay:5,
117 hidedelay:5,
118 showdelay:20,
118 showdelay:20,
119 });
119 });
120
120
121 // Set the text for the tooltip just before we display it. Lazy method
121 // Set the text for the tooltip just before we display it. Lazy method
122 myToolTips.contextTriggerEvent.subscribe(
122 myToolTips.contextTriggerEvent.subscribe(
123 function(type, args) {
123 function(type, args) {
124
124
125 var context = args[0];
125 var context = args[0];
126
126
127 //positioning of tooltip
127 //positioning of tooltip
128 var tt_w = this.element.clientWidth;//tooltip width
128 var tt_w = this.element.clientWidth;//tooltip width
129 var tt_h = this.element.clientHeight;//tooltip height
129 var tt_h = this.element.clientHeight;//tooltip height
130
130
131 var context_w = context.offsetWidth;
131 var context_w = context.offsetWidth;
132 var context_h = context.offsetHeight;
132 var context_h = context.offsetHeight;
133
133
134 var pos_x = YAHOO.util.Dom.getX(context);
134 var pos_x = YAHOO.util.Dom.getX(context);
135 var pos_y = YAHOO.util.Dom.getY(context);
135 var pos_y = YAHOO.util.Dom.getY(context);
136
136
137 var display_strategy = 'right';
137 var display_strategy = 'right';
138 var xy_pos = [0,0];
138 var xy_pos = [0,0];
139 switch (display_strategy){
139 switch (display_strategy){
140
140
141 case 'top':
141 case 'top':
142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
142 var cur_x = (pos_x+context_w/2)-(tt_w/2);
143 var cur_y = (pos_y-tt_h-4);
143 var cur_y = (pos_y-tt_h-4);
144 xy_pos = [cur_x,cur_y];
144 xy_pos = [cur_x,cur_y];
145 break;
145 break;
146 case 'bottom':
146 case 'bottom':
147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
147 var cur_x = (pos_x+context_w/2)-(tt_w/2);
148 var cur_y = pos_y+context_h+4;
148 var cur_y = pos_y+context_h+4;
149 xy_pos = [cur_x,cur_y];
149 xy_pos = [cur_x,cur_y];
150 break;
150 break;
151 case 'left':
151 case 'left':
152 var cur_x = (pos_x-tt_w-4);
152 var cur_x = (pos_x-tt_w-4);
153 var cur_y = pos_y-((tt_h/2)-context_h/2);
153 var cur_y = pos_y-((tt_h/2)-context_h/2);
154 xy_pos = [cur_x,cur_y];
154 xy_pos = [cur_x,cur_y];
155 break;
155 break;
156 case 'right':
156 case 'right':
157 var cur_x = (pos_x+context_w+4);
157 var cur_x = (pos_x+context_w+4);
158 var cur_y = pos_y-((tt_h/2)-context_h/2);
158 var cur_y = pos_y-((tt_h/2)-context_h/2);
159 xy_pos = [cur_x,cur_y];
159 xy_pos = [cur_x,cur_y];
160 break;
160 break;
161 default:
161 default:
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
162 var cur_x = (pos_x+context_w/2)-(tt_w/2);
163 var cur_y = pos_y-tt_h-4;
163 var cur_y = pos_y-tt_h-4;
164 xy_pos = [cur_x,cur_y];
164 xy_pos = [cur_x,cur_y];
165 break;
165 break;
166
166
167 }
167 }
168
168
169 this.cfg.setProperty("xy",xy_pos);
169 this.cfg.setProperty("xy",xy_pos);
170
170
171 });
171 });
172
172
173 //Mouse out
173 //Mouse out
174 myToolTips.contextMouseOutEvent.subscribe(
174 myToolTips.contextMouseOutEvent.subscribe(
175 function(type, args) {
175 function(type, args) {
176 var context = args[0];
176 var context = args[0];
177
177
178 });
178 });
179 });
179 });
180 '''
180 '''
181 return literal(js)
181 return literal(js)
182
182
183 tooltip = _ToolTip()
183 tooltip = _ToolTip()
184
184
185 class _FilesBreadCrumbs(object):
185 class _FilesBreadCrumbs(object):
186
186
187 def __call__(self, repo_name, rev, paths):
187 def __call__(self, repo_name, rev, paths):
188 if isinstance(paths, str):
188 if isinstance(paths, str):
189 paths = paths.decode('utf-8')
189 paths = paths.decode('utf-8')
190 url_l = [link_to(repo_name, url('files_home',
190 url_l = [link_to(repo_name, url('files_home',
191 repo_name=repo_name,
191 repo_name=repo_name,
192 revision=rev, f_path=''))]
192 revision=rev, f_path=''))]
193 paths_l = paths.split('/')
193 paths_l = paths.split('/')
194 for cnt, p in enumerate(paths_l):
194 for cnt, p in enumerate(paths_l):
195 if p != '':
195 if p != '':
196 url_l.append(link_to(p, url('files_home',
196 url_l.append(link_to(p, url('files_home',
197 repo_name=repo_name,
197 repo_name=repo_name,
198 revision=rev,
198 revision=rev,
199 f_path='/'.join(paths_l[:cnt + 1]))))
199 f_path='/'.join(paths_l[:cnt + 1]))))
200
200
201 return literal('/'.join(url_l))
201 return literal('/'.join(url_l))
202
202
203 files_breadcrumbs = _FilesBreadCrumbs()
203 files_breadcrumbs = _FilesBreadCrumbs()
204
204
205 class CodeHtmlFormatter(HtmlFormatter):
205 class CodeHtmlFormatter(HtmlFormatter):
206 """My code Html Formatter for source codes
206 """My code Html Formatter for source codes
207 """
207 """
208
208
209 def wrap(self, source, outfile):
209 def wrap(self, source, outfile):
210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
210 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
211
211
212 def _wrap_code(self, source):
212 def _wrap_code(self, source):
213 for cnt, it in enumerate(source):
213 for cnt, it in enumerate(source):
214 i, t = it
214 i, t = it
215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
215 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
216 yield i, t
216 yield i, t
217
217
218 def _wrap_tablelinenos(self, inner):
218 def _wrap_tablelinenos(self, inner):
219 dummyoutfile = StringIO.StringIO()
219 dummyoutfile = StringIO.StringIO()
220 lncount = 0
220 lncount = 0
221 for t, line in inner:
221 for t, line in inner:
222 if t:
222 if t:
223 lncount += 1
223 lncount += 1
224 dummyoutfile.write(line)
224 dummyoutfile.write(line)
225
225
226 fl = self.linenostart
226 fl = self.linenostart
227 mw = len(str(lncount + fl - 1))
227 mw = len(str(lncount + fl - 1))
228 sp = self.linenospecial
228 sp = self.linenospecial
229 st = self.linenostep
229 st = self.linenostep
230 la = self.lineanchors
230 la = self.lineanchors
231 aln = self.anchorlinenos
231 aln = self.anchorlinenos
232 nocls = self.noclasses
232 nocls = self.noclasses
233 if sp:
233 if sp:
234 lines = []
234 lines = []
235
235
236 for i in range(fl, fl + lncount):
236 for i in range(fl, fl + lncount):
237 if i % st == 0:
237 if i % st == 0:
238 if i % sp == 0:
238 if i % sp == 0:
239 if aln:
239 if aln:
240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
240 lines.append('<a href="#%s%d" class="special">%*d</a>' %
241 (la, i, mw, i))
241 (la, i, mw, i))
242 else:
242 else:
243 lines.append('<span class="special">%*d</span>' % (mw, i))
243 lines.append('<span class="special">%*d</span>' % (mw, i))
244 else:
244 else:
245 if aln:
245 if aln:
246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
246 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
247 else:
247 else:
248 lines.append('%*d' % (mw, i))
248 lines.append('%*d' % (mw, i))
249 else:
249 else:
250 lines.append('')
250 lines.append('')
251 ls = '\n'.join(lines)
251 ls = '\n'.join(lines)
252 else:
252 else:
253 lines = []
253 lines = []
254 for i in range(fl, fl + lncount):
254 for i in range(fl, fl + lncount):
255 if i % st == 0:
255 if i % st == 0:
256 if aln:
256 if aln:
257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
257 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
258 else:
258 else:
259 lines.append('%*d' % (mw, i))
259 lines.append('%*d' % (mw, i))
260 else:
260 else:
261 lines.append('')
261 lines.append('')
262 ls = '\n'.join(lines)
262 ls = '\n'.join(lines)
263
263
264 # in case you wonder about the seemingly redundant <div> here: since the
264 # in case you wonder about the seemingly redundant <div> here: since the
265 # content in the other cell also is wrapped in a div, some browsers in
265 # content in the other cell also is wrapped in a div, some browsers in
266 # some configurations seem to mess up the formatting...
266 # some configurations seem to mess up the formatting...
267 if nocls:
267 if nocls:
268 yield 0, ('<table class="%stable">' % self.cssclass +
268 yield 0, ('<table class="%stable">' % self.cssclass +
269 '<tr><td><div class="linenodiv" '
269 '<tr><td><div class="linenodiv" '
270 'style="background-color: #f0f0f0; padding-right: 10px">'
270 'style="background-color: #f0f0f0; padding-right: 10px">'
271 '<pre style="line-height: 125%">' +
271 '<pre style="line-height: 125%">' +
272 ls + '</pre></div></td><td class="code">')
272 ls + '</pre></div></td><td class="code">')
273 else:
273 else:
274 yield 0, ('<table class="%stable">' % self.cssclass +
274 yield 0, ('<table class="%stable">' % self.cssclass +
275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
275 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
276 ls + '</pre></div></td><td class="code">')
276 ls + '</pre></div></td><td class="code">')
277 yield 0, dummyoutfile.getvalue()
277 yield 0, dummyoutfile.getvalue()
278 yield 0, '</td></tr></table>'
278 yield 0, '</td></tr></table>'
279
279
280
280
281 def pygmentize(filenode, **kwargs):
281 def pygmentize(filenode, **kwargs):
282 """pygmentize function using pygments
282 """pygmentize function using pygments
283
283
284 :param filenode:
284 :param filenode:
285 """
285 """
286
286
287 return literal(code_highlight(filenode.content,
287 return literal(code_highlight(filenode.content,
288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
288 filenode.lexer, CodeHtmlFormatter(**kwargs)))
289
289
290 def pygmentize_annotation(filenode, **kwargs):
290 def pygmentize_annotation(filenode, **kwargs):
291 """pygmentize function for annotation
291 """pygmentize function for annotation
292
292
293 :param filenode:
293 :param filenode:
294 """
294 """
295
295
296 color_dict = {}
296 color_dict = {}
297 def gen_color(n=10000):
297 def gen_color(n=10000):
298 """generator for getting n of evenly distributed colors using
298 """generator for getting n of evenly distributed colors using
299 hsv color and golden ratio. It always return same order of colors
299 hsv color and golden ratio. It always return same order of colors
300
300
301 :returns: RGB tuple
301 :returns: RGB tuple
302 """
302 """
303 import colorsys
303 import colorsys
304 golden_ratio = 0.618033988749895
304 golden_ratio = 0.618033988749895
305 h = 0.22717784590367374
305 h = 0.22717784590367374
306
306
307 for c in xrange(n):
307 for c in xrange(n):
308 h += golden_ratio
308 h += golden_ratio
309 h %= 1
309 h %= 1
310 HSV_tuple = [h, 0.95, 0.95]
310 HSV_tuple = [h, 0.95, 0.95]
311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
311 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
312 yield map(lambda x:str(int(x * 256)), RGB_tuple)
313
313
314 cgenerator = gen_color()
314 cgenerator = gen_color()
315
315
316 def get_color_string(cs):
316 def get_color_string(cs):
317 if color_dict.has_key(cs):
317 if color_dict.has_key(cs):
318 col = color_dict[cs]
318 col = color_dict[cs]
319 else:
319 else:
320 col = color_dict[cs] = cgenerator.next()
320 col = color_dict[cs] = cgenerator.next()
321 return "color: rgb(%s)! important;" % (', '.join(col))
321 return "color: rgb(%s)! important;" % (', '.join(col))
322
322
323 def url_func(changeset):
323 def url_func(changeset):
324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
324 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
326
326
327 tooltip_html = tooltip_html % (changeset.author,
327 tooltip_html = tooltip_html % (changeset.author,
328 changeset.date,
328 changeset.date,
329 tooltip(changeset.message))
329 tooltip(changeset.message))
330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
330 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
331 short_id(changeset.raw_id))
331 short_id(changeset.raw_id))
332 uri = link_to(
332 uri = link_to(
333 lnk_format,
333 lnk_format,
334 url('changeset_home', repo_name=changeset.repository.name,
334 url('changeset_home', repo_name=changeset.repository.name,
335 revision=changeset.raw_id),
335 revision=changeset.raw_id),
336 style=get_color_string(changeset.raw_id),
336 style=get_color_string(changeset.raw_id),
337 class_='tooltip',
337 class_='tooltip',
338 title=tooltip_html
338 title=tooltip_html
339 )
339 )
340
340
341 uri += '\n'
341 uri += '\n'
342 return uri
342 return uri
343 return literal(annotate_highlight(filenode, url_func, **kwargs))
343 return literal(annotate_highlight(filenode, url_func, **kwargs))
344
344
345 def get_changeset_safe(repo, rev):
345 def get_changeset_safe(repo, rev):
346 from vcs.backends.base import BaseRepository
346 from vcs.backends.base import BaseRepository
347 from vcs.exceptions import RepositoryError
347 from vcs.exceptions import RepositoryError
348 if not isinstance(repo, BaseRepository):
348 if not isinstance(repo, BaseRepository):
349 raise Exception('You must pass an Repository '
349 raise Exception('You must pass an Repository '
350 'object as first argument got %s', type(repo))
350 'object as first argument got %s', type(repo))
351
351
352 try:
352 try:
353 cs = repo.get_changeset(rev)
353 cs = repo.get_changeset(rev)
354 except RepositoryError:
354 except RepositoryError:
355 from rhodecode.lib.utils import EmptyChangeset
355 from rhodecode.lib.utils import EmptyChangeset
356 cs = EmptyChangeset()
356 cs = EmptyChangeset()
357 return cs
357 return cs
358
358
359
359
360 def is_following_repo(repo_name, user_id):
360 def is_following_repo(repo_name, user_id):
361 from rhodecode.model.scm import ScmModel
361 from rhodecode.model.scm import ScmModel
362 return ScmModel().is_following_repo(repo_name, user_id)
362 return ScmModel().is_following_repo(repo_name, user_id)
363
363
364 flash = _Flash()
364 flash = _Flash()
365
365
366
366
367 #==============================================================================
367 #==============================================================================
368 # MERCURIAL FILTERS available via h.
368 # MERCURIAL FILTERS available via h.
369 #==============================================================================
369 #==============================================================================
370 from mercurial import util
370 from mercurial import util
371 from mercurial.templatefilters import person as _person
371 from mercurial.templatefilters import person as _person
372
372
373 def _age(curdate):
373 def _age(curdate):
374 """turns a datetime into an age string."""
374 """turns a datetime into an age string."""
375
375
376 if not curdate:
376 if not curdate:
377 return ''
377 return ''
378
378
379 from datetime import timedelta, datetime
379 from datetime import timedelta, datetime
380
380
381 agescales = [("year", 3600 * 24 * 365),
381 agescales = [("year", 3600 * 24 * 365),
382 ("month", 3600 * 24 * 30),
382 ("month", 3600 * 24 * 30),
383 ("day", 3600 * 24),
383 ("day", 3600 * 24),
384 ("hour", 3600),
384 ("hour", 3600),
385 ("minute", 60),
385 ("minute", 60),
386 ("second", 1), ]
386 ("second", 1), ]
387
387
388 age = datetime.now() - curdate
388 age = datetime.now() - curdate
389 age_seconds = (age.days * agescales[2][1]) + age.seconds
389 age_seconds = (age.days * agescales[2][1]) + age.seconds
390 pos = 1
390 pos = 1
391 for scale in agescales:
391 for scale in agescales:
392 if scale[1] <= age_seconds:
392 if scale[1] <= age_seconds:
393 if pos == 6:pos = 5
393 if pos == 6:pos = 5
394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
394 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
395 pos += 1
395 pos += 1
396
396
397 return _('just now')
397 return _('just now')
398
398
399 age = lambda x:_age(x)
399 age = lambda x:_age(x)
400 capitalize = lambda x: x.capitalize()
400 capitalize = lambda x: x.capitalize()
401 email = util.email
401 email = util.email
402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
402 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
403 person = lambda x: _person(x)
403 person = lambda x: _person(x)
404 short_id = lambda x: x[:12]
404 short_id = lambda x: x[:12]
405
405
406
406
407 def bool2icon(value):
407 def bool2icon(value):
408 """Returns True/False values represented as small html image of true/false
408 """Returns True/False values represented as small html image of true/false
409 icons
409 icons
410
410
411 :param value: bool value
411 :param value: bool value
412 """
412 """
413
413
414 if value is True:
414 if value is True:
415 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
415 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
416
416
417 if value is False:
417 if value is False:
418 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
418 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
419
419
420 return value
420 return value
421
421
422
422
423 def action_parser(user_log):
423 def action_parser(user_log):
424 """This helper will map the specified string action into translated
424 """This helper will map the specified string action into translated
425 fancy names with icons and links
425 fancy names with icons and links
426
426
427 :param user_log: user log instance
427 :param user_log: user log instance
428 """
428 """
429
429
430 action = user_log.action
430 action = user_log.action
431 action_params = ' '
431 action_params = ' '
432
432
433 x = action.split(':')
433 x = action.split(':')
434
434
435 if len(x) > 1:
435 if len(x) > 1:
436 action, action_params = x
436 action, action_params = x
437
437
438 def get_cs_links():
438 def get_cs_links():
439 revs_limit = 5 #display this amount always
439 revs_limit = 5 #display this amount always
440 revs_top_limit = 50 #show upto this amount of changesets hidden
440 revs_top_limit = 50 #show upto this amount of changesets hidden
441 revs = action_params.split(',')
441 revs = action_params.split(',')
442 repo_name = user_log.repository.repo_name
442 repo_name = user_log.repository.repo_name
443 from rhodecode.model.scm import ScmModel
443 from rhodecode.model.scm import ScmModel
444
444
445 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
445 message = lambda rev: get_changeset_safe(ScmModel().get(repo_name),
446 rev).message
446 rev).message
447
447
448 cs_links = " " + ', '.join ([link_to(rev,
448 cs_links = " " + ', '.join ([link_to(rev,
449 url('changeset_home',
449 url('changeset_home',
450 repo_name=repo_name,
450 repo_name=repo_name,
451 revision=rev), title=tooltip(message(rev)),
451 revision=rev), title=tooltip(message(rev)),
452 class_='tooltip') for rev in revs[:revs_limit] ])
452 class_='tooltip') for rev in revs[:revs_limit] ])
453
453
454 compare_view = (' <div class="compare_view tooltip" title="%s">'
454 compare_view = (' <div class="compare_view tooltip" title="%s">'
455 '<a href="%s">%s</a> '
455 '<a href="%s">%s</a> '
456 '</div>' % (_('Show all combined changesets %s->%s' \
456 '</div>' % (_('Show all combined changesets %s->%s' \
457 % (revs[0], revs[-1])),
457 % (revs[0], revs[-1])),
458 url('changeset_home', repo_name=repo_name,
458 url('changeset_home', repo_name=repo_name,
459 revision='%s...%s' % (revs[0], revs[-1])
459 revision='%s...%s' % (revs[0], revs[-1])
460 ),
460 ),
461 _('compare view'))
461 _('compare view'))
462 )
462 )
463
463
464 if len(revs) > revs_limit:
464 if len(revs) > revs_limit:
465 uniq_id = revs[0]
465 uniq_id = revs[0]
466 html_tmpl = ('<span> %s '
466 html_tmpl = ('<span> %s '
467 '<a class="show_more" id="_%s" href="#more">%s</a> '
467 '<a class="show_more" id="_%s" href="#more">%s</a> '
468 '%s</span>')
468 '%s</span>')
469 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
469 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
470 % (len(revs) - revs_limit),
470 % (len(revs) - revs_limit),
471 _('revisions'))
471 _('revisions'))
472
472
473 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
473 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
474 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
474 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
475 url('changeset_home',
475 url('changeset_home',
476 repo_name=repo_name, revision=rev),
476 repo_name=repo_name, revision=rev),
477 title=message(rev), class_='tooltip')
477 title=message(rev), class_='tooltip')
478 for rev in revs[revs_limit:revs_top_limit]]))
478 for rev in revs[revs_limit:revs_top_limit]]))
479 if len(revs) > 1:
479 cs_links += compare_view
480 cs_links += compare_view
480 return cs_links
481 return cs_links
481
482
482 def get_fork_name():
483 def get_fork_name():
483 from rhodecode.model.scm import ScmModel
484 from rhodecode.model.scm import ScmModel
484 repo_name = action_params
485 repo_name = action_params
485 repo = ScmModel().get(repo_name)
486 repo = ScmModel().get(repo_name)
486 if repo is None:
487 if repo is None:
487 return repo_name
488 return repo_name
488 return link_to(action_params, url('summary_home',
489 return link_to(action_params, url('summary_home',
489 repo_name=repo.name,),
490 repo_name=repo.name,),
490 title=repo.dbrepo.description)
491 title=repo.dbrepo.description)
491
492
492 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
493 map = {'user_deleted_repo':(_('User [deleted] repository'), None),
493 'user_created_repo':(_('User [created] repository'), None),
494 'user_created_repo':(_('User [created] repository'), None),
494 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
495 'user_forked_repo':(_('User [forked] repository as:'), get_fork_name),
495 'user_updated_repo':(_('User [updated] repository'), None),
496 'user_updated_repo':(_('User [updated] repository'), None),
496 'admin_deleted_repo':(_('Admin [delete] repository'), None),
497 'admin_deleted_repo':(_('Admin [delete] repository'), None),
497 'admin_created_repo':(_('Admin [created] repository'), None),
498 'admin_created_repo':(_('Admin [created] repository'), None),
498 'admin_forked_repo':(_('Admin [forked] repository'), None),
499 'admin_forked_repo':(_('Admin [forked] repository'), None),
499 'admin_updated_repo':(_('Admin [updated] repository'), None),
500 'admin_updated_repo':(_('Admin [updated] repository'), None),
500 'push':(_('[Pushed]'), get_cs_links),
501 'push':(_('[Pushed]'), get_cs_links),
501 'pull':(_('[Pulled]'), None),
502 'pull':(_('[Pulled]'), None),
502 'started_following_repo':(_('User [started following] repository'), None),
503 'started_following_repo':(_('User [started following] repository'), None),
503 'stopped_following_repo':(_('User [stopped following] repository'), None),
504 'stopped_following_repo':(_('User [stopped following] repository'), None),
504 }
505 }
505
506
506 action_str = map.get(action, action)
507 action_str = map.get(action, action)
507 action = action_str[0].replace('[', '<span class="journal_highlight">')\
508 action = action_str[0].replace('[', '<span class="journal_highlight">')\
508 .replace(']', '</span>')
509 .replace(']', '</span>')
509 if action_str[1] is not None:
510 if action_str[1] is not None:
510 action = action + " " + action_str[1]()
511 action = action + " " + action_str[1]()
511
512
512 return literal(action)
513 return literal(action)
513
514
514 def action_parser_icon(user_log):
515 def action_parser_icon(user_log):
515 action = user_log.action
516 action = user_log.action
516 action_params = None
517 action_params = None
517 x = action.split(':')
518 x = action.split(':')
518
519
519 if len(x) > 1:
520 if len(x) > 1:
520 action, action_params = x
521 action, action_params = x
521
522
522 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
523 tmpl = """<img src="/images/icons/%s" alt="%s"/>"""
523 map = {'user_deleted_repo':'database_delete.png',
524 map = {'user_deleted_repo':'database_delete.png',
524 'user_created_repo':'database_add.png',
525 'user_created_repo':'database_add.png',
525 'user_forked_repo':'arrow_divide.png',
526 'user_forked_repo':'arrow_divide.png',
526 'user_updated_repo':'database_edit.png',
527 'user_updated_repo':'database_edit.png',
527 'admin_deleted_repo':'database_delete.png',
528 'admin_deleted_repo':'database_delete.png',
528 'admin_created_repo':'database_add.png',
529 'admin_created_repo':'database_add.png',
529 'admin_forked_repo':'arrow_divide.png',
530 'admin_forked_repo':'arrow_divide.png',
530 'admin_updated_repo':'database_edit.png',
531 'admin_updated_repo':'database_edit.png',
531 'push':'script_add.png',
532 'push':'script_add.png',
532 'pull':'down_16.png',
533 'pull':'down_16.png',
533 'started_following_repo':'heart_add.png',
534 'started_following_repo':'heart_add.png',
534 'stopped_following_repo':'heart_delete.png',
535 'stopped_following_repo':'heart_delete.png',
535 }
536 }
536 return literal(tmpl % (map.get(action, action), action))
537 return literal(tmpl % (map.get(action, action), action))
537
538
538
539
539 #==============================================================================
540 #==============================================================================
540 # PERMS
541 # PERMS
541 #==============================================================================
542 #==============================================================================
542 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
543 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
543 HasRepoPermissionAny, HasRepoPermissionAll
544 HasRepoPermissionAny, HasRepoPermissionAll
544
545
545 #==============================================================================
546 #==============================================================================
546 # GRAVATAR URL
547 # GRAVATAR URL
547 #==============================================================================
548 #==============================================================================
548 import hashlib
549 import hashlib
549 import urllib
550 import urllib
550 from pylons import request
551 from pylons import request
551
552
552 def gravatar_url(email_address, size=30):
553 def gravatar_url(email_address, size=30):
553 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
554 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
554 default = 'identicon'
555 default = 'identicon'
555 baseurl_nossl = "http://www.gravatar.com/avatar/"
556 baseurl_nossl = "http://www.gravatar.com/avatar/"
556 baseurl_ssl = "https://secure.gravatar.com/avatar/"
557 baseurl_ssl = "https://secure.gravatar.com/avatar/"
557 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
558 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
558
559
559
560
560 # construct the url
561 # construct the url
561 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
562 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
562 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
563 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
563
564
564 return gravatar_url
565 return gravatar_url
565
566
566 def safe_unicode(str):
567 def safe_unicode(str):
567 """safe unicode function. In case of UnicodeDecode error we try to return
568 """safe unicode function. In case of UnicodeDecode error we try to return
568 unicode with errors replace, if this failes we return unicode with
569 unicode with errors replace, if this failes we return unicode with
569 string_escape decoding """
570 string_escape decoding """
570
571
571 try:
572 try:
572 u_str = unicode(str)
573 u_str = unicode(str)
573 except UnicodeDecodeError:
574 except UnicodeDecodeError:
574 try:
575 try:
575 u_str = unicode(str, 'utf-8', 'replace')
576 u_str = unicode(str, 'utf-8', 'replace')
576 except UnicodeDecodeError:
577 except UnicodeDecodeError:
577 #incase we have a decode error just represent as byte string
578 #incase we have a decode error just represent as byte string
578 u_str = unicode(str(str).encode('string_escape'))
579 u_str = unicode(str(str).encode('string_escape'))
579
580
580 return u_str
581 return u_str
581
582
582 def changed_tooltip(nodes):
583 def changed_tooltip(nodes):
583 if nodes:
584 if nodes:
584 pref = ': <br/> '
585 pref = ': <br/> '
585 suf = ''
586 suf = ''
586 if len(nodes) > 30:
587 if len(nodes) > 30:
587 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
588 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
588 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
589 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
589 else:
590 else:
590 return ': ' + _('No Files')
591 return ': ' + _('No Files')
General Comments 0
You need to be logged in to leave comments. Login now