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