##// END OF EJS Templates
journal: polish
Aras Pranckevicius -
r1979:3dc2cf95 beta
parent child Browse files
Show More
@@ -1,870 +1,886
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 is_git(repository):
333 333 if hasattr(repository, 'alias'):
334 334 _type = repository.alias
335 335 elif hasattr(repository, 'repo_type'):
336 336 _type = repository.repo_type
337 337 else:
338 338 _type = repository
339 339 return _type == 'git'
340 340
341 341
342 342 def is_hg(repository):
343 343 if hasattr(repository, 'alias'):
344 344 _type = repository.alias
345 345 elif hasattr(repository, 'repo_type'):
346 346 _type = repository.repo_type
347 347 else:
348 348 _type = repository
349 349 return _type == 'hg'
350 350
351 351
352 352 def email_or_none(author):
353 353 _email = email(author)
354 354 if _email != '':
355 355 return _email
356 356
357 357 # See if it contains a username we can get an email from
358 358 user = User.get_by_username(author_name(author), case_insensitive=True,
359 359 cache=True)
360 360 if user is not None:
361 361 return user.email
362 362
363 363 # No valid email, not a valid user in the system, none!
364 364 return None
365 365
366 366
367 367 def person(author):
368 368 # attr to return from fetched user
369 369 person_getter = lambda usr: usr.username
370 370
371 371 # Valid email in the attribute passed, see if they're in the system
372 372 _email = email(author)
373 373 if _email != '':
374 374 user = User.get_by_email(_email, case_insensitive=True, cache=True)
375 375 if user is not None:
376 376 return person_getter(user)
377 377 return _email
378 378
379 379 # Maybe it's a username?
380 380 _author = author_name(author)
381 381 user = User.get_by_username(_author, case_insensitive=True,
382 382 cache=True)
383 383 if user is not None:
384 384 return person_getter(user)
385 385
386 386 # Still nothing? Just pass back the author name then
387 387 return _author
388 388
389 389
390 390 def bool2icon(value):
391 391 """Returns True/False values represented as small html image of true/false
392 392 icons
393 393
394 394 :param value: bool value
395 395 """
396 396
397 397 if value is True:
398 398 return HTML.tag('img', src=url("/images/icons/accept.png"),
399 399 alt=_('True'))
400 400
401 401 if value is False:
402 402 return HTML.tag('img', src=url("/images/icons/cancel.png"),
403 403 alt=_('False'))
404 404
405 405 return value
406 406
407 407
408 408 def action_parser(user_log, feed=False):
409 409 """This helper will action_map the specified string action into translated
410 410 fancy names with icons and links
411 411
412 412 :param user_log: user log instance
413 413 :param feed: use output for feeds (no html and fancy icons)
414 414 """
415 415
416 416 action = user_log.action
417 417 action_params = ' '
418 418
419 419 x = action.split(':')
420 420
421 421 if len(x) > 1:
422 422 action, action_params = x
423 423
424 424 def get_cs_links():
425 425 revs_limit = 3 #display this amount always
426 426 revs_top_limit = 50 #show upto this amount of changesets hidden
427 427 revs = action_params.split(',')
428 428 repo_name = user_log.repository.repo_name
429 429
430 430 from rhodecode.model.scm import ScmModel
431 431 repo = user_log.repository.scm_instance
432 432
433 433 message = lambda rev: get_changeset_safe(repo, rev).message
434 434 cs_links = []
435 435 cs_links.append(" " + ', '.join ([link_to(rev,
436 436 url('changeset_home',
437 437 repo_name=repo_name,
438 438 revision=rev), title=tooltip(message(rev)),
439 439 class_='tooltip') for rev in revs[:revs_limit] ]))
440 440
441 441 compare_view = (' <div class="compare_view tooltip" title="%s">'
442 442 '<a href="%s">%s</a> '
443 443 '</div>' % (_('Show all combined changesets %s->%s' \
444 444 % (revs[0], revs[-1])),
445 445 url('changeset_home', repo_name=repo_name,
446 446 revision='%s...%s' % (revs[0], revs[-1])
447 447 ),
448 448 _('compare view'))
449 449 )
450 450
451 if len(revs) > revs_limit:
451 # if we have exactly one more than normally displayed:
452 # just display it, takes less space than displaying "and 1 more revisions"
453 if len(revs) == revs_limit + 1:
454 rev = revs[revs_limit]
455 cs_links.append(", " + link_to(rev,
456 url('changeset_home',
457 repo_name=repo_name,
458 revision=rev), title=tooltip(message(rev)),
459 class_='tooltip') )
460
461 # hidden-by-default ones
462 if len(revs) > revs_limit + 1:
452 463 uniq_id = revs[0]
453 464 html_tmpl = ('<span> %s '
454 465 '<a class="show_more" id="_%s" href="#more">%s</a> '
455 466 '%s</span>')
456 467 if not feed:
457 468 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
458 469 % (len(revs) - revs_limit),
459 470 _('revisions')))
460 471
461 472 if not feed:
462 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
473 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
463 474 else:
464 475 html_tmpl = '<span id="%s"> %s </span>'
465 476
466 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
477 morelinks = ', '.join([link_to(rev,
467 478 url('changeset_home',
468 479 repo_name=repo_name, revision=rev),
469 480 title=message(rev), class_='tooltip')
470 for rev in revs[revs_limit:revs_top_limit]])))
481 for rev in revs[revs_limit:revs_top_limit]])
482
483 if len(revs) > revs_top_limit:
484 morelinks += ', ...'
485
486 cs_links.append(html_tmpl % (uniq_id, morelinks))
471 487 if len(revs) > 1:
472 488 cs_links.append(compare_view)
473 489 return ''.join(cs_links)
474 490
475 491 def get_fork_name():
476 492 repo_name = action_params
477 493 return _('fork name ') + str(link_to(action_params, url('summary_home',
478 494 repo_name=repo_name,)))
479 495
480 496 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
481 497 'user_created_repo':(_('[created] repository'), None),
482 498 'user_created_fork':(_('[created] repository as fork'), None),
483 499 'user_forked_repo':(_('[forked] repository'), get_fork_name),
484 500 'user_updated_repo':(_('[updated] repository'), None),
485 501 'admin_deleted_repo':(_('[delete] repository'), None),
486 502 'admin_created_repo':(_('[created] repository'), None),
487 503 'admin_forked_repo':(_('[forked] repository'), None),
488 504 'admin_updated_repo':(_('[updated] repository'), None),
489 505 'push':(_('[pushed] into'), get_cs_links),
490 506 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
491 507 'push_remote':(_('[pulled from remote] into'), get_cs_links),
492 508 'pull':(_('[pulled] from'), None),
493 509 'started_following_repo':(_('[started following] repository'), None),
494 510 'stopped_following_repo':(_('[stopped following] repository'), None),
495 511 }
496 512
497 513 action_str = action_map.get(action, action)
498 514 if feed:
499 515 action = action_str[0].replace('[', '').replace(']', '')
500 516 else:
501 517 action = action_str[0].replace('[', '<span class="journal_highlight">')\
502 518 .replace(']', '</span>')
503 519
504 520 action_params_func = lambda :""
505 521
506 522 if callable(action_str[1]):
507 523 action_params_func = action_str[1]
508 524
509 525 return [literal(action), action_params_func]
510 526
511 527
512 528 def action_parser_icon(user_log):
513 529 action = user_log.action
514 530 action_params = None
515 531 x = action.split(':')
516 532
517 533 if len(x) > 1:
518 534 action, action_params = x
519 535
520 536 tmpl = """<img src="%s%s" alt="%s"/>"""
521 537 map = {'user_deleted_repo':'database_delete.png',
522 538 'user_created_repo':'database_add.png',
523 539 'user_created_fork':'arrow_divide.png',
524 540 'user_forked_repo':'arrow_divide.png',
525 541 'user_updated_repo':'database_edit.png',
526 542 'admin_deleted_repo':'database_delete.png',
527 543 'admin_created_repo':'database_add.png',
528 544 'admin_forked_repo':'arrow_divide.png',
529 545 'admin_updated_repo':'database_edit.png',
530 546 'push':'script_add.png',
531 547 'push_local':'script_edit.png',
532 548 'push_remote':'connect.png',
533 549 'pull':'down_16.png',
534 550 'started_following_repo':'heart_add.png',
535 551 'stopped_following_repo':'heart_delete.png',
536 552 }
537 553 return literal(tmpl % ((url('/images/icons/')),
538 554 map.get(action, action), action))
539 555
540 556
541 557 #==============================================================================
542 558 # PERMS
543 559 #==============================================================================
544 560 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
545 561 HasRepoPermissionAny, HasRepoPermissionAll
546 562
547 563
548 564 #==============================================================================
549 565 # GRAVATAR URL
550 566 #==============================================================================
551 567
552 568 def gravatar_url(email_address, size=30):
553 569 if (not str2bool(config['app_conf'].get('use_gravatar')) or
554 570 not email_address or email_address == 'anonymous@rhodecode.org'):
555 571 f=lambda a,l:min(l,key=lambda x:abs(x-a))
556 572 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
557 573
558 574 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
559 575 default = 'identicon'
560 576 baseurl_nossl = "http://www.gravatar.com/avatar/"
561 577 baseurl_ssl = "https://secure.gravatar.com/avatar/"
562 578 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
563 579
564 580 if isinstance(email_address, unicode):
565 581 #hashlib crashes on unicode items
566 582 email_address = safe_str(email_address)
567 583 # construct the url
568 584 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
569 585 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
570 586
571 587 return gravatar_url
572 588
573 589
574 590 #==============================================================================
575 591 # REPO PAGER, PAGER FOR REPOSITORY
576 592 #==============================================================================
577 593 class RepoPage(Page):
578 594
579 595 def __init__(self, collection, page=1, items_per_page=20,
580 596 item_count=None, url=None, **kwargs):
581 597
582 598 """Create a "RepoPage" instance. special pager for paging
583 599 repository
584 600 """
585 601 self._url_generator = url
586 602
587 603 # Safe the kwargs class-wide so they can be used in the pager() method
588 604 self.kwargs = kwargs
589 605
590 606 # Save a reference to the collection
591 607 self.original_collection = collection
592 608
593 609 self.collection = collection
594 610
595 611 # The self.page is the number of the current page.
596 612 # The first page has the number 1!
597 613 try:
598 614 self.page = int(page) # make it int() if we get it as a string
599 615 except (ValueError, TypeError):
600 616 self.page = 1
601 617
602 618 self.items_per_page = items_per_page
603 619
604 620 # Unless the user tells us how many items the collections has
605 621 # we calculate that ourselves.
606 622 if item_count is not None:
607 623 self.item_count = item_count
608 624 else:
609 625 self.item_count = len(self.collection)
610 626
611 627 # Compute the number of the first and last available page
612 628 if self.item_count > 0:
613 629 self.first_page = 1
614 630 self.page_count = int(math.ceil(float(self.item_count) /
615 631 self.items_per_page))
616 632 self.last_page = self.first_page + self.page_count - 1
617 633
618 634 # Make sure that the requested page number is the range of
619 635 # valid pages
620 636 if self.page > self.last_page:
621 637 self.page = self.last_page
622 638 elif self.page < self.first_page:
623 639 self.page = self.first_page
624 640
625 641 # Note: the number of items on this page can be less than
626 642 # items_per_page if the last page is not full
627 643 self.first_item = max(0, (self.item_count) - (self.page *
628 644 items_per_page))
629 645 self.last_item = ((self.item_count - 1) - items_per_page *
630 646 (self.page - 1))
631 647
632 648 self.items = list(self.collection[self.first_item:self.last_item + 1])
633 649
634 650 # Links to previous and next page
635 651 if self.page > self.first_page:
636 652 self.previous_page = self.page - 1
637 653 else:
638 654 self.previous_page = None
639 655
640 656 if self.page < self.last_page:
641 657 self.next_page = self.page + 1
642 658 else:
643 659 self.next_page = None
644 660
645 661 # No items available
646 662 else:
647 663 self.first_page = None
648 664 self.page_count = 0
649 665 self.last_page = None
650 666 self.first_item = None
651 667 self.last_item = None
652 668 self.previous_page = None
653 669 self.next_page = None
654 670 self.items = []
655 671
656 672 # This is a subclass of the 'list' type. Initialise the list now.
657 673 list.__init__(self, reversed(self.items))
658 674
659 675
660 676 def changed_tooltip(nodes):
661 677 """
662 678 Generates a html string for changed nodes in changeset page.
663 679 It limits the output to 30 entries
664 680
665 681 :param nodes: LazyNodesGenerator
666 682 """
667 683 if nodes:
668 684 pref = ': <br/> '
669 685 suf = ''
670 686 if len(nodes) > 30:
671 687 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
672 688 return literal(pref + '<br/> '.join([safe_unicode(x.path)
673 689 for x in nodes[:30]]) + suf)
674 690 else:
675 691 return ': ' + _('No Files')
676 692
677 693
678 694 def repo_link(groups_and_repos):
679 695 """
680 696 Makes a breadcrumbs link to repo within a group
681 697 joins &raquo; on each group to create a fancy link
682 698
683 699 ex::
684 700 group >> subgroup >> repo
685 701
686 702 :param groups_and_repos:
687 703 """
688 704 groups, repo_name = groups_and_repos
689 705
690 706 if not groups:
691 707 return repo_name
692 708 else:
693 709 def make_link(group):
694 710 return link_to(group.name, url('repos_group_home',
695 711 group_name=group.group_name))
696 712 return literal(' &raquo; '.join(map(make_link, groups)) + \
697 713 " &raquo; " + repo_name)
698 714
699 715
700 716 def fancy_file_stats(stats):
701 717 """
702 718 Displays a fancy two colored bar for number of added/deleted
703 719 lines of code on file
704 720
705 721 :param stats: two element list of added/deleted lines of code
706 722 """
707 723
708 724 a, d, t = stats[0], stats[1], stats[0] + stats[1]
709 725 width = 100
710 726 unit = float(width) / (t or 1)
711 727
712 728 # needs > 9% of width to be visible or 0 to be hidden
713 729 a_p = max(9, unit * a) if a > 0 else 0
714 730 d_p = max(9, unit * d) if d > 0 else 0
715 731 p_sum = a_p + d_p
716 732
717 733 if p_sum > width:
718 734 #adjust the percentage to be == 100% since we adjusted to 9
719 735 if a_p > d_p:
720 736 a_p = a_p - (p_sum - width)
721 737 else:
722 738 d_p = d_p - (p_sum - width)
723 739
724 740 a_v = a if a > 0 else ''
725 741 d_v = d if d > 0 else ''
726 742
727 743 def cgen(l_type):
728 744 mapping = {'tr': 'top-right-rounded-corner',
729 745 'tl': 'top-left-rounded-corner',
730 746 'br': 'bottom-right-rounded-corner',
731 747 'bl': 'bottom-left-rounded-corner'}
732 748 map_getter = lambda x: mapping[x]
733 749
734 750 if l_type == 'a' and d_v:
735 751 #case when added and deleted are present
736 752 return ' '.join(map(map_getter, ['tl', 'bl']))
737 753
738 754 if l_type == 'a' and not d_v:
739 755 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
740 756
741 757 if l_type == 'd' and a_v:
742 758 return ' '.join(map(map_getter, ['tr', 'br']))
743 759
744 760 if l_type == 'd' and not a_v:
745 761 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
746 762
747 763 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
748 764 cgen('a'),a_p, a_v
749 765 )
750 766 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
751 767 cgen('d'),d_p, d_v
752 768 )
753 769 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
754 770
755 771
756 772 def urlify_text(text_):
757 773 import re
758 774
759 775 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
760 776 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
761 777
762 778 def url_func(match_obj):
763 779 url_full = match_obj.groups()[0]
764 780 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
765 781
766 782 return literal(url_pat.sub(url_func, text_))
767 783
768 784
769 785 def urlify_changesets(text_, repository):
770 786 import re
771 787 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
772 788
773 789 def url_func(match_obj):
774 790 rev = match_obj.groups()[0]
775 791 pref = ''
776 792 if match_obj.group().startswith(' '):
777 793 pref = ' '
778 794 tmpl = (
779 795 '%(pref)s<a class="%(cls)s" href="%(url)s">'
780 796 '%(rev)s'
781 797 '</a>'
782 798 )
783 799 return tmpl % {
784 800 'pref': pref,
785 801 'cls': 'revision-link',
786 802 'url': url('changeset_home', repo_name=repository, revision=rev),
787 803 'rev': rev,
788 804 }
789 805
790 806 newtext = URL_PAT.sub(url_func, text_)
791 807
792 808 return newtext
793 809
794 810
795 811 def urlify_commit(text_, repository=None, link_=None):
796 812 import re
797 813 import traceback
798 814
799 815 # urlify changesets
800 816 text_ = urlify_changesets(text_, repository)
801 817
802 818 def linkify_others(t,l):
803 819 urls = re.compile(r'(\<a.*?\<\/a\>)',)
804 820 links = []
805 821 for e in urls.split(t):
806 822 if not urls.match(e):
807 823 links.append('<a class="message-link" href="%s">%s</a>' % (l,e))
808 824 else:
809 825 links.append(e)
810 826
811 827 return ''.join(links)
812 828 try:
813 829 conf = config['app_conf']
814 830
815 831 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
816 832
817 833 if URL_PAT:
818 834 ISSUE_SERVER_LNK = conf.get('issue_server_link')
819 835 ISSUE_PREFIX = conf.get('issue_prefix')
820 836
821 837 def url_func(match_obj):
822 838 pref = ''
823 839 if match_obj.group().startswith(' '):
824 840 pref = ' '
825 841
826 842 issue_id = ''.join(match_obj.groups())
827 843 tmpl = (
828 844 '%(pref)s<a class="%(cls)s" href="%(url)s">'
829 845 '%(issue-prefix)s%(id-repr)s'
830 846 '</a>'
831 847 )
832 848 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
833 849 if repository:
834 850 url = url.replace('{repo}', repository)
835 851
836 852 return tmpl % {
837 853 'pref': pref,
838 854 'cls': 'issue-tracker-link',
839 855 'url': url,
840 856 'id-repr': issue_id,
841 857 'issue-prefix': ISSUE_PREFIX,
842 858 'serv': ISSUE_SERVER_LNK,
843 859 }
844 860
845 861 newtext = URL_PAT.sub(url_func, text_)
846 862
847 863 # wrap not links into final link => link_
848 864 newtext = linkify_others(newtext, link_)
849 865
850 866 return literal(newtext)
851 867 except:
852 868 log.error(traceback.format_exc())
853 869 pass
854 870
855 871 return text_
856 872
857 873
858 874 def rst(source):
859 875 return literal('<div class="rst-block">%s</div>' %
860 876 MarkupRenderer.rst(source))
861 877
862 878
863 879 def rst_w_mentions(source):
864 880 """
865 881 Wrapped rst renderer with @mention highlighting
866 882
867 883 :param source:
868 884 """
869 885 return literal('<div class="rst-block">%s</div>' %
870 886 MarkupRenderer.rst_with_mentions(source))
General Comments 0
You need to be logged in to leave comments. Login now