##// END OF EJS Templates
formatting change on helper, remove str() call
marcink -
r2914:1cd1cbe6 beta
parent child Browse files
Show More
@@ -1,1096 +1,1096 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 import re
13 13 import urlparse
14 14
15 15 from datetime import datetime
16 16 from pygments.formatters.html import HtmlFormatter
17 17 from pygments import highlight as code_highlight
18 18 from pylons import url, request, config
19 19 from pylons.i18n.translation import _, ungettext
20 20 from hashlib import md5
21 21
22 22 from webhelpers.html import literal, HTML, escape
23 23 from webhelpers.html.tools import *
24 24 from webhelpers.html.builder import make_tag
25 25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 26 end_form, file, form, hidden, image, javascript_link, link_to, \
27 27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 28 submit, text, password, textarea, title, ul, xml_declaration, radio
29 29 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 31 from webhelpers.number import format_byte_size, format_bit_size
32 32 from webhelpers.pylonslib import Flash as _Flash
33 33 from webhelpers.pylonslib.secure_form import secure_form
34 34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 36 replace_whitespace, urlify, truncate, wrap_paragraphs
37 37 from webhelpers.date import time_ago_in_words
38 38 from webhelpers.paginate import Page
39 39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 40 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 41
42 42 from rhodecode.lib.annotate import annotate_highlight
43 43 from rhodecode.lib.utils import repo_name_slug
44 44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 45 get_changeset_safe, datetime_to_time, time_to_datetime
46 46 from rhodecode.lib.markup_renderer import MarkupRenderer
47 47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 48 from rhodecode.lib.vcs.backends.base import BaseChangeset
49 49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 51 from rhodecode.model.db import URL_SEP, Permission
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 html_escape_table = {
57 57 "&": "&",
58 58 '"': """,
59 59 "'": "'",
60 60 ">": ">",
61 61 "<": "&lt;",
62 62 }
63 63
64 64
65 65 def html_escape(text):
66 66 """Produce entities within text."""
67 67 return "".join(html_escape_table.get(c,c) for c in text)
68 68
69 69
70 70 def shorter(text, size=20):
71 71 postfix = '...'
72 72 if len(text) > size:
73 73 return text[:size - len(postfix)] + postfix
74 74 return text
75 75
76 76
77 77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 78 """
79 79 Reset button
80 80 """
81 81 _set_input_attrs(attrs, type, name, value)
82 82 _set_id_attr(attrs, id, name)
83 83 convert_boolean_attrs(attrs, ["disabled"])
84 84 return HTML.input(**attrs)
85 85
86 86 reset = _reset
87 87 safeid = _make_safe_id_component
88 88
89 89
90 90 def FID(raw_id, path):
91 91 """
92 92 Creates a uniqe ID for filenode based on it's hash of path and revision
93 93 it's safe to use in urls
94 94
95 95 :param raw_id:
96 96 :param path:
97 97 """
98 98
99 99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100 100
101 101
102 102 def get_token():
103 103 """Return the current authentication token, creating one if one doesn't
104 104 already exist.
105 105 """
106 106 token_key = "_authentication_token"
107 107 from pylons import session
108 108 if not token_key in session:
109 109 try:
110 110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 111 except AttributeError: # Python < 2.4
112 112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 113 session[token_key] = token
114 114 if hasattr(session, 'save'):
115 115 session.save()
116 116 return session[token_key]
117 117
118 118
119 119 class _GetError(object):
120 120 """Get error from form_errors, and represent it as span wrapped error
121 121 message
122 122
123 123 :param field_name: field to fetch errors for
124 124 :param form_errors: form errors dict
125 125 """
126 126
127 127 def __call__(self, field_name, form_errors):
128 128 tmpl = """<span class="error_msg">%s</span>"""
129 129 if form_errors and field_name in form_errors:
130 130 return literal(tmpl % form_errors.get(field_name))
131 131
132 132 get_error = _GetError()
133 133
134 134
135 135 class _ToolTip(object):
136 136
137 137 def __call__(self, tooltip_title, trim_at=50):
138 138 """
139 139 Special function just to wrap our text into nice formatted
140 140 autowrapped text
141 141
142 142 :param tooltip_title:
143 143 """
144 144 tooltip_title = escape(tooltip_title)
145 145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 146 return tooltip_title
147 147 tooltip = _ToolTip()
148 148
149 149
150 150 class _FilesBreadCrumbs(object):
151 151
152 152 def __call__(self, repo_name, rev, paths):
153 153 if isinstance(paths, str):
154 154 paths = safe_unicode(paths)
155 155 url_l = [link_to(repo_name, url('files_home',
156 156 repo_name=repo_name,
157 157 revision=rev, f_path=''),
158 158 class_='ypjax-link')]
159 159 paths_l = paths.split('/')
160 160 for cnt, p in enumerate(paths_l):
161 161 if p != '':
162 162 url_l.append(link_to(p,
163 163 url('files_home',
164 164 repo_name=repo_name,
165 165 revision=rev,
166 166 f_path='/'.join(paths_l[:cnt + 1])
167 167 ),
168 168 class_='ypjax-link'
169 169 )
170 170 )
171 171
172 172 return literal('/'.join(url_l))
173 173
174 174 files_breadcrumbs = _FilesBreadCrumbs()
175 175
176 176
177 177 class CodeHtmlFormatter(HtmlFormatter):
178 178 """
179 179 My code Html Formatter for source codes
180 180 """
181 181
182 182 def wrap(self, source, outfile):
183 183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184 184
185 185 def _wrap_code(self, source):
186 186 for cnt, it in enumerate(source):
187 187 i, t = it
188 188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 189 yield i, t
190 190
191 191 def _wrap_tablelinenos(self, inner):
192 192 dummyoutfile = StringIO.StringIO()
193 193 lncount = 0
194 194 for t, line in inner:
195 195 if t:
196 196 lncount += 1
197 197 dummyoutfile.write(line)
198 198
199 199 fl = self.linenostart
200 200 mw = len(str(lncount + fl - 1))
201 201 sp = self.linenospecial
202 202 st = self.linenostep
203 203 la = self.lineanchors
204 204 aln = self.anchorlinenos
205 205 nocls = self.noclasses
206 206 if sp:
207 207 lines = []
208 208
209 209 for i in range(fl, fl + lncount):
210 210 if i % st == 0:
211 211 if i % sp == 0:
212 212 if aln:
213 213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 214 (la, i, mw, i))
215 215 else:
216 216 lines.append('<span class="special">%*d</span>' % (mw, i))
217 217 else:
218 218 if aln:
219 219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 220 else:
221 221 lines.append('%*d' % (mw, i))
222 222 else:
223 223 lines.append('')
224 224 ls = '\n'.join(lines)
225 225 else:
226 226 lines = []
227 227 for i in range(fl, fl + lncount):
228 228 if i % st == 0:
229 229 if aln:
230 230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 231 else:
232 232 lines.append('%*d' % (mw, i))
233 233 else:
234 234 lines.append('')
235 235 ls = '\n'.join(lines)
236 236
237 237 # in case you wonder about the seemingly redundant <div> here: since the
238 238 # content in the other cell also is wrapped in a div, some browsers in
239 239 # some configurations seem to mess up the formatting...
240 240 if nocls:
241 241 yield 0, ('<table class="%stable">' % self.cssclass +
242 242 '<tr><td><div class="linenodiv" '
243 243 'style="background-color: #f0f0f0; padding-right: 10px">'
244 244 '<pre style="line-height: 125%">' +
245 245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 246 else:
247 247 yield 0, ('<table class="%stable">' % self.cssclass +
248 248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 249 ls + '</pre></div></td><td id="hlcode" class="code">')
250 250 yield 0, dummyoutfile.getvalue()
251 251 yield 0, '</td></tr></table>'
252 252
253 253
254 254 def pygmentize(filenode, **kwargs):
255 255 """pygmentize function using pygments
256 256
257 257 :param filenode:
258 258 """
259 259
260 260 return literal(code_highlight(filenode.content,
261 261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262 262
263 263
264 264 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 265 """
266 266 pygmentize function for annotation
267 267
268 268 :param filenode:
269 269 """
270 270
271 271 color_dict = {}
272 272
273 273 def gen_color(n=10000):
274 274 """generator for getting n of evenly distributed colors using
275 275 hsv color and golden ratio. It always return same order of colors
276 276
277 277 :returns: RGB tuple
278 278 """
279 279
280 280 def hsv_to_rgb(h, s, v):
281 281 if s == 0.0:
282 282 return v, v, v
283 283 i = int(h * 6.0) # XXX assume int() truncates!
284 284 f = (h * 6.0) - i
285 285 p = v * (1.0 - s)
286 286 q = v * (1.0 - s * f)
287 287 t = v * (1.0 - s * (1.0 - f))
288 288 i = i % 6
289 289 if i == 0:
290 290 return v, t, p
291 291 if i == 1:
292 292 return q, v, p
293 293 if i == 2:
294 294 return p, v, t
295 295 if i == 3:
296 296 return p, q, v
297 297 if i == 4:
298 298 return t, p, v
299 299 if i == 5:
300 300 return v, p, q
301 301
302 302 golden_ratio = 0.618033988749895
303 303 h = 0.22717784590367374
304 304
305 305 for _ in xrange(n):
306 306 h += golden_ratio
307 307 h %= 1
308 308 HSV_tuple = [h, 0.95, 0.95]
309 309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311 311
312 312 cgenerator = gen_color()
313 313
314 314 def get_color_string(cs):
315 315 if cs in color_dict:
316 316 col = color_dict[cs]
317 317 else:
318 318 col = color_dict[cs] = cgenerator.next()
319 319 return "color: rgb(%s)! important;" % (', '.join(col))
320 320
321 321 def url_func(repo_name):
322 322
323 323 def _url_func(changeset):
324 324 author = changeset.author
325 325 date = changeset.date
326 326 message = tooltip(changeset.message)
327 327
328 328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 330 "</b> %s<br/></div>")
331 331
332 332 tooltip_html = tooltip_html % (author, date, message)
333 333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 334 short_id(changeset.raw_id))
335 335 uri = link_to(
336 336 lnk_format,
337 337 url('changeset_home', repo_name=repo_name,
338 338 revision=changeset.raw_id),
339 339 style=get_color_string(changeset.raw_id),
340 340 class_='tooltip',
341 341 title=tooltip_html
342 342 )
343 343
344 344 uri += '\n'
345 345 return uri
346 346 return _url_func
347 347
348 348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349 349
350 350
351 351 def is_following_repo(repo_name, user_id):
352 352 from rhodecode.model.scm import ScmModel
353 353 return ScmModel().is_following_repo(repo_name, user_id)
354 354
355 355 flash = _Flash()
356 356
357 357 #==============================================================================
358 358 # SCM FILTERS available via h.
359 359 #==============================================================================
360 360 from rhodecode.lib.vcs.utils import author_name, author_email
361 361 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 362 from rhodecode.model.db import User, ChangesetStatus
363 363
364 364 age = lambda x: _age(x)
365 365 capitalize = lambda x: x.capitalize()
366 366 email = author_email
367 367 short_id = lambda x: x[:12]
368 368 hide_credentials = lambda x: ''.join(credentials_filter(x))
369 369
370 370
371 371 def fmt_date(date):
372 372 if date:
373 373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 374 return date.strftime(_fmt).decode('utf8')
375 375
376 376 return ""
377 377
378 378
379 379 def is_git(repository):
380 380 if hasattr(repository, 'alias'):
381 381 _type = repository.alias
382 382 elif hasattr(repository, 'repo_type'):
383 383 _type = repository.repo_type
384 384 else:
385 385 _type = repository
386 386 return _type == 'git'
387 387
388 388
389 389 def is_hg(repository):
390 390 if hasattr(repository, 'alias'):
391 391 _type = repository.alias
392 392 elif hasattr(repository, 'repo_type'):
393 393 _type = repository.repo_type
394 394 else:
395 395 _type = repository
396 396 return _type == 'hg'
397 397
398 398
399 399 def email_or_none(author):
400 400 # extract email from the commit string
401 401 _email = email(author)
402 402 if _email != '':
403 403 # check it against RhodeCode database, and use the MAIN email for this
404 404 # user
405 405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 406 if user is not None:
407 407 return user.email
408 408 return _email
409 409
410 410 # See if it contains a username we can get an email from
411 411 user = User.get_by_username(author_name(author), case_insensitive=True,
412 412 cache=True)
413 413 if user is not None:
414 414 return user.email
415 415
416 416 # No valid email, not a valid user in the system, none!
417 417 return None
418 418
419 419
420 420 def person(author, show_attr="username_and_name"):
421 421 # attr to return from fetched user
422 422 person_getter = lambda usr: getattr(usr, show_attr)
423 423
424 424 # Valid email in the attribute passed, see if they're in the system
425 425 _email = email(author)
426 426 if _email != '':
427 427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 428 if user is not None:
429 429 return person_getter(user)
430 430 return _email
431 431
432 432 # Maybe it's a username?
433 433 _author = author_name(author)
434 434 user = User.get_by_username(_author, case_insensitive=True,
435 435 cache=True)
436 436 if user is not None:
437 437 return person_getter(user)
438 438
439 439 # Still nothing? Just pass back the author name then
440 440 return _author
441 441
442 442
443 443 def person_by_id(id_, show_attr="username_and_name"):
444 444 # attr to return from fetched user
445 445 person_getter = lambda usr: getattr(usr, show_attr)
446 446
447 447 #maybe it's an ID ?
448 448 if str(id_).isdigit() or isinstance(id_, int):
449 449 id_ = int(id_)
450 450 user = User.get(id_)
451 451 if user is not None:
452 452 return person_getter(user)
453 453 return id_
454 454
455 455
456 456 def desc_stylize(value):
457 457 """
458 458 converts tags from value into html equivalent
459 459
460 460 :param value:
461 461 """
462 462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
469 469 '<div class="metatag" tag="lang">\\2</div>', value)
470 470 value = re.sub(r'\[([a-z]+)\]',
471 471 '<div class="metatag" tag="\\1">\\1</div>', value)
472 472
473 473 return value
474 474
475 475
476 476 def bool2icon(value):
477 477 """Returns True/False values represented as small html image of true/false
478 478 icons
479 479
480 480 :param value: bool value
481 481 """
482 482
483 483 if value is True:
484 484 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 485 alt=_('True'))
486 486
487 487 if value is False:
488 488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 489 alt=_('False'))
490 490
491 491 return value
492 492
493 493
494 494 def action_parser(user_log, feed=False):
495 495 """
496 496 This helper will action_map the specified string action into translated
497 497 fancy names with icons and links
498 498
499 499 :param user_log: user log instance
500 500 :param feed: use output for feeds (no html and fancy icons)
501 501 """
502 502
503 503 action = user_log.action
504 504 action_params = ' '
505 505
506 506 x = action.split(':')
507 507
508 508 if len(x) > 1:
509 509 action, action_params = x
510 510
511 511 def get_cs_links():
512 512 revs_limit = 3 # display this amount always
513 513 revs_top_limit = 50 # show upto this amount of changesets hidden
514 514 revs_ids = action_params.split(',')
515 515 deleted = user_log.repository is None
516 516 if deleted:
517 517 return ','.join(revs_ids)
518 518
519 519 repo_name = user_log.repository.repo_name
520 520
521 521 repo = user_log.repository.scm_instance
522 522
523 523 def lnk(rev, repo_name):
524 524
525 525 if isinstance(rev, BaseChangeset):
526 526 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
527 527 _url = url('changeset_home', repo_name=repo_name,
528 528 revision=rev.raw_id)
529 529 title = tooltip(rev.message)
530 530 else:
531 531 lbl = '%s' % rev
532 532 _url = '#'
533 533 title = _('Changeset not found')
534 534
535 535 return link_to(lbl, _url, title=title, class_='tooltip',)
536 536
537 537 revs = []
538 538 if len(filter(lambda v: v != '', revs_ids)) > 0:
539 539 for rev in revs_ids[:revs_top_limit]:
540 540 try:
541 541 rev = repo.get_changeset(rev)
542 542 revs.append(rev)
543 543 except ChangesetDoesNotExistError:
544 544 log.error('cannot find revision %s in this repo' % rev)
545 545 revs.append(rev)
546 546 continue
547 547 cs_links = []
548 548 cs_links.append(" " + ', '.join(
549 549 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
550 550 )
551 551 )
552 552
553 553 compare_view = (
554 554 ' <div class="compare_view tooltip" title="%s">'
555 555 '<a href="%s">%s</a> </div>' % (
556 556 _('Show all combined changesets %s->%s') % (
557 557 revs_ids[0], revs_ids[-1]
558 558 ),
559 559 url('changeset_home', repo_name=repo_name,
560 560 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
561 561 ),
562 562 _('compare view')
563 563 )
564 564 )
565 565
566 566 # if we have exactly one more than normally displayed
567 567 # just display it, takes less space than displaying
568 568 # "and 1 more revisions"
569 569 if len(revs_ids) == revs_limit + 1:
570 570 rev = revs[revs_limit]
571 571 cs_links.append(", " + lnk(rev, repo_name))
572 572
573 573 # hidden-by-default ones
574 574 if len(revs_ids) > revs_limit + 1:
575 575 uniq_id = revs_ids[0]
576 576 html_tmpl = (
577 577 '<span> %s <a class="show_more" id="_%s" '
578 578 'href="#more">%s</a> %s</span>'
579 579 )
580 580 if not feed:
581 581 cs_links.append(html_tmpl % (
582 582 _('and'),
583 583 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
584 584 _('revisions')
585 585 )
586 586 )
587 587
588 588 if not feed:
589 589 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
590 590 else:
591 591 html_tmpl = '<span id="%s"> %s </span>'
592 592
593 593 morelinks = ', '.join(
594 594 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
595 595 )
596 596
597 597 if len(revs_ids) > revs_top_limit:
598 598 morelinks += ', ...'
599 599
600 600 cs_links.append(html_tmpl % (uniq_id, morelinks))
601 601 if len(revs) > 1:
602 602 cs_links.append(compare_view)
603 603 return ''.join(cs_links)
604 604
605 605 def get_fork_name():
606 606 repo_name = action_params
607 return _('fork name ') + str(link_to(action_params, url('summary_home',
608 repo_name=repo_name,)))
607 _url = url('summary_home', repo_name=repo_name)
608 return _('fork name %s') % link_to(action_params, _url)
609 609
610 610 def get_user_name():
611 611 user_name = action_params
612 612 return user_name
613 613
614 614 def get_users_group():
615 615 group_name = action_params
616 616 return group_name
617 617
618 618 def get_pull_request():
619 619 pull_request_id = action_params
620 620 repo_name = user_log.repository.repo_name
621 621 return link_to(_('Pull request #%s') % pull_request_id,
622 622 url('pullrequest_show', repo_name=repo_name,
623 623 pull_request_id=pull_request_id))
624 624
625 625 # action : translated str, callback(extractor), icon
626 626 action_map = {
627 627 'user_deleted_repo': (_('[deleted] repository'),
628 628 None, 'database_delete.png'),
629 629 'user_created_repo': (_('[created] repository'),
630 630 None, 'database_add.png'),
631 631 'user_created_fork': (_('[created] repository as fork'),
632 632 None, 'arrow_divide.png'),
633 633 'user_forked_repo': (_('[forked] repository'),
634 634 get_fork_name, 'arrow_divide.png'),
635 635 'user_updated_repo': (_('[updated] repository'),
636 636 None, 'database_edit.png'),
637 637 'admin_deleted_repo': (_('[delete] repository'),
638 638 None, 'database_delete.png'),
639 639 'admin_created_repo': (_('[created] repository'),
640 640 None, 'database_add.png'),
641 641 'admin_forked_repo': (_('[forked] repository'),
642 642 None, 'arrow_divide.png'),
643 643 'admin_updated_repo': (_('[updated] repository'),
644 644 None, 'database_edit.png'),
645 645 'admin_created_user': (_('[created] user'),
646 646 get_user_name, 'user_add.png'),
647 647 'admin_updated_user': (_('[updated] user'),
648 648 get_user_name, 'user_edit.png'),
649 649 'admin_created_users_group': (_('[created] users group'),
650 650 get_users_group, 'group_add.png'),
651 651 'admin_updated_users_group': (_('[updated] users group'),
652 652 get_users_group, 'group_edit.png'),
653 653 'user_commented_revision': (_('[commented] on revision in repository'),
654 654 get_cs_links, 'comment_add.png'),
655 655 'user_commented_pull_request': (_('[commented] on pull request for'),
656 656 get_pull_request, 'comment_add.png'),
657 657 'user_closed_pull_request': (_('[closed] pull request for'),
658 658 get_pull_request, 'tick.png'),
659 659 'push': (_('[pushed] into'),
660 660 get_cs_links, 'script_add.png'),
661 661 'push_local': (_('[committed via RhodeCode] into repository'),
662 662 get_cs_links, 'script_edit.png'),
663 663 'push_remote': (_('[pulled from remote] into repository'),
664 664 get_cs_links, 'connect.png'),
665 665 'pull': (_('[pulled] from'),
666 666 None, 'down_16.png'),
667 667 'started_following_repo': (_('[started following] repository'),
668 668 None, 'heart_add.png'),
669 669 'stopped_following_repo': (_('[stopped following] repository'),
670 670 None, 'heart_delete.png'),
671 671 }
672 672
673 673 action_str = action_map.get(action, action)
674 674 if feed:
675 675 action = action_str[0].replace('[', '').replace(']', '')
676 676 else:
677 677 action = action_str[0]\
678 678 .replace('[', '<span class="journal_highlight">')\
679 679 .replace(']', '</span>')
680 680
681 681 action_params_func = lambda: ""
682 682
683 683 if callable(action_str[1]):
684 684 action_params_func = action_str[1]
685 685
686 686 def action_parser_icon():
687 687 action = user_log.action
688 688 action_params = None
689 689 x = action.split(':')
690 690
691 691 if len(x) > 1:
692 692 action, action_params = x
693 693
694 694 tmpl = """<img src="%s%s" alt="%s"/>"""
695 695 ico = action_map.get(action, ['', '', ''])[2]
696 696 return literal(tmpl % ((url('/images/icons/')), ico, action))
697 697
698 698 # returned callbacks we need to call to get
699 699 return [lambda: literal(action), action_params_func, action_parser_icon]
700 700
701 701
702 702
703 703 #==============================================================================
704 704 # PERMS
705 705 #==============================================================================
706 706 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
707 707 HasRepoPermissionAny, HasRepoPermissionAll
708 708
709 709
710 710 #==============================================================================
711 711 # GRAVATAR URL
712 712 #==============================================================================
713 713
714 714 def gravatar_url(email_address, size=30):
715 715 from pylons import url ## doh, we need to re-import url to mock it later
716 716 if(str2bool(config['app_conf'].get('use_gravatar')) and
717 717 config['app_conf'].get('alternative_gravatar_url')):
718 718 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
719 719 parsed_url = urlparse.urlparse(url.current(qualified=True))
720 720 tmpl = tmpl.replace('{email}', email_address)\
721 721 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
722 722 .replace('{netloc}', parsed_url.netloc)\
723 723 .replace('{scheme}', parsed_url.scheme)\
724 724 .replace('{size}', str(size))
725 725 return tmpl
726 726
727 727 if (not str2bool(config['app_conf'].get('use_gravatar')) or
728 728 not email_address or email_address == 'anonymous@rhodecode.org'):
729 729 f = lambda a, l: min(l, key=lambda x: abs(x - a))
730 730 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
731 731
732 732 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
733 733 default = 'identicon'
734 734 baseurl_nossl = "http://www.gravatar.com/avatar/"
735 735 baseurl_ssl = "https://secure.gravatar.com/avatar/"
736 736 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
737 737
738 738 if isinstance(email_address, unicode):
739 739 #hashlib crashes on unicode items
740 740 email_address = safe_str(email_address)
741 741 # construct the url
742 742 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
743 743 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
744 744
745 745 return gravatar_url
746 746
747 747
748 748 #==============================================================================
749 749 # REPO PAGER, PAGER FOR REPOSITORY
750 750 #==============================================================================
751 751 class RepoPage(Page):
752 752
753 753 def __init__(self, collection, page=1, items_per_page=20,
754 754 item_count=None, url=None, **kwargs):
755 755
756 756 """Create a "RepoPage" instance. special pager for paging
757 757 repository
758 758 """
759 759 self._url_generator = url
760 760
761 761 # Safe the kwargs class-wide so they can be used in the pager() method
762 762 self.kwargs = kwargs
763 763
764 764 # Save a reference to the collection
765 765 self.original_collection = collection
766 766
767 767 self.collection = collection
768 768
769 769 # The self.page is the number of the current page.
770 770 # The first page has the number 1!
771 771 try:
772 772 self.page = int(page) # make it int() if we get it as a string
773 773 except (ValueError, TypeError):
774 774 self.page = 1
775 775
776 776 self.items_per_page = items_per_page
777 777
778 778 # Unless the user tells us how many items the collections has
779 779 # we calculate that ourselves.
780 780 if item_count is not None:
781 781 self.item_count = item_count
782 782 else:
783 783 self.item_count = len(self.collection)
784 784
785 785 # Compute the number of the first and last available page
786 786 if self.item_count > 0:
787 787 self.first_page = 1
788 788 self.page_count = int(math.ceil(float(self.item_count) /
789 789 self.items_per_page))
790 790 self.last_page = self.first_page + self.page_count - 1
791 791
792 792 # Make sure that the requested page number is the range of
793 793 # valid pages
794 794 if self.page > self.last_page:
795 795 self.page = self.last_page
796 796 elif self.page < self.first_page:
797 797 self.page = self.first_page
798 798
799 799 # Note: the number of items on this page can be less than
800 800 # items_per_page if the last page is not full
801 801 self.first_item = max(0, (self.item_count) - (self.page *
802 802 items_per_page))
803 803 self.last_item = ((self.item_count - 1) - items_per_page *
804 804 (self.page - 1))
805 805
806 806 self.items = list(self.collection[self.first_item:self.last_item + 1])
807 807
808 808 # Links to previous and next page
809 809 if self.page > self.first_page:
810 810 self.previous_page = self.page - 1
811 811 else:
812 812 self.previous_page = None
813 813
814 814 if self.page < self.last_page:
815 815 self.next_page = self.page + 1
816 816 else:
817 817 self.next_page = None
818 818
819 819 # No items available
820 820 else:
821 821 self.first_page = None
822 822 self.page_count = 0
823 823 self.last_page = None
824 824 self.first_item = None
825 825 self.last_item = None
826 826 self.previous_page = None
827 827 self.next_page = None
828 828 self.items = []
829 829
830 830 # This is a subclass of the 'list' type. Initialise the list now.
831 831 list.__init__(self, reversed(self.items))
832 832
833 833
834 834 def changed_tooltip(nodes):
835 835 """
836 836 Generates a html string for changed nodes in changeset page.
837 837 It limits the output to 30 entries
838 838
839 839 :param nodes: LazyNodesGenerator
840 840 """
841 841 if nodes:
842 842 pref = ': <br/> '
843 843 suf = ''
844 844 if len(nodes) > 30:
845 845 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
846 846 return literal(pref + '<br/> '.join([safe_unicode(x.path)
847 847 for x in nodes[:30]]) + suf)
848 848 else:
849 849 return ': ' + _('No Files')
850 850
851 851
852 852 def repo_link(groups_and_repos):
853 853 """
854 854 Makes a breadcrumbs link to repo within a group
855 855 joins &raquo; on each group to create a fancy link
856 856
857 857 ex::
858 858 group >> subgroup >> repo
859 859
860 860 :param groups_and_repos:
861 861 """
862 862 groups, repo_name = groups_and_repos
863 863
864 864 if not groups:
865 865 return repo_name
866 866 else:
867 867 def make_link(group):
868 868 return link_to(group.name, url('repos_group_home',
869 869 group_name=group.group_name))
870 870 return literal(' &raquo; '.join(map(make_link, groups)) + \
871 871 " &raquo; " + repo_name)
872 872
873 873
874 874 def fancy_file_stats(stats):
875 875 """
876 876 Displays a fancy two colored bar for number of added/deleted
877 877 lines of code on file
878 878
879 879 :param stats: two element list of added/deleted lines of code
880 880 """
881 881
882 882 a, d, t = stats[0], stats[1], stats[0] + stats[1]
883 883 width = 100
884 884 unit = float(width) / (t or 1)
885 885
886 886 # needs > 9% of width to be visible or 0 to be hidden
887 887 a_p = max(9, unit * a) if a > 0 else 0
888 888 d_p = max(9, unit * d) if d > 0 else 0
889 889 p_sum = a_p + d_p
890 890
891 891 if p_sum > width:
892 892 #adjust the percentage to be == 100% since we adjusted to 9
893 893 if a_p > d_p:
894 894 a_p = a_p - (p_sum - width)
895 895 else:
896 896 d_p = d_p - (p_sum - width)
897 897
898 898 a_v = a if a > 0 else ''
899 899 d_v = d if d > 0 else ''
900 900
901 901 def cgen(l_type):
902 902 mapping = {'tr': 'top-right-rounded-corner-mid',
903 903 'tl': 'top-left-rounded-corner-mid',
904 904 'br': 'bottom-right-rounded-corner-mid',
905 905 'bl': 'bottom-left-rounded-corner-mid'}
906 906 map_getter = lambda x: mapping[x]
907 907
908 908 if l_type == 'a' and d_v:
909 909 #case when added and deleted are present
910 910 return ' '.join(map(map_getter, ['tl', 'bl']))
911 911
912 912 if l_type == 'a' and not d_v:
913 913 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
914 914
915 915 if l_type == 'd' and a_v:
916 916 return ' '.join(map(map_getter, ['tr', 'br']))
917 917
918 918 if l_type == 'd' and not a_v:
919 919 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
920 920
921 921 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
922 922 cgen('a'), a_p, a_v
923 923 )
924 924 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
925 925 cgen('d'), d_p, d_v
926 926 )
927 927 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
928 928
929 929
930 930 def urlify_text(text_):
931 931 import re
932 932
933 933 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
934 934 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
935 935
936 936 def url_func(match_obj):
937 937 url_full = match_obj.groups()[0]
938 938 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
939 939
940 940 return literal(url_pat.sub(url_func, text_))
941 941
942 942
943 943 def urlify_changesets(text_, repository):
944 944 """
945 945 Extract revision ids from changeset and make link from them
946 946
947 947 :param text_:
948 948 :param repository:
949 949 """
950 950 import re
951 951 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
952 952
953 953 def url_func(match_obj):
954 954 rev = match_obj.groups()[0]
955 955 pref = ''
956 956 if match_obj.group().startswith(' '):
957 957 pref = ' '
958 958 tmpl = (
959 959 '%(pref)s<a class="%(cls)s" href="%(url)s">'
960 960 '%(rev)s'
961 961 '</a>'
962 962 )
963 963 return tmpl % {
964 964 'pref': pref,
965 965 'cls': 'revision-link',
966 966 'url': url('changeset_home', repo_name=repository, revision=rev),
967 967 'rev': rev,
968 968 }
969 969
970 970 newtext = URL_PAT.sub(url_func, text_)
971 971
972 972 return newtext
973 973
974 974
975 975 def urlify_commit(text_, repository=None, link_=None):
976 976 """
977 977 Parses given text message and makes proper links.
978 978 issues are linked to given issue-server, and rest is a changeset link
979 979 if link_ is given, in other case it's a plain text
980 980
981 981 :param text_:
982 982 :param repository:
983 983 :param link_: changeset link
984 984 """
985 985 import re
986 986 import traceback
987 987
988 988 def escaper(string):
989 989 return string.replace('<', '&lt;').replace('>', '&gt;')
990 990
991 991 def linkify_others(t, l):
992 992 urls = re.compile(r'(\<a.*?\<\/a\>)',)
993 993 links = []
994 994 for e in urls.split(t):
995 995 if not urls.match(e):
996 996 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
997 997 else:
998 998 links.append(e)
999 999
1000 1000 return ''.join(links)
1001 1001
1002 1002 # urlify changesets - extrac revisions and make link out of them
1003 1003 newtext = urlify_changesets(escaper(text_), repository)
1004 1004
1005 1005 try:
1006 1006 conf = config['app_conf']
1007 1007
1008 1008 # allow multiple issue servers to be used
1009 1009 valid_indices = [
1010 1010 x.group(1)
1011 1011 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1012 1012 if x and 'issue_server_link%s' % x.group(1) in conf
1013 1013 and 'issue_prefix%s' % x.group(1) in conf
1014 1014 ]
1015 1015
1016 1016 log.debug('found issue server suffixes `%s` during valuation of: %s'
1017 1017 % (','.join(valid_indices), newtext))
1018 1018
1019 1019 for pattern_index in valid_indices:
1020 1020 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1021 1021 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1022 1022 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1023 1023
1024 1024 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1025 1025 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1026 1026 ISSUE_PREFIX))
1027 1027
1028 1028 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1029 1029
1030 1030 def url_func(match_obj):
1031 1031 pref = ''
1032 1032 if match_obj.group().startswith(' '):
1033 1033 pref = ' '
1034 1034
1035 1035 issue_id = ''.join(match_obj.groups())
1036 1036 tmpl = (
1037 1037 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1038 1038 '%(issue-prefix)s%(id-repr)s'
1039 1039 '</a>'
1040 1040 )
1041 1041 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1042 1042 if repository:
1043 1043 url = url.replace('{repo}', repository)
1044 1044 repo_name = repository.split(URL_SEP)[-1]
1045 1045 url = url.replace('{repo_name}', repo_name)
1046 1046
1047 1047 return tmpl % {
1048 1048 'pref': pref,
1049 1049 'cls': 'issue-tracker-link',
1050 1050 'url': url,
1051 1051 'id-repr': issue_id,
1052 1052 'issue-prefix': ISSUE_PREFIX,
1053 1053 'serv': ISSUE_SERVER_LNK,
1054 1054 }
1055 1055 newtext = URL_PAT.sub(url_func, newtext)
1056 1056 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1057 1057
1058 1058 # if we actually did something above
1059 1059 if valid_indices:
1060 1060 if link_:
1061 1061 # wrap not links into final link => link_
1062 1062 newtext = linkify_others(newtext, link_)
1063 1063
1064 1064 return literal(newtext)
1065 1065 except:
1066 1066 log.error(traceback.format_exc())
1067 1067 pass
1068 1068
1069 1069 return newtext
1070 1070
1071 1071
1072 1072 def rst(source):
1073 1073 return literal('<div class="rst-block">%s</div>' %
1074 1074 MarkupRenderer.rst(source))
1075 1075
1076 1076
1077 1077 def rst_w_mentions(source):
1078 1078 """
1079 1079 Wrapped rst renderer with @mention highlighting
1080 1080
1081 1081 :param source:
1082 1082 """
1083 1083 return literal('<div class="rst-block">%s</div>' %
1084 1084 MarkupRenderer.rst_with_mentions(source))
1085 1085
1086 1086
1087 1087 def changeset_status(repo, revision):
1088 1088 return ChangesetStatusModel().get_status(repo, revision)
1089 1089
1090 1090
1091 1091 def changeset_status_lbl(changeset_status):
1092 1092 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1093 1093
1094 1094
1095 1095 def get_permission_name(key):
1096 1096 return dict(Permission.PERMS).get(key)
General Comments 0
You need to be logged in to leave comments. Login now