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