##// END OF EJS Templates
wrap_paragraph was to slow for toolip generation and was removed.
marcink -
r1352:79041f2d beta
parent child Browse files
Show More
@@ -1,696 +1,699 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 import StringIO
9 9 import urllib
10 10
11 11 from datetime import datetime
12 12 from pygments.formatters import HtmlFormatter
13 13 from pygments import highlight as code_highlight
14 14 from pylons import url, request, config
15 15 from pylons.i18n.translation import _, ungettext
16 16
17 17 from webhelpers.html import literal, HTML, escape
18 18 from webhelpers.html.tools import *
19 19 from webhelpers.html.builder import make_tag
20 20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 23 password, textarea, title, ul, xml_declaration, radio
24 24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 25 mail_to, strip_links, strip_tags, tag_re
26 26 from webhelpers.number import format_byte_size, format_bit_size
27 27 from webhelpers.pylonslib import Flash as _Flash
28 28 from webhelpers.pylonslib.secure_form import secure_form
29 29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 32 from webhelpers.date import time_ago_in_words
33 33 from webhelpers.paginate import Page
34 34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 35 convert_boolean_attrs, NotGiven
36 36
37 37 from vcs.utils.annotate import annotate_highlight
38 38 from rhodecode.lib.utils import repo_name_slug
39 39 from rhodecode.lib import str2bool, safe_unicode
40 40
41 41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 42 """
43 43 Reset button
44 44 """
45 45 _set_input_attrs(attrs, type, name, value)
46 46 _set_id_attr(attrs, id, name)
47 47 convert_boolean_attrs(attrs, ["disabled"])
48 48 return HTML.input(**attrs)
49 49
50 50 reset = _reset
51 51
52 52
53 53 def get_token():
54 54 """Return the current authentication token, creating one if one doesn't
55 55 already exist.
56 56 """
57 57 token_key = "_authentication_token"
58 58 from pylons import session
59 59 if not token_key in session:
60 60 try:
61 61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 62 except AttributeError: # Python < 2.4
63 63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 64 session[token_key] = token
65 65 if hasattr(session, 'save'):
66 66 session.save()
67 67 return session[token_key]
68 68
69 69 class _GetError(object):
70 70 """Get error from form_errors, and represent it as span wrapped error
71 71 message
72 72
73 73 :param field_name: field to fetch errors for
74 74 :param form_errors: form errors dict
75 75 """
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 class _ToolTip(object):
85 85
86 86 def __call__(self, tooltip_title, trim_at=50):
87 87 """Special function just to wrap our text into nice formatted
88 88 autowrapped text
89 89
90 90 :param tooltip_title:
91 91 """
92 92
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 .replace('\n', '<br/>')
93 return escape(tooltip_title)
95 94
96 95 def activate(self):
97 96 """Adds tooltip mechanism to the given Html all tooltips have to have
98 97 set class `tooltip` and set attribute `tooltip_title`.
99 98 Then a tooltip will be generated based on that. All with yui js tooltip
100 99 """
101 100
102 101 js = '''
103 102 YAHOO.util.Event.onDOMReady(function(){
104 103 function toolTipsId(){
105 104 var ids = [];
106 105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107 106
108 107 for (var i = 0; i < tts.length; i++) {
109 108 //if element doesn't not have and id autogenerate one for tooltip
110 109
111 110 if (!tts[i].id){
112 111 tts[i].id='tt'+i*100;
113 112 }
114 113 ids.push(tts[i].id);
115 114 }
116 115 return ids
117 116 };
118 117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
120 119 monitorresize:false,
121 120 xyoffset :[0,0],
122 121 autodismissdelay:300000,
123 122 hidedelay:5,
124 123 showdelay:20,
125 124 });
126 125
127 126 });
128 127 '''
129 128 return literal(js)
130 129
131 130 tooltip = _ToolTip()
132 131
133 132 class _FilesBreadCrumbs(object):
134 133
135 134 def __call__(self, repo_name, rev, paths):
136 135 if isinstance(paths, str):
137 136 paths = safe_unicode(paths)
138 137 url_l = [link_to(repo_name, url('files_home',
139 138 repo_name=repo_name,
140 139 revision=rev, f_path=''))]
141 140 paths_l = paths.split('/')
142 141 for cnt, p in enumerate(paths_l):
143 142 if p != '':
144 143 url_l.append(link_to(p, url('files_home',
145 144 repo_name=repo_name,
146 145 revision=rev,
147 146 f_path='/'.join(paths_l[:cnt + 1]))))
148 147
149 148 return literal('/'.join(url_l))
150 149
151 150 files_breadcrumbs = _FilesBreadCrumbs()
152 151
153 152 class CodeHtmlFormatter(HtmlFormatter):
154 153 """My code Html Formatter for source codes
155 154 """
156 155
157 156 def wrap(self, source, outfile):
158 157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
159 158
160 159 def _wrap_code(self, source):
161 160 for cnt, it in enumerate(source):
162 161 i, t = it
163 162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
164 163 yield i, t
165 164
166 165 def _wrap_tablelinenos(self, inner):
167 166 dummyoutfile = StringIO.StringIO()
168 167 lncount = 0
169 168 for t, line in inner:
170 169 if t:
171 170 lncount += 1
172 171 dummyoutfile.write(line)
173 172
174 173 fl = self.linenostart
175 174 mw = len(str(lncount + fl - 1))
176 175 sp = self.linenospecial
177 176 st = self.linenostep
178 177 la = self.lineanchors
179 178 aln = self.anchorlinenos
180 179 nocls = self.noclasses
181 180 if sp:
182 181 lines = []
183 182
184 183 for i in range(fl, fl + lncount):
185 184 if i % st == 0:
186 185 if i % sp == 0:
187 186 if aln:
188 187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
189 188 (la, i, mw, i))
190 189 else:
191 190 lines.append('<span class="special">%*d</span>' % (mw, i))
192 191 else:
193 192 if aln:
194 193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
195 194 else:
196 195 lines.append('%*d' % (mw, i))
197 196 else:
198 197 lines.append('')
199 198 ls = '\n'.join(lines)
200 199 else:
201 200 lines = []
202 201 for i in range(fl, fl + lncount):
203 202 if i % st == 0:
204 203 if aln:
205 204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
206 205 else:
207 206 lines.append('%*d' % (mw, i))
208 207 else:
209 208 lines.append('')
210 209 ls = '\n'.join(lines)
211 210
212 211 # in case you wonder about the seemingly redundant <div> here: since the
213 212 # content in the other cell also is wrapped in a div, some browsers in
214 213 # some configurations seem to mess up the formatting...
215 214 if nocls:
216 215 yield 0, ('<table class="%stable">' % self.cssclass +
217 216 '<tr><td><div class="linenodiv" '
218 217 'style="background-color: #f0f0f0; padding-right: 10px">'
219 218 '<pre style="line-height: 125%">' +
220 219 ls + '</pre></div></td><td id="hlcode" class="code">')
221 220 else:
222 221 yield 0, ('<table class="%stable">' % self.cssclass +
223 222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
224 223 ls + '</pre></div></td><td id="hlcode" class="code">')
225 224 yield 0, dummyoutfile.getvalue()
226 225 yield 0, '</td></tr></table>'
227 226
228 227
229 228 def pygmentize(filenode, **kwargs):
230 229 """pygmentize function using pygments
231 230
232 231 :param filenode:
233 232 """
234 233
235 234 return literal(code_highlight(filenode.content,
236 235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
237 236
238 237 def pygmentize_annotation(repo_name, filenode, **kwargs):
239 238 """pygmentize function for annotation
240 239
241 240 :param filenode:
242 241 """
243 242
244 243 color_dict = {}
245 244 def gen_color(n=10000):
246 245 """generator for getting n of evenly distributed colors using
247 246 hsv color and golden ratio. It always return same order of colors
248 247
249 248 :returns: RGB tuple
250 249 """
251 250 import colorsys
252 251 golden_ratio = 0.618033988749895
253 252 h = 0.22717784590367374
254 253
255 254 for _ in xrange(n):
256 255 h += golden_ratio
257 256 h %= 1
258 257 HSV_tuple = [h, 0.95, 0.95]
259 258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
260 259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
261 260
262 261 cgenerator = gen_color()
263 262
264 263 def get_color_string(cs):
265 264 if color_dict.has_key(cs):
266 265 col = color_dict[cs]
267 266 else:
268 267 col = color_dict[cs] = cgenerator.next()
269 268 return "color: rgb(%s)! important;" % (', '.join(col))
270 269
271 270 def url_func(repo_name):
271
272 272 def _url_func(changeset):
273 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
274 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
273 author = changeset.author
274 date = changeset.date
275 message = tooltip(changeset.message)
275 276
276 tooltip_html = tooltip_html % (changeset.author,
277 changeset.date,
278 tooltip(changeset.message))
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 "</b> %s<br/></div>")
280
281 tooltip_html = tooltip_html % (author, date, message)
279 282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
280 283 short_id(changeset.raw_id))
281 284 uri = link_to(
282 285 lnk_format,
283 286 url('changeset_home', repo_name=repo_name,
284 287 revision=changeset.raw_id),
285 288 style=get_color_string(changeset.raw_id),
286 289 class_='tooltip',
287 290 title=tooltip_html
288 291 )
289 292
290 293 uri += '\n'
291 294 return uri
292 295 return _url_func
293 296
294 297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
295 298
296 299 def get_changeset_safe(repo, rev):
297 300 from vcs.backends.base import BaseRepository
298 301 from vcs.exceptions import RepositoryError
299 302 if not isinstance(repo, BaseRepository):
300 303 raise Exception('You must pass an Repository '
301 304 'object as first argument got %s', type(repo))
302 305
303 306 try:
304 307 cs = repo.get_changeset(rev)
305 308 except RepositoryError:
306 309 from rhodecode.lib.utils import EmptyChangeset
307 310 cs = EmptyChangeset()
308 311 return cs
309 312
310 313
311 314 def is_following_repo(repo_name, user_id):
312 315 from rhodecode.model.scm import ScmModel
313 316 return ScmModel().is_following_repo(repo_name, user_id)
314 317
315 318 flash = _Flash()
316 319
317 320
318 321 #==============================================================================
319 322 # MERCURIAL FILTERS available via h.
320 323 #==============================================================================
321 324 from mercurial import util
322 325 from mercurial.templatefilters import person as _person
323 326 from rhodecode.lib import credentials_hidder, age as _age
324 327
325 328
326 329
327 330 age = lambda x:_age(x)
328 331 capitalize = lambda x: x.capitalize()
329 332 email = util.email
330 333 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
331 334 person = lambda x: _person(x)
332 335 short_id = lambda x: x[:12]
333 336 hide_credentials = lambda x: ''.join(credentials_hidder(x))
334 337
335 338 def bool2icon(value):
336 339 """Returns True/False values represented as small html image of true/false
337 340 icons
338 341
339 342 :param value: bool value
340 343 """
341 344
342 345 if value is True:
343 346 return HTML.tag('img', src=url("/images/icons/accept.png"),
344 347 alt=_('True'))
345 348
346 349 if value is False:
347 350 return HTML.tag('img', src=url("/images/icons/cancel.png"),
348 351 alt=_('False'))
349 352
350 353 return value
351 354
352 355
353 356 def action_parser(user_log, feed=False):
354 357 """This helper will action_map the specified string action into translated
355 358 fancy names with icons and links
356 359
357 360 :param user_log: user log instance
358 361 :param feed: use output for feeds (no html and fancy icons)
359 362 """
360 363
361 364 action = user_log.action
362 365 action_params = ' '
363 366
364 367 x = action.split(':')
365 368
366 369 if len(x) > 1:
367 370 action, action_params = x
368 371
369 372 def get_cs_links():
370 373 revs_limit = 5 #display this amount always
371 374 revs_top_limit = 50 #show upto this amount of changesets hidden
372 375 revs = action_params.split(',')
373 376 repo_name = user_log.repository.repo_name
374 377
375 378 from rhodecode.model.scm import ScmModel
376 379 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
377 380 invalidation_list=[])
378 381
379 382 message = lambda rev: get_changeset_safe(repo, rev).message
380 383
381 384 cs_links = " " + ', '.join ([link_to(rev,
382 385 url('changeset_home',
383 386 repo_name=repo_name,
384 387 revision=rev), title=tooltip(message(rev)),
385 388 class_='tooltip') for rev in revs[:revs_limit] ])
386 389
387 390 compare_view = (' <div class="compare_view tooltip" title="%s">'
388 391 '<a href="%s">%s</a> '
389 392 '</div>' % (_('Show all combined changesets %s->%s' \
390 393 % (revs[0], revs[-1])),
391 394 url('changeset_home', repo_name=repo_name,
392 395 revision='%s...%s' % (revs[0], revs[-1])
393 396 ),
394 397 _('compare view'))
395 398 )
396 399
397 400 if len(revs) > revs_limit:
398 401 uniq_id = revs[0]
399 402 html_tmpl = ('<span> %s '
400 403 '<a class="show_more" id="_%s" href="#more">%s</a> '
401 404 '%s</span>')
402 405 if not feed:
403 406 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
404 407 % (len(revs) - revs_limit),
405 408 _('revisions'))
406 409
407 410 if not feed:
408 411 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
409 412 else:
410 413 html_tmpl = '<span id="%s"> %s </span>'
411 414
412 415 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
413 416 url('changeset_home',
414 417 repo_name=repo_name, revision=rev),
415 418 title=message(rev), class_='tooltip')
416 419 for rev in revs[revs_limit:revs_top_limit]]))
417 420 if len(revs) > 1:
418 421 cs_links += compare_view
419 422 return cs_links
420 423
421 424 def get_fork_name():
422 425 repo_name = action_params
423 426 return _('fork name ') + str(link_to(action_params, url('summary_home',
424 427 repo_name=repo_name,)))
425 428
426 429 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
427 430 'user_created_repo':(_('[created] repository'), None),
428 431 'user_forked_repo':(_('[forked] repository'), get_fork_name),
429 432 'user_updated_repo':(_('[updated] repository'), None),
430 433 'admin_deleted_repo':(_('[delete] repository'), None),
431 434 'admin_created_repo':(_('[created] repository'), None),
432 435 'admin_forked_repo':(_('[forked] repository'), None),
433 436 'admin_updated_repo':(_('[updated] repository'), None),
434 437 'push':(_('[pushed] into'), get_cs_links),
435 438 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
436 439 'push_remote':(_('[pulled from remote] into'), get_cs_links),
437 440 'pull':(_('[pulled] from'), None),
438 441 'started_following_repo':(_('[started following] repository'), None),
439 442 'stopped_following_repo':(_('[stopped following] repository'), None),
440 443 }
441 444
442 445 action_str = action_map.get(action, action)
443 446 if feed:
444 447 action = action_str[0].replace('[', '').replace(']', '')
445 448 else:
446 449 action = action_str[0].replace('[', '<span class="journal_highlight">')\
447 450 .replace(']', '</span>')
448 451
449 452 action_params_func = lambda :""
450 453
451 454 if callable(action_str[1]):
452 455 action_params_func = action_str[1]
453 456
454 457 return [literal(action), action_params_func]
455 458
456 459 def action_parser_icon(user_log):
457 460 action = user_log.action
458 461 action_params = None
459 462 x = action.split(':')
460 463
461 464 if len(x) > 1:
462 465 action, action_params = x
463 466
464 467 tmpl = """<img src="%s%s" alt="%s"/>"""
465 468 map = {'user_deleted_repo':'database_delete.png',
466 469 'user_created_repo':'database_add.png',
467 470 'user_forked_repo':'arrow_divide.png',
468 471 'user_updated_repo':'database_edit.png',
469 472 'admin_deleted_repo':'database_delete.png',
470 473 'admin_created_repo':'database_add.png',
471 474 'admin_forked_repo':'arrow_divide.png',
472 475 'admin_updated_repo':'database_edit.png',
473 476 'push':'script_add.png',
474 477 'push_local':'script_edit.png',
475 478 'push_remote':'connect.png',
476 479 'pull':'down_16.png',
477 480 'started_following_repo':'heart_add.png',
478 481 'stopped_following_repo':'heart_delete.png',
479 482 }
480 483 return literal(tmpl % ((url('/images/icons/')),
481 484 map.get(action, action), action))
482 485
483 486
484 487 #==============================================================================
485 488 # PERMS
486 489 #==============================================================================
487 490 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
488 491 HasRepoPermissionAny, HasRepoPermissionAll
489 492
490 493 #==============================================================================
491 494 # GRAVATAR URL
492 495 #==============================================================================
493 496
494 497 def gravatar_url(email_address, size=30):
495 498 if not str2bool(config['app_conf'].get('use_gravatar')) or \
496 499 email_address == 'anonymous@rhodecode.org':
497 500 return "/images/user%s.png" % size
498 501
499 502 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
500 503 default = 'identicon'
501 504 baseurl_nossl = "http://www.gravatar.com/avatar/"
502 505 baseurl_ssl = "https://secure.gravatar.com/avatar/"
503 506 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
504 507
505 508 if isinstance(email_address, unicode):
506 509 #hashlib crashes on unicode items
507 510 email_address = email_address.encode('utf8', 'replace')
508 511 # construct the url
509 512 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
510 513 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
511 514
512 515 return gravatar_url
513 516
514 517
515 518 #==============================================================================
516 519 # REPO PAGER, PAGER FOR REPOSITORY
517 520 #==============================================================================
518 521 class RepoPage(Page):
519 522
520 523 def __init__(self, collection, page=1, items_per_page=20,
521 524 item_count=None, url=None, branch_name=None, **kwargs):
522 525
523 526 """Create a "RepoPage" instance. special pager for paging
524 527 repository
525 528 """
526 529 self._url_generator = url
527 530
528 531 # Safe the kwargs class-wide so they can be used in the pager() method
529 532 self.kwargs = kwargs
530 533
531 534 # Save a reference to the collection
532 535 self.original_collection = collection
533 536
534 537 self.collection = collection
535 538
536 539 # The self.page is the number of the current page.
537 540 # The first page has the number 1!
538 541 try:
539 542 self.page = int(page) # make it int() if we get it as a string
540 543 except (ValueError, TypeError):
541 544 self.page = 1
542 545
543 546 self.items_per_page = items_per_page
544 547
545 548 # Unless the user tells us how many items the collections has
546 549 # we calculate that ourselves.
547 550 if item_count is not None:
548 551 self.item_count = item_count
549 552 else:
550 553 self.item_count = len(self.collection)
551 554
552 555 # Compute the number of the first and last available page
553 556 if self.item_count > 0:
554 557 self.first_page = 1
555 558 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
556 559 self.last_page = self.first_page + self.page_count - 1
557 560
558 561 # Make sure that the requested page number is the range of valid pages
559 562 if self.page > self.last_page:
560 563 self.page = self.last_page
561 564 elif self.page < self.first_page:
562 565 self.page = self.first_page
563 566
564 567 # Note: the number of items on this page can be less than
565 568 # items_per_page if the last page is not full
566 569 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
567 570 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
568 571
569 572 iterator = self.collection.get_changesets(start=self.first_item,
570 573 end=self.last_item,
571 574 reverse=True,
572 575 branch_name=branch_name)
573 576 self.items = list(iterator)
574 577
575 578 # Links to previous and next page
576 579 if self.page > self.first_page:
577 580 self.previous_page = self.page - 1
578 581 else:
579 582 self.previous_page = None
580 583
581 584 if self.page < self.last_page:
582 585 self.next_page = self.page + 1
583 586 else:
584 587 self.next_page = None
585 588
586 589 # No items available
587 590 else:
588 591 self.first_page = None
589 592 self.page_count = 0
590 593 self.last_page = None
591 594 self.first_item = None
592 595 self.last_item = None
593 596 self.previous_page = None
594 597 self.next_page = None
595 598 self.items = []
596 599
597 600 # This is a subclass of the 'list' type. Initialise the list now.
598 601 list.__init__(self, self.items)
599 602
600 603
601 604 def changed_tooltip(nodes):
602 605 """
603 606 Generates a html string for changed nodes in changeset page.
604 607 It limits the output to 30 entries
605 608
606 609 :param nodes: LazyNodesGenerator
607 610 """
608 611 if nodes:
609 612 pref = ': <br/> '
610 613 suf = ''
611 614 if len(nodes) > 30:
612 615 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
613 616 return literal(pref + '<br/> '.join([safe_unicode(x.path)
614 617 for x in nodes[:30]]) + suf)
615 618 else:
616 619 return ': ' + _('No Files')
617 620
618 621
619 622
620 623 def repo_link(groups_and_repos):
621 624 """
622 625 Makes a breadcrumbs link to repo within a group
623 626 joins &raquo; on each group to create a fancy link
624 627
625 628 ex::
626 629 group >> subgroup >> repo
627 630
628 631 :param groups_and_repos:
629 632 """
630 633 groups, repo_name = groups_and_repos
631 634
632 635 if not groups:
633 636 return repo_name
634 637 else:
635 638 def make_link(group):
636 639 return link_to(group.group_name, url('repos_group',
637 640 id=group.group_id))
638 641 return literal(' &raquo; '.join(map(make_link, groups)) + \
639 642 " &raquo; " + repo_name)
640 643
641 644
642 645 def fancy_file_stats(stats):
643 646 """
644 647 Displays a fancy two colored bar for number of added/deleted
645 648 lines of code on file
646 649
647 650 :param stats: two element list of added/deleted lines of code
648 651 """
649 652
650 653 a, d, t = stats[0], stats[1], stats[0] + stats[1]
651 654 width = 100
652 655 unit = float(width) / (t or 1)
653 656
654 657 # needs > 9% of width to be visible or 0 to be hidden
655 658 a_p = max(9, unit * a) if a > 0 else 0
656 659 d_p = max(9, unit * d) if d > 0 else 0
657 660 p_sum = a_p + d_p
658 661
659 662 if p_sum > width:
660 663 #adjust the percentage to be == 100% since we adjusted to 9
661 664 if a_p > d_p:
662 665 a_p = a_p - (p_sum - width)
663 666 else:
664 667 d_p = d_p - (p_sum - width)
665 668
666 669 a_v = a if a > 0 else ''
667 670 d_v = d if d > 0 else ''
668 671
669 672
670 673 def cgen(l_type):
671 674 mapping = {'tr':'top-right-rounded-corner',
672 675 'tl':'top-left-rounded-corner',
673 676 'br':'bottom-right-rounded-corner',
674 677 'bl':'bottom-left-rounded-corner'}
675 678 map_getter = lambda x:mapping[x]
676 679
677 680 if l_type == 'a' and d_v:
678 681 #case when added and deleted are present
679 682 return ' '.join(map(map_getter, ['tl', 'bl']))
680 683
681 684 if l_type == 'a' and not d_v:
682 685 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
683 686
684 687 if l_type == 'd' and a_v:
685 688 return ' '.join(map(map_getter, ['tr', 'br']))
686 689
687 690 if l_type == 'd' and not a_v:
688 691 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
689 692
690 693
691 694
692 695 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
693 696 a_p, a_v)
694 697 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
695 698 d_p, d_v)
696 699 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
General Comments 0
You need to be logged in to leave comments. Login now