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