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