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