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