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