##// END OF EJS Templates
garden
marcink -
r1766:60790618 beta
parent child Browse files
Show More
@@ -1,711 +1,717 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 import math
11 11
12 12 from datetime import datetime
13 13 from pygments.formatters.html import HtmlFormatter
14 14 from pygments import highlight as code_highlight
15 15 from pylons import url, request, config
16 16 from pylons.i18n.translation import _, ungettext
17 17
18 18 from webhelpers.html import literal, HTML, escape
19 19 from webhelpers.html.tools import *
20 20 from webhelpers.html.builder import make_tag
21 21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 mail_to, strip_links, strip_tags, tag_re
22 end_form, file, form, hidden, image, javascript_link, link_to, \
23 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
24 submit, text, password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, \
26 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
27 27 from webhelpers.number import format_byte_size, format_bit_size
28 28 from webhelpers.pylonslib import Flash as _Flash
29 29 from webhelpers.pylonslib.secure_form import secure_form
30 30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
31 31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
32 32 replace_whitespace, urlify, truncate, wrap_paragraphs
33 33 from webhelpers.date import time_ago_in_words
34 34 from webhelpers.paginate import Page
35 35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 36 convert_boolean_attrs, NotGiven, _make_safe_id_component
37 37
38 38 from rhodecode.lib.annotate import annotate_highlight
39 39 from rhodecode.lib.utils import repo_name_slug
40 40 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
41 41 from rhodecode.lib.markup_renderer import MarkupRenderer
42 42
43 43 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
44 44 """
45 45 Reset button
46 46 """
47 47 _set_input_attrs(attrs, type, name, value)
48 48 _set_id_attr(attrs, id, name)
49 49 convert_boolean_attrs(attrs, ["disabled"])
50 50 return HTML.input(**attrs)
51 51
52 52 reset = _reset
53 53 safeid = _make_safe_id_component
54 54
55 55 def get_token():
56 56 """Return the current authentication token, creating one if one doesn't
57 57 already exist.
58 58 """
59 59 token_key = "_authentication_token"
60 60 from pylons import session
61 61 if not token_key in session:
62 62 try:
63 63 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
64 64 except AttributeError: # Python < 2.4
65 65 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
66 66 session[token_key] = token
67 67 if hasattr(session, 'save'):
68 68 session.save()
69 69 return session[token_key]
70 70
71 71 class _GetError(object):
72 72 """Get error from form_errors, and represent it as span wrapped error
73 73 message
74 74
75 75 :param field_name: field to fetch errors for
76 76 :param form_errors: form errors dict
77 77 """
78 78
79 79 def __call__(self, field_name, form_errors):
80 80 tmpl = """<span class="error_msg">%s</span>"""
81 81 if form_errors and form_errors.has_key(field_name):
82 82 return literal(tmpl % form_errors.get(field_name))
83 83
84 84 get_error = _GetError()
85 85
86 86 class _ToolTip(object):
87 87
88 88 def __call__(self, tooltip_title, trim_at=50):
89 89 """Special function just to wrap our text into nice formatted
90 90 autowrapped text
91 91
92 92 :param tooltip_title:
93 93 """
94 94 return escape(tooltip_title)
95 95 tooltip = _ToolTip()
96 96
97 97 class _FilesBreadCrumbs(object):
98 98
99 99 def __call__(self, repo_name, rev, paths):
100 100 if isinstance(paths, str):
101 101 paths = safe_unicode(paths)
102 102 url_l = [link_to(repo_name, url('files_home',
103 103 repo_name=repo_name,
104 104 revision=rev, f_path=''))]
105 105 paths_l = paths.split('/')
106 106 for cnt, p in enumerate(paths_l):
107 107 if p != '':
108 url_l.append(link_to(p, url('files_home',
109 repo_name=repo_name,
110 revision=rev,
111 f_path='/'.join(paths_l[:cnt + 1]))))
108 url_l.append(link_to(p,
109 url('files_home',
110 repo_name=repo_name,
111 revision=rev,
112 f_path='/'.join(paths_l[:cnt + 1])
113 )
114 )
115 )
112 116
113 117 return literal('/'.join(url_l))
114 118
115 119 files_breadcrumbs = _FilesBreadCrumbs()
116 120
117 121 class CodeHtmlFormatter(HtmlFormatter):
118 122 """My code Html Formatter for source codes
119 123 """
120 124
121 125 def wrap(self, source, outfile):
122 126 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
123 127
124 128 def _wrap_code(self, source):
125 129 for cnt, it in enumerate(source):
126 130 i, t = it
127 131 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
128 132 yield i, t
129 133
130 134 def _wrap_tablelinenos(self, inner):
131 135 dummyoutfile = StringIO.StringIO()
132 136 lncount = 0
133 137 for t, line in inner:
134 138 if t:
135 139 lncount += 1
136 140 dummyoutfile.write(line)
137 141
138 142 fl = self.linenostart
139 143 mw = len(str(lncount + fl - 1))
140 144 sp = self.linenospecial
141 145 st = self.linenostep
142 146 la = self.lineanchors
143 147 aln = self.anchorlinenos
144 148 nocls = self.noclasses
145 149 if sp:
146 150 lines = []
147 151
148 152 for i in range(fl, fl + lncount):
149 153 if i % st == 0:
150 154 if i % sp == 0:
151 155 if aln:
152 156 lines.append('<a href="#%s%d" class="special">%*d</a>' %
153 157 (la, i, mw, i))
154 158 else:
155 159 lines.append('<span class="special">%*d</span>' % (mw, i))
156 160 else:
157 161 if aln:
158 162 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
159 163 else:
160 164 lines.append('%*d' % (mw, i))
161 165 else:
162 166 lines.append('')
163 167 ls = '\n'.join(lines)
164 168 else:
165 169 lines = []
166 170 for i in range(fl, fl + lncount):
167 171 if i % st == 0:
168 172 if aln:
169 173 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
170 174 else:
171 175 lines.append('%*d' % (mw, i))
172 176 else:
173 177 lines.append('')
174 178 ls = '\n'.join(lines)
175 179
176 180 # in case you wonder about the seemingly redundant <div> here: since the
177 181 # content in the other cell also is wrapped in a div, some browsers in
178 182 # some configurations seem to mess up the formatting...
179 183 if nocls:
180 184 yield 0, ('<table class="%stable">' % self.cssclass +
181 185 '<tr><td><div class="linenodiv" '
182 186 'style="background-color: #f0f0f0; padding-right: 10px">'
183 187 '<pre style="line-height: 125%">' +
184 188 ls + '</pre></div></td><td id="hlcode" class="code">')
185 189 else:
186 190 yield 0, ('<table class="%stable">' % self.cssclass +
187 191 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
188 192 ls + '</pre></div></td><td id="hlcode" class="code">')
189 193 yield 0, dummyoutfile.getvalue()
190 194 yield 0, '</td></tr></table>'
191 195
192 196
193 197 def pygmentize(filenode, **kwargs):
194 198 """pygmentize function using pygments
195 199
196 200 :param filenode:
197 201 """
198 202
199 203 return literal(code_highlight(filenode.content,
200 204 filenode.lexer, CodeHtmlFormatter(**kwargs)))
201 205
202 206 def pygmentize_annotation(repo_name, filenode, **kwargs):
203 207 """pygmentize function for annotation
204 208
205 209 :param filenode:
206 210 """
207 211
208 212 color_dict = {}
209 213 def gen_color(n=10000):
210 214 """generator for getting n of evenly distributed colors using
211 215 hsv color and golden ratio. It always return same order of colors
212 216
213 217 :returns: RGB tuple
214 218 """
215 219
216 220 def hsv_to_rgb(h, s, v):
217 221 if s == 0.0: return v, v, v
218 222 i = int(h * 6.0) # XXX assume int() truncates!
219 223 f = (h * 6.0) - i
220 224 p = v * (1.0 - s)
221 225 q = v * (1.0 - s * f)
222 226 t = v * (1.0 - s * (1.0 - f))
223 227 i = i % 6
224 228 if i == 0: return v, t, p
225 229 if i == 1: return q, v, p
226 230 if i == 2: return p, v, t
227 231 if i == 3: return p, q, v
228 232 if i == 4: return t, p, v
229 233 if i == 5: return v, p, q
230 234
231 235 golden_ratio = 0.618033988749895
232 236 h = 0.22717784590367374
233 237
234 238 for _ in xrange(n):
235 239 h += golden_ratio
236 240 h %= 1
237 241 HSV_tuple = [h, 0.95, 0.95]
238 242 RGB_tuple = hsv_to_rgb(*HSV_tuple)
239 243 yield map(lambda x:str(int(x * 256)), RGB_tuple)
240 244
241 245 cgenerator = gen_color()
242 246
243 247 def get_color_string(cs):
244 248 if color_dict.has_key(cs):
245 249 col = color_dict[cs]
246 250 else:
247 251 col = color_dict[cs] = cgenerator.next()
248 252 return "color: rgb(%s)! important;" % (', '.join(col))
249 253
250 254 def url_func(repo_name):
251 255
252 256 def _url_func(changeset):
253 257 author = changeset.author
254 258 date = changeset.date
255 259 message = tooltip(changeset.message)
256 260
257 261 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
258 262 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
259 263 "</b> %s<br/></div>")
260 264
261 265 tooltip_html = tooltip_html % (author, date, message)
262 266 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
263 267 short_id(changeset.raw_id))
264 268 uri = link_to(
265 269 lnk_format,
266 270 url('changeset_home', repo_name=repo_name,
267 271 revision=changeset.raw_id),
268 272 style=get_color_string(changeset.raw_id),
269 273 class_='tooltip',
270 274 title=tooltip_html
271 275 )
272 276
273 277 uri += '\n'
274 278 return uri
275 279 return _url_func
276 280
277 281 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
278 282
279 283 def is_following_repo(repo_name, user_id):
280 284 from rhodecode.model.scm import ScmModel
281 285 return ScmModel().is_following_repo(repo_name, user_id)
282 286
283 287 flash = _Flash()
284 288
285 289 #==============================================================================
286 290 # SCM FILTERS available via h.
287 291 #==============================================================================
288 292 from vcs.utils import author_name, author_email
289 293 from rhodecode.lib import credentials_filter, age as _age
290 294 from rhodecode.model.db import User
291 295
292 296 age = lambda x:_age(x)
293 297 capitalize = lambda x: x.capitalize()
294 298 email = author_email
295 person = lambda x: author_name(x)
296 299 short_id = lambda x: x[:12]
297 300 hide_credentials = lambda x: ''.join(credentials_filter(x))
298 301
299 302
300 303 def email_or_none(x):
301 304 if email(x) != '':
302 305 return email(x)
303 306
304 307 # See if it contains a username we can get an email from
305 308 user = User.get_by_username(author_name(x), case_insensitive=True,
306 309 cache=True)
307 310 if user is not None:
308 311 return user.email
309 312
310 313 # No valid email, not a valid user in the system, none!
311 314 return None
312 315
313 316 def person(x):
314 317 # Valid email in the attribute passed, see if they're in the system
315 318
316 319 # attr to return from fetched user
317 320 person_getter = lambda usr: usr.username
318 321
319 322 if email(x) != '':
320 323 user = User.get_by_email(email(x), case_insensitive=True, cache=True)
321 324 if user is not None:
322 325 return person_getter(user)
323 326 return email(x)
324 327
325 328 # Maybe it's a username?
326 329 user = User.get_by_username(author_name(x), case_insensitive=True,
327 330 cache=True)
328 331 if user is not None:
329 332 return person_getter(user)
330 333
331 334 # Still nothing? Just pass back the author name then
332 335 return author_name(x)
333 336
334 337 def bool2icon(value):
335 338 """Returns True/False values represented as small html image of true/false
336 339 icons
337 340
338 341 :param value: bool value
339 342 """
340 343
341 344 if value is True:
342 345 return HTML.tag('img', src=url("/images/icons/accept.png"),
343 346 alt=_('True'))
344 347
345 348 if value is False:
346 349 return HTML.tag('img', src=url("/images/icons/cancel.png"),
347 350 alt=_('False'))
348 351
349 352 return value
350 353
351 354
352 355 def action_parser(user_log, feed=False):
353 356 """This helper will action_map the specified string action into translated
354 357 fancy names with icons and links
355 358
356 359 :param user_log: user log instance
357 360 :param feed: use output for feeds (no html and fancy icons)
358 361 """
359 362
360 363 action = user_log.action
361 364 action_params = ' '
362 365
363 366 x = action.split(':')
364 367
365 368 if len(x) > 1:
366 369 action, action_params = x
367 370
368 371 def get_cs_links():
369 372 revs_limit = 3 #display this amount always
370 373 revs_top_limit = 50 #show upto this amount of changesets hidden
371 374 revs = action_params.split(',')
372 375 repo_name = user_log.repository.repo_name
373 376
374 377 from rhodecode.model.scm import ScmModel
375 378 repo = user_log.repository.scm_instance
376 379
377 380 message = lambda rev: get_changeset_safe(repo, rev).message
378 381 cs_links = []
379 382 cs_links.append(" " + ', '.join ([link_to(rev,
380 383 url('changeset_home',
381 384 repo_name=repo_name,
382 385 revision=rev), title=tooltip(message(rev)),
383 386 class_='tooltip') for rev in revs[:revs_limit] ]))
384 387
385 388 compare_view = (' <div class="compare_view tooltip" title="%s">'
386 389 '<a href="%s">%s</a> '
387 390 '</div>' % (_('Show all combined changesets %s->%s' \
388 391 % (revs[0], revs[-1])),
389 392 url('changeset_home', repo_name=repo_name,
390 393 revision='%s...%s' % (revs[0], revs[-1])
391 394 ),
392 395 _('compare view'))
393 396 )
394 397
395 398 if len(revs) > revs_limit:
396 399 uniq_id = revs[0]
397 400 html_tmpl = ('<span> %s '
398 401 '<a class="show_more" id="_%s" href="#more">%s</a> '
399 402 '%s</span>')
400 403 if not feed:
401 404 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
402 405 % (len(revs) - revs_limit),
403 406 _('revisions')))
404 407
405 408 if not feed:
406 409 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
407 410 else:
408 411 html_tmpl = '<span id="%s"> %s </span>'
409 412
410 413 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
411 414 url('changeset_home',
412 415 repo_name=repo_name, revision=rev),
413 416 title=message(rev), class_='tooltip')
414 417 for rev in revs[revs_limit:revs_top_limit]])))
415 418 if len(revs) > 1:
416 419 cs_links.append(compare_view)
417 420 return ''.join(cs_links)
418 421
419 422 def get_fork_name():
420 423 repo_name = action_params
421 424 return _('fork name ') + str(link_to(action_params, url('summary_home',
422 425 repo_name=repo_name,)))
423 426
424 427 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
425 428 'user_created_repo':(_('[created] repository'), None),
426 429 'user_created_fork':(_('[created] repository as fork'), None),
427 430 'user_forked_repo':(_('[forked] repository'), get_fork_name),
428 431 'user_updated_repo':(_('[updated] repository'), None),
429 432 'admin_deleted_repo':(_('[delete] repository'), None),
430 433 'admin_created_repo':(_('[created] repository'), None),
431 434 'admin_forked_repo':(_('[forked] repository'), None),
432 435 'admin_updated_repo':(_('[updated] repository'), None),
433 436 'push':(_('[pushed] into'), get_cs_links),
434 437 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
435 438 'push_remote':(_('[pulled from remote] into'), get_cs_links),
436 439 'pull':(_('[pulled] from'), None),
437 440 'started_following_repo':(_('[started following] repository'), None),
438 441 'stopped_following_repo':(_('[stopped following] repository'), None),
439 442 }
440 443
441 444 action_str = action_map.get(action, action)
442 445 if feed:
443 446 action = action_str[0].replace('[', '').replace(']', '')
444 447 else:
445 448 action = action_str[0].replace('[', '<span class="journal_highlight">')\
446 449 .replace(']', '</span>')
447 450
448 451 action_params_func = lambda :""
449 452
450 453 if callable(action_str[1]):
451 454 action_params_func = action_str[1]
452 455
453 456 return [literal(action), action_params_func]
454 457
455 458 def action_parser_icon(user_log):
456 459 action = user_log.action
457 460 action_params = None
458 461 x = action.split(':')
459 462
460 463 if len(x) > 1:
461 464 action, action_params = x
462 465
463 466 tmpl = """<img src="%s%s" alt="%s"/>"""
464 467 map = {'user_deleted_repo':'database_delete.png',
465 468 'user_created_repo':'database_add.png',
466 469 'user_created_fork':'arrow_divide.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 not email_address or email_address == 'anonymous@rhodecode.org'):
497 500 return url("/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 = safe_str(email_address)
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, **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 = int(math.ceil(float(self.item_count) /
556 559 self.items_per_page))
557 560 self.last_page = self.first_page + self.page_count - 1
558 561
559 # Make sure that the requested page number is the range of valid pages
562 # Make sure that the requested page number is the range of
563 # valid pages
560 564 if self.page > self.last_page:
561 565 self.page = self.last_page
562 566 elif self.page < self.first_page:
563 567 self.page = self.first_page
564 568
565 569 # Note: the number of items on this page can be less than
566 570 # items_per_page if the last page is not full
567 571 self.first_item = max(0, (self.item_count) - (self.page *
568 572 items_per_page))
569 573 self.last_item = ((self.item_count - 1) - items_per_page *
570 574 (self.page - 1))
571 575
572 576 self.items = list(self.collection[self.first_item:self.last_item + 1])
573 577
574 578
575 579 # Links to previous and next page
576 580 if self.page > self.first_page:
577 581 self.previous_page = self.page - 1
578 582 else:
579 583 self.previous_page = None
580 584
581 585 if self.page < self.last_page:
582 586 self.next_page = self.page + 1
583 587 else:
584 588 self.next_page = None
585 589
586 590 # No items available
587 591 else:
588 592 self.first_page = None
589 593 self.page_count = 0
590 594 self.last_page = None
591 595 self.first_item = None
592 596 self.last_item = None
593 597 self.previous_page = None
594 598 self.next_page = None
595 599 self.items = []
596 600
597 601 # This is a subclass of the 'list' type. Initialise the list now.
598 602 list.__init__(self, reversed(self.items))
599 603
600 604
601 605 def changed_tooltip(nodes):
602 606 """
603 607 Generates a html string for changed nodes in changeset page.
604 608 It limits the output to 30 entries
605 609
606 610 :param nodes: LazyNodesGenerator
607 611 """
608 612 if nodes:
609 613 pref = ': <br/> '
610 614 suf = ''
611 615 if len(nodes) > 30:
612 616 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
613 617 return literal(pref + '<br/> '.join([safe_unicode(x.path)
614 618 for x in nodes[:30]]) + suf)
615 619 else:
616 620 return ': ' + _('No Files')
617 621
618 622
619 623
620 624 def repo_link(groups_and_repos):
621 625 """
622 626 Makes a breadcrumbs link to repo within a group
623 627 joins &raquo; on each group to create a fancy link
624 628
625 629 ex::
626 630 group >> subgroup >> repo
627 631
628 632 :param groups_and_repos:
629 633 """
630 634 groups, repo_name = groups_and_repos
631 635
632 636 if not groups:
633 637 return repo_name
634 638 else:
635 639 def make_link(group):
636 640 return link_to(group.name, url('repos_group_home',
637 641 group_name=group.group_name))
638 642 return literal(' &raquo; '.join(map(make_link, groups)) + \
639 643 " &raquo; " + repo_name)
640 644
641 645 def fancy_file_stats(stats):
642 646 """
643 647 Displays a fancy two colored bar for number of added/deleted
644 648 lines of code on file
645 649
646 650 :param stats: two element list of added/deleted lines of code
647 651 """
648 652
649 653 a, d, t = stats[0], stats[1], stats[0] + stats[1]
650 654 width = 100
651 655 unit = float(width) / (t or 1)
652 656
653 657 # needs > 9% of width to be visible or 0 to be hidden
654 658 a_p = max(9, unit * a) if a > 0 else 0
655 659 d_p = max(9, unit * d) if d > 0 else 0
656 660 p_sum = a_p + d_p
657 661
658 662 if p_sum > width:
659 663 #adjust the percentage to be == 100% since we adjusted to 9
660 664 if a_p > d_p:
661 665 a_p = a_p - (p_sum - width)
662 666 else:
663 667 d_p = d_p - (p_sum - width)
664 668
665 669 a_v = a if a > 0 else ''
666 670 d_v = d if d > 0 else ''
667 671
668 672
669 673 def cgen(l_type):
670 674 mapping = {'tr':'top-right-rounded-corner',
671 675 'tl':'top-left-rounded-corner',
672 676 'br':'bottom-right-rounded-corner',
673 677 'bl':'bottom-left-rounded-corner'}
674 678 map_getter = lambda x:mapping[x]
675 679
676 680 if l_type == 'a' and d_v:
677 681 #case when added and deleted are present
678 682 return ' '.join(map(map_getter, ['tl', 'bl']))
679 683
680 684 if l_type == 'a' and not d_v:
681 685 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
682 686
683 687 if l_type == 'd' and a_v:
684 688 return ' '.join(map(map_getter, ['tr', 'br']))
685 689
686 690 if l_type == 'd' and not a_v:
687 691 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
688 692
689 693
690 694
691 695 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
692 696 a_p, a_v)
693 697 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
694 698 d_p, d_v)
695 699 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
696 700
697 701
698 702 def urlify_text(text):
699 703 import re
700 704
701 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
705 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
706 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
702 707
703 708 def url_func(match_obj):
704 709 url_full = match_obj.groups()[0]
705 710 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
706 711
707 712 return literal(url_pat.sub(url_func, text))
708 713
709 714
710 715 def rst(source):
711 return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
716 return literal('<div class="rst-block">%s</div>' %
717 MarkupRenderer.rst(source))
General Comments 0
You need to be logged in to leave comments. Login now