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