##// END OF EJS Templates
py3: don't use lambda assignments.
marcink -
r4397:f5f03a80 default
parent child Browse files
Show More
@@ -1,2029 +1,2040 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27
28 28 import os
29 29 import random
30 30 import hashlib
31 31 import StringIO
32 32 import textwrap
33 33 import urllib
34 34 import math
35 35 import logging
36 36 import re
37 37 import time
38 38 import string
39 39 import hashlib
40 40 from collections import OrderedDict
41 41
42 42 import pygments
43 43 import itertools
44 44 import fnmatch
45 45 import bleach
46 46
47 47 from pyramid import compat
48 48 from datetime import datetime
49 49 from functools import partial
50 50 from pygments.formatters.html import HtmlFormatter
51 51 from pygments.lexers import (
52 52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53 53
54 54 from pyramid.threadlocal import get_current_request
55 55
56 56 from webhelpers2.html import literal, HTML, escape
57 57 from webhelpers2.html._autolink import _auto_link_urls
58 58 from webhelpers2.html.tools import (
59 59 button_to, highlight, js_obfuscate, strip_links, strip_tags)
60 60
61 61 from webhelpers2.text import (
62 62 chop_at, collapse, convert_accented_entities,
63 63 convert_misc_entities, lchop, plural, rchop, remove_formatting,
64 64 replace_whitespace, urlify, truncate, wrap_paragraphs)
65 65 from webhelpers2.date import time_ago_in_words
66 66
67 67 from webhelpers2.html.tags import (
68 68 _input, NotGiven, _make_safe_id_component as safeid,
69 69 form as insecure_form,
70 70 auto_discovery_link, checkbox, end_form, file,
71 71 hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol,
72 72 select as raw_select, stylesheet_link, submit, text, password, textarea,
73 73 ul, radio, Options)
74 74
75 75 from webhelpers2.number import format_byte_size
76 76
77 77 from rhodecode.lib.action_parser import action_parser
78 78 from rhodecode.lib.pagination import Page, RepoPage, SqlPage
79 79 from rhodecode.lib.ext_json import json
80 80 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
81 81 from rhodecode.lib.utils2 import (
82 82 str2bool, safe_unicode, safe_str,
83 83 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime,
84 84 AttributeDict, safe_int, md5, md5_safe, get_host_info)
85 85 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
86 86 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
87 87 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
88 88 from rhodecode.lib.index.search_utils import get_matching_line_offsets
89 89 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
90 90 from rhodecode.model.changeset_status import ChangesetStatusModel
91 91 from rhodecode.model.db import Permission, User, Repository
92 92 from rhodecode.model.repo_group import RepoGroupModel
93 93 from rhodecode.model.settings import IssueTrackerSettingsModel
94 94
95 95
96 96 log = logging.getLogger(__name__)
97 97
98 98
99 99 DEFAULT_USER = User.DEFAULT_USER
100 100 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
101 101
102 102
103 103 def asset(path, ver=None, **kwargs):
104 104 """
105 105 Helper to generate a static asset file path for rhodecode assets
106 106
107 107 eg. h.asset('images/image.png', ver='3923')
108 108
109 109 :param path: path of asset
110 110 :param ver: optional version query param to append as ?ver=
111 111 """
112 112 request = get_current_request()
113 113 query = {}
114 114 query.update(kwargs)
115 115 if ver:
116 116 query = {'ver': ver}
117 117 return request.static_path(
118 118 'rhodecode:public/{}'.format(path), _query=query)
119 119
120 120
121 121 default_html_escape_table = {
122 122 ord('&'): u'&amp;',
123 123 ord('<'): u'&lt;',
124 124 ord('>'): u'&gt;',
125 125 ord('"'): u'&quot;',
126 126 ord("'"): u'&#39;',
127 127 }
128 128
129 129
130 130 def html_escape(text, html_escape_table=default_html_escape_table):
131 131 """Produce entities within text."""
132 132 return text.translate(html_escape_table)
133 133
134 134
135 135 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
136 136 """
137 137 Truncate string ``s`` at the first occurrence of ``sub``.
138 138
139 139 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
140 140 """
141 141 suffix_if_chopped = suffix_if_chopped or ''
142 142 pos = s.find(sub)
143 143 if pos == -1:
144 144 return s
145 145
146 146 if inclusive:
147 147 pos += len(sub)
148 148
149 149 chopped = s[:pos]
150 150 left = s[pos:].strip()
151 151
152 152 if left and suffix_if_chopped:
153 153 chopped += suffix_if_chopped
154 154
155 155 return chopped
156 156
157 157
158 158 def shorter(text, size=20, prefix=False):
159 159 postfix = '...'
160 160 if len(text) > size:
161 161 if prefix:
162 162 # shorten in front
163 163 return postfix + text[-(size - len(postfix)):]
164 164 else:
165 165 return text[:size - len(postfix)] + postfix
166 166 return text
167 167
168 168
169 169 def reset(name, value=None, id=NotGiven, type="reset", **attrs):
170 170 """
171 171 Reset button
172 172 """
173 173 return _input(type, name, value, id, attrs)
174 174
175 175
176 176 def select(name, selected_values, options, id=NotGiven, **attrs):
177 177
178 178 if isinstance(options, (list, tuple)):
179 179 options_iter = options
180 180 # Handle old value,label lists ... where value also can be value,label lists
181 181 options = Options()
182 182 for opt in options_iter:
183 183 if isinstance(opt, tuple) and len(opt) == 2:
184 184 value, label = opt
185 185 elif isinstance(opt, basestring):
186 186 value = label = opt
187 187 else:
188 188 raise ValueError('invalid select option type %r' % type(opt))
189 189
190 190 if isinstance(value, (list, tuple)):
191 191 option_group = options.add_optgroup(label)
192 192 for opt2 in value:
193 193 if isinstance(opt2, tuple) and len(opt2) == 2:
194 194 group_value, group_label = opt2
195 195 elif isinstance(opt2, basestring):
196 196 group_value = group_label = opt2
197 197 else:
198 198 raise ValueError('invalid select option type %r' % type(opt2))
199 199
200 200 option_group.add_option(group_label, group_value)
201 201 else:
202 202 options.add_option(label, value)
203 203
204 204 return raw_select(name, selected_values, options, id=id, **attrs)
205 205
206 206
207 207 def branding(name, length=40):
208 208 return truncate(name, length, indicator="")
209 209
210 210
211 211 def FID(raw_id, path):
212 212 """
213 213 Creates a unique ID for filenode based on it's hash of path and commit
214 214 it's safe to use in urls
215 215
216 216 :param raw_id:
217 217 :param path:
218 218 """
219 219
220 220 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
221 221
222 222
223 223 class _GetError(object):
224 224 """Get error from form_errors, and represent it as span wrapped error
225 225 message
226 226
227 227 :param field_name: field to fetch errors for
228 228 :param form_errors: form errors dict
229 229 """
230 230
231 231 def __call__(self, field_name, form_errors):
232 232 tmpl = """<span class="error_msg">%s</span>"""
233 233 if form_errors and field_name in form_errors:
234 234 return literal(tmpl % form_errors.get(field_name))
235 235
236 236
237 237 get_error = _GetError()
238 238
239 239
240 240 class _ToolTip(object):
241 241
242 242 def __call__(self, tooltip_title, trim_at=50):
243 243 """
244 244 Special function just to wrap our text into nice formatted
245 245 autowrapped text
246 246
247 247 :param tooltip_title:
248 248 """
249 249 tooltip_title = escape(tooltip_title)
250 250 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
251 251 return tooltip_title
252 252
253 253
254 254 tooltip = _ToolTip()
255 255
256 256 files_icon = u'<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy file path"></i>'
257 257
258 258
259 259 def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None,
260 260 limit_items=False, linkify_last_item=False, hide_last_item=False,
261 261 copy_path_icon=True):
262 262 if isinstance(file_path, str):
263 263 file_path = safe_unicode(file_path)
264 264
265 265 if at_ref:
266 266 route_qry = {'at': at_ref}
267 267 default_landing_ref = at_ref or landing_ref_name or commit_id
268 268 else:
269 269 route_qry = None
270 270 default_landing_ref = commit_id
271 271
272 272 # first segment is a `HOME` link to repo files root location
273 273 root_name = literal(u'<i class="icon-home"></i>')
274 274
275 275 url_segments = [
276 276 link_to(
277 277 root_name,
278 278 repo_files_by_ref_url(
279 279 repo_name,
280 280 repo_type,
281 281 f_path=None, # None here is a special case for SVN repos,
282 282 # that won't prefix with a ref
283 283 ref_name=default_landing_ref,
284 284 commit_id=commit_id,
285 285 query=route_qry
286 286 )
287 287 )]
288 288
289 289 path_segments = file_path.split('/')
290 290 last_cnt = len(path_segments) - 1
291 291 for cnt, segment in enumerate(path_segments):
292 292 if not segment:
293 293 continue
294 294 segment_html = escape(segment)
295 295
296 296 last_item = cnt == last_cnt
297 297
298 298 if last_item and hide_last_item:
299 299 # iterate over and hide last element
300 300 continue
301 301
302 302 if last_item and linkify_last_item is False:
303 303 # plain version
304 304 url_segments.append(segment_html)
305 305 else:
306 306 url_segments.append(
307 307 link_to(
308 308 segment_html,
309 309 repo_files_by_ref_url(
310 310 repo_name,
311 311 repo_type,
312 312 f_path='/'.join(path_segments[:cnt + 1]),
313 313 ref_name=default_landing_ref,
314 314 commit_id=commit_id,
315 315 query=route_qry
316 316 ),
317 317 ))
318 318
319 319 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
320 320 if limit_items and len(limited_url_segments) < len(url_segments):
321 321 url_segments = limited_url_segments
322 322
323 323 full_path = file_path
324 324 if copy_path_icon:
325 325 icon = files_icon.format(escape(full_path))
326 326 else:
327 327 icon = ''
328 328
329 329 if file_path == '':
330 330 return root_name
331 331 else:
332 332 return literal(' / '.join(url_segments) + icon)
333 333
334 334
335 335 def files_url_data(request):
336 336 matchdict = request.matchdict
337 337
338 338 if 'f_path' not in matchdict:
339 339 matchdict['f_path'] = ''
340 340
341 341 if 'commit_id' not in matchdict:
342 342 matchdict['commit_id'] = 'tip'
343 343
344 344 return json.dumps(matchdict)
345 345
346 346
347 347 def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ):
348 348 _is_svn = is_svn(db_repo_type)
349 349 final_f_path = f_path
350 350
351 351 if _is_svn:
352 352 """
353 353 For SVN the ref_name cannot be used as a commit_id, it needs to be prefixed with
354 354 actually commit_id followed by the ref_name. This should be done only in case
355 355 This is a initial landing url, without additional paths.
356 356
357 357 like: /1000/tags/1.0.0/?at=tags/1.0.0
358 358 """
359 359
360 360 if ref_name and ref_name != 'tip':
361 361 # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it
362 362 # for SVN we only do this magic prefix if it's root, .eg landing revision
363 363 # of files link. If we are in the tree we don't need this since we traverse the url
364 364 # that has everything stored
365 365 if f_path in ['', '/']:
366 366 final_f_path = '/'.join([ref_name, f_path])
367 367
368 368 # SVN always needs a commit_id explicitly, without a named REF
369 369 default_commit_id = commit_id
370 370 else:
371 371 """
372 372 For git and mercurial we construct a new URL using the names instead of commit_id
373 373 like: /master/some_path?at=master
374 374 """
375 375 # We currently do not support branches with slashes
376 376 if '/' in ref_name:
377 377 default_commit_id = commit_id
378 378 else:
379 379 default_commit_id = ref_name
380 380
381 381 # sometimes we pass f_path as None, to indicate explicit no prefix,
382 382 # we translate it to string to not have None
383 383 final_f_path = final_f_path or ''
384 384
385 385 files_url = route_path(
386 386 'repo_files',
387 387 repo_name=db_repo_name,
388 388 commit_id=default_commit_id,
389 389 f_path=final_f_path,
390 390 _query=query
391 391 )
392 392 return files_url
393 393
394 394
395 395 def code_highlight(code, lexer, formatter, use_hl_filter=False):
396 396 """
397 397 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
398 398
399 399 If ``outfile`` is given and a valid file object (an object
400 400 with a ``write`` method), the result will be written to it, otherwise
401 401 it is returned as a string.
402 402 """
403 403 if use_hl_filter:
404 404 # add HL filter
405 405 from rhodecode.lib.index import search_utils
406 406 lexer.add_filter(search_utils.ElasticSearchHLFilter())
407 407 return pygments.format(pygments.lex(code, lexer), formatter)
408 408
409 409
410 410 class CodeHtmlFormatter(HtmlFormatter):
411 411 """
412 412 My code Html Formatter for source codes
413 413 """
414 414
415 415 def wrap(self, source, outfile):
416 416 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
417 417
418 418 def _wrap_code(self, source):
419 419 for cnt, it in enumerate(source):
420 420 i, t = it
421 421 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
422 422 yield i, t
423 423
424 424 def _wrap_tablelinenos(self, inner):
425 425 dummyoutfile = StringIO.StringIO()
426 426 lncount = 0
427 427 for t, line in inner:
428 428 if t:
429 429 lncount += 1
430 430 dummyoutfile.write(line)
431 431
432 432 fl = self.linenostart
433 433 mw = len(str(lncount + fl - 1))
434 434 sp = self.linenospecial
435 435 st = self.linenostep
436 436 la = self.lineanchors
437 437 aln = self.anchorlinenos
438 438 nocls = self.noclasses
439 439 if sp:
440 440 lines = []
441 441
442 442 for i in range(fl, fl + lncount):
443 443 if i % st == 0:
444 444 if i % sp == 0:
445 445 if aln:
446 446 lines.append('<a href="#%s%d" class="special">%*d</a>' %
447 447 (la, i, mw, i))
448 448 else:
449 449 lines.append('<span class="special">%*d</span>' % (mw, i))
450 450 else:
451 451 if aln:
452 452 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
453 453 else:
454 454 lines.append('%*d' % (mw, i))
455 455 else:
456 456 lines.append('')
457 457 ls = '\n'.join(lines)
458 458 else:
459 459 lines = []
460 460 for i in range(fl, fl + lncount):
461 461 if i % st == 0:
462 462 if aln:
463 463 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
464 464 else:
465 465 lines.append('%*d' % (mw, i))
466 466 else:
467 467 lines.append('')
468 468 ls = '\n'.join(lines)
469 469
470 470 # in case you wonder about the seemingly redundant <div> here: since the
471 471 # content in the other cell also is wrapped in a div, some browsers in
472 472 # some configurations seem to mess up the formatting...
473 473 if nocls:
474 474 yield 0, ('<table class="%stable">' % self.cssclass +
475 475 '<tr><td><div class="linenodiv" '
476 476 'style="background-color: #f0f0f0; padding-right: 10px">'
477 477 '<pre style="line-height: 125%">' +
478 478 ls + '</pre></div></td><td id="hlcode" class="code">')
479 479 else:
480 480 yield 0, ('<table class="%stable">' % self.cssclass +
481 481 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
482 482 ls + '</pre></div></td><td id="hlcode" class="code">')
483 483 yield 0, dummyoutfile.getvalue()
484 484 yield 0, '</td></tr></table>'
485 485
486 486
487 487 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
488 488 def __init__(self, **kw):
489 489 # only show these line numbers if set
490 490 self.only_lines = kw.pop('only_line_numbers', [])
491 491 self.query_terms = kw.pop('query_terms', [])
492 492 self.max_lines = kw.pop('max_lines', 5)
493 493 self.line_context = kw.pop('line_context', 3)
494 494 self.url = kw.pop('url', None)
495 495
496 496 super(CodeHtmlFormatter, self).__init__(**kw)
497 497
498 498 def _wrap_code(self, source):
499 499 for cnt, it in enumerate(source):
500 500 i, t = it
501 501 t = '<pre>%s</pre>' % t
502 502 yield i, t
503 503
504 504 def _wrap_tablelinenos(self, inner):
505 505 yield 0, '<table class="code-highlight %stable">' % self.cssclass
506 506
507 507 last_shown_line_number = 0
508 508 current_line_number = 1
509 509
510 510 for t, line in inner:
511 511 if not t:
512 512 yield t, line
513 513 continue
514 514
515 515 if current_line_number in self.only_lines:
516 516 if last_shown_line_number + 1 != current_line_number:
517 517 yield 0, '<tr>'
518 518 yield 0, '<td class="line">...</td>'
519 519 yield 0, '<td id="hlcode" class="code"></td>'
520 520 yield 0, '</tr>'
521 521
522 522 yield 0, '<tr>'
523 523 if self.url:
524 524 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
525 525 self.url, current_line_number, current_line_number)
526 526 else:
527 527 yield 0, '<td class="line"><a href="">%i</a></td>' % (
528 528 current_line_number)
529 529 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
530 530 yield 0, '</tr>'
531 531
532 532 last_shown_line_number = current_line_number
533 533
534 534 current_line_number += 1
535 535
536 536 yield 0, '</table>'
537 537
538 538
539 539 def hsv_to_rgb(h, s, v):
540 540 """ Convert hsv color values to rgb """
541 541
542 542 if s == 0.0:
543 543 return v, v, v
544 544 i = int(h * 6.0) # XXX assume int() truncates!
545 545 f = (h * 6.0) - i
546 546 p = v * (1.0 - s)
547 547 q = v * (1.0 - s * f)
548 548 t = v * (1.0 - s * (1.0 - f))
549 549 i = i % 6
550 550 if i == 0:
551 551 return v, t, p
552 552 if i == 1:
553 553 return q, v, p
554 554 if i == 2:
555 555 return p, v, t
556 556 if i == 3:
557 557 return p, q, v
558 558 if i == 4:
559 559 return t, p, v
560 560 if i == 5:
561 561 return v, p, q
562 562
563 563
564 564 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
565 565 """
566 566 Generator for getting n of evenly distributed colors using
567 567 hsv color and golden ratio. It always return same order of colors
568 568
569 569 :param n: number of colors to generate
570 570 :param saturation: saturation of returned colors
571 571 :param lightness: lightness of returned colors
572 572 :returns: RGB tuple
573 573 """
574 574
575 575 golden_ratio = 0.618033988749895
576 576 h = 0.22717784590367374
577 577
578 578 for _ in xrange(n):
579 579 h += golden_ratio
580 580 h %= 1
581 581 HSV_tuple = [h, saturation, lightness]
582 582 RGB_tuple = hsv_to_rgb(*HSV_tuple)
583 583 yield map(lambda x: str(int(x * 256)), RGB_tuple)
584 584
585 585
586 586 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
587 587 """
588 588 Returns a function which when called with an argument returns a unique
589 589 color for that argument, eg.
590 590
591 591 :param n: number of colors to generate
592 592 :param saturation: saturation of returned colors
593 593 :param lightness: lightness of returned colors
594 594 :returns: css RGB string
595 595
596 596 >>> color_hash = color_hasher()
597 597 >>> color_hash('hello')
598 598 'rgb(34, 12, 59)'
599 599 >>> color_hash('hello')
600 600 'rgb(34, 12, 59)'
601 601 >>> color_hash('other')
602 602 'rgb(90, 224, 159)'
603 603 """
604 604
605 605 color_dict = {}
606 606 cgenerator = unique_color_generator(
607 607 saturation=saturation, lightness=lightness)
608 608
609 609 def get_color_string(thing):
610 610 if thing in color_dict:
611 611 col = color_dict[thing]
612 612 else:
613 613 col = color_dict[thing] = cgenerator.next()
614 614 return "rgb(%s)" % (', '.join(col))
615 615
616 616 return get_color_string
617 617
618 618
619 619 def get_lexer_safe(mimetype=None, filepath=None):
620 620 """
621 621 Tries to return a relevant pygments lexer using mimetype/filepath name,
622 622 defaulting to plain text if none could be found
623 623 """
624 624 lexer = None
625 625 try:
626 626 if mimetype:
627 627 lexer = get_lexer_for_mimetype(mimetype)
628 628 if not lexer:
629 629 lexer = get_lexer_for_filename(filepath)
630 630 except pygments.util.ClassNotFound:
631 631 pass
632 632
633 633 if not lexer:
634 634 lexer = get_lexer_by_name('text')
635 635
636 636 return lexer
637 637
638 638
639 639 def get_lexer_for_filenode(filenode):
640 640 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
641 641 return lexer
642 642
643 643
644 644 def pygmentize(filenode, **kwargs):
645 645 """
646 646 pygmentize function using pygments
647 647
648 648 :param filenode:
649 649 """
650 650 lexer = get_lexer_for_filenode(filenode)
651 651 return literal(code_highlight(filenode.content, lexer,
652 652 CodeHtmlFormatter(**kwargs)))
653 653
654 654
655 655 def is_following_repo(repo_name, user_id):
656 656 from rhodecode.model.scm import ScmModel
657 657 return ScmModel().is_following_repo(repo_name, user_id)
658 658
659 659
660 660 class _Message(object):
661 661 """A message returned by ``Flash.pop_messages()``.
662 662
663 663 Converting the message to a string returns the message text. Instances
664 664 also have the following attributes:
665 665
666 666 * ``message``: the message text.
667 667 * ``category``: the category specified when the message was created.
668 668 """
669 669
670 670 def __init__(self, category, message, sub_data=None):
671 671 self.category = category
672 672 self.message = message
673 673 self.sub_data = sub_data or {}
674 674
675 675 def __str__(self):
676 676 return self.message
677 677
678 678 __unicode__ = __str__
679 679
680 680 def __html__(self):
681 681 return escape(safe_unicode(self.message))
682 682
683 683
684 684 class Flash(object):
685 685 # List of allowed categories. If None, allow any category.
686 686 categories = ["warning", "notice", "error", "success"]
687 687
688 688 # Default category if none is specified.
689 689 default_category = "notice"
690 690
691 691 def __init__(self, session_key="flash", categories=None,
692 692 default_category=None):
693 693 """
694 694 Instantiate a ``Flash`` object.
695 695
696 696 ``session_key`` is the key to save the messages under in the user's
697 697 session.
698 698
699 699 ``categories`` is an optional list which overrides the default list
700 700 of categories.
701 701
702 702 ``default_category`` overrides the default category used for messages
703 703 when none is specified.
704 704 """
705 705 self.session_key = session_key
706 706 if categories is not None:
707 707 self.categories = categories
708 708 if default_category is not None:
709 709 self.default_category = default_category
710 710 if self.categories and self.default_category not in self.categories:
711 711 raise ValueError(
712 712 "unrecognized default category %r" % (self.default_category,))
713 713
714 714 def pop_messages(self, session=None, request=None):
715 715 """
716 716 Return all accumulated messages and delete them from the session.
717 717
718 718 The return value is a list of ``Message`` objects.
719 719 """
720 720 messages = []
721 721
722 722 if not session:
723 723 if not request:
724 724 request = get_current_request()
725 725 session = request.session
726 726
727 727 # Pop the 'old' pylons flash messages. They are tuples of the form
728 728 # (category, message)
729 729 for cat, msg in session.pop(self.session_key, []):
730 730 messages.append(_Message(cat, msg))
731 731
732 732 # Pop the 'new' pyramid flash messages for each category as list
733 733 # of strings.
734 734 for cat in self.categories:
735 735 for msg in session.pop_flash(queue=cat):
736 736 sub_data = {}
737 737 if hasattr(msg, 'rsplit'):
738 738 flash_data = msg.rsplit('|DELIM|', 1)
739 739 org_message = flash_data[0]
740 740 if len(flash_data) > 1:
741 741 sub_data = json.loads(flash_data[1])
742 742 else:
743 743 org_message = msg
744 744
745 745 messages.append(_Message(cat, org_message, sub_data=sub_data))
746 746
747 747 # Map messages from the default queue to the 'notice' category.
748 748 for msg in session.pop_flash():
749 749 messages.append(_Message('notice', msg))
750 750
751 751 session.save()
752 752 return messages
753 753
754 754 def json_alerts(self, session=None, request=None):
755 755 payloads = []
756 756 messages = flash.pop_messages(session=session, request=request) or []
757 757 for message in messages:
758 758 payloads.append({
759 759 'message': {
760 760 'message': u'{}'.format(message.message),
761 761 'level': message.category,
762 762 'force': True,
763 763 'subdata': message.sub_data
764 764 }
765 765 })
766 766 return json.dumps(payloads)
767 767
768 768 def __call__(self, message, category=None, ignore_duplicate=True,
769 769 session=None, request=None):
770 770
771 771 if not session:
772 772 if not request:
773 773 request = get_current_request()
774 774 session = request.session
775 775
776 776 session.flash(
777 777 message, queue=category, allow_duplicate=not ignore_duplicate)
778 778
779 779
780 780 flash = Flash()
781 781
782 782 #==============================================================================
783 783 # SCM FILTERS available via h.
784 784 #==============================================================================
785 785 from rhodecode.lib.vcs.utils import author_name, author_email
786 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
786 from rhodecode.lib.utils2 import age, age_from_seconds
787 787 from rhodecode.model.db import User, ChangesetStatus
788 788
789 capitalize = lambda x: x.capitalize()
789
790 790 email = author_email
791 short_id = lambda x: x[:12]
792 hide_credentials = lambda x: ''.join(credentials_filter(x))
791
792
793 def capitalize(raw_text):
794 return raw_text.capitalize()
795
796
797 def short_id(long_id):
798 return long_id[:12]
799
800
801 def hide_credentials(url):
802 from rhodecode.lib.utils2 import credentials_filter
803 return credentials_filter(url)
793 804
794 805
795 806 import pytz
796 807 import tzlocal
797 808 local_timezone = tzlocal.get_localzone()
798 809
799 810
800 811 def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True):
801 812 title = value or format_date(datetime_iso)
802 813 tzinfo = '+00:00'
803 814
804 815 # detect if we have a timezone info, otherwise, add it
805 816 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
806 817 force_timezone = os.environ.get('RC_TIMEZONE', '')
807 818 if force_timezone:
808 819 force_timezone = pytz.timezone(force_timezone)
809 820 timezone = force_timezone or local_timezone
810 821 offset = timezone.localize(datetime_iso).strftime('%z')
811 822 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
812 823
813 824 return literal(
814 825 '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format(
815 826 cls='tooltip' if tooltip else '',
816 827 tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '',
817 828 title=title, dt=datetime_iso, tzinfo=tzinfo
818 829 ))
819 830
820 831
821 832 def _shorten_commit_id(commit_id, commit_len=None):
822 833 if commit_len is None:
823 834 request = get_current_request()
824 835 commit_len = request.call_context.visual.show_sha_length
825 836 return commit_id[:commit_len]
826 837
827 838
828 839 def show_id(commit, show_idx=None, commit_len=None):
829 840 """
830 841 Configurable function that shows ID
831 842 by default it's r123:fffeeefffeee
832 843
833 844 :param commit: commit instance
834 845 """
835 846 if show_idx is None:
836 847 request = get_current_request()
837 848 show_idx = request.call_context.visual.show_revision_number
838 849
839 850 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
840 851 if show_idx:
841 852 return 'r%s:%s' % (commit.idx, raw_id)
842 853 else:
843 854 return '%s' % (raw_id, )
844 855
845 856
846 857 def format_date(date):
847 858 """
848 859 use a standardized formatting for dates used in RhodeCode
849 860
850 861 :param date: date/datetime object
851 862 :return: formatted date
852 863 """
853 864
854 865 if date:
855 866 _fmt = "%a, %d %b %Y %H:%M:%S"
856 867 return safe_unicode(date.strftime(_fmt))
857 868
858 869 return u""
859 870
860 871
861 872 class _RepoChecker(object):
862 873
863 874 def __init__(self, backend_alias):
864 875 self._backend_alias = backend_alias
865 876
866 877 def __call__(self, repository):
867 878 if hasattr(repository, 'alias'):
868 879 _type = repository.alias
869 880 elif hasattr(repository, 'repo_type'):
870 881 _type = repository.repo_type
871 882 else:
872 883 _type = repository
873 884 return _type == self._backend_alias
874 885
875 886
876 887 is_git = _RepoChecker('git')
877 888 is_hg = _RepoChecker('hg')
878 889 is_svn = _RepoChecker('svn')
879 890
880 891
881 892 def get_repo_type_by_name(repo_name):
882 893 repo = Repository.get_by_repo_name(repo_name)
883 894 if repo:
884 895 return repo.repo_type
885 896
886 897
887 898 def is_svn_without_proxy(repository):
888 899 if is_svn(repository):
889 900 from rhodecode.model.settings import VcsSettingsModel
890 901 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
891 902 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
892 903 return False
893 904
894 905
895 906 def discover_user(author):
896 907 """
897 908 Tries to discover RhodeCode User based on the author string. Author string
898 909 is typically `FirstName LastName <email@address.com>`
899 910 """
900 911
901 912 # if author is already an instance use it for extraction
902 913 if isinstance(author, User):
903 914 return author
904 915
905 916 # Valid email in the attribute passed, see if they're in the system
906 917 _email = author_email(author)
907 918 if _email != '':
908 919 user = User.get_by_email(_email, case_insensitive=True, cache=True)
909 920 if user is not None:
910 921 return user
911 922
912 923 # Maybe it's a username, we try to extract it and fetch by username ?
913 924 _author = author_name(author)
914 925 user = User.get_by_username(_author, case_insensitive=True, cache=True)
915 926 if user is not None:
916 927 return user
917 928
918 929 return None
919 930
920 931
921 932 def email_or_none(author):
922 933 # extract email from the commit string
923 934 _email = author_email(author)
924 935
925 936 # If we have an email, use it, otherwise
926 937 # see if it contains a username we can get an email from
927 938 if _email != '':
928 939 return _email
929 940 else:
930 941 user = User.get_by_username(
931 942 author_name(author), case_insensitive=True, cache=True)
932 943
933 944 if user is not None:
934 945 return user.email
935 946
936 947 # No valid email, not a valid user in the system, none!
937 948 return None
938 949
939 950
940 951 def link_to_user(author, length=0, **kwargs):
941 952 user = discover_user(author)
942 953 # user can be None, but if we have it already it means we can re-use it
943 954 # in the person() function, so we save 1 intensive-query
944 955 if user:
945 956 author = user
946 957
947 958 display_person = person(author, 'username_or_name_or_email')
948 959 if length:
949 960 display_person = shorter(display_person, length)
950 961
951 962 if user:
952 963 return link_to(
953 964 escape(display_person),
954 965 route_path('user_profile', username=user.username),
955 966 **kwargs)
956 967 else:
957 968 return escape(display_person)
958 969
959 970
960 971 def link_to_group(users_group_name, **kwargs):
961 972 return link_to(
962 973 escape(users_group_name),
963 974 route_path('user_group_profile', user_group_name=users_group_name),
964 975 **kwargs)
965 976
966 977
967 978 def person(author, show_attr="username_and_name"):
968 979 user = discover_user(author)
969 980 if user:
970 981 return getattr(user, show_attr)
971 982 else:
972 983 _author = author_name(author)
973 984 _email = email(author)
974 985 return _author or _email
975 986
976 987
977 988 def author_string(email):
978 989 if email:
979 990 user = User.get_by_email(email, case_insensitive=True, cache=True)
980 991 if user:
981 992 if user.first_name or user.last_name:
982 993 return '%s %s &lt;%s&gt;' % (
983 994 user.first_name, user.last_name, email)
984 995 else:
985 996 return email
986 997 else:
987 998 return email
988 999 else:
989 1000 return None
990 1001
991 1002
992 1003 def person_by_id(id_, show_attr="username_and_name"):
993 1004 # attr to return from fetched user
994 1005 person_getter = lambda usr: getattr(usr, show_attr)
995 1006
996 1007 #maybe it's an ID ?
997 1008 if str(id_).isdigit() or isinstance(id_, int):
998 1009 id_ = int(id_)
999 1010 user = User.get(id_)
1000 1011 if user is not None:
1001 1012 return person_getter(user)
1002 1013 return id_
1003 1014
1004 1015
1005 1016 def gravatar_with_user(request, author, show_disabled=False, tooltip=False):
1006 1017 _render = request.get_partial_renderer('rhodecode:templates/base/base.mako')
1007 1018 return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip)
1008 1019
1009 1020
1010 1021 tags_paterns = OrderedDict((
1011 1022 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
1012 1023 '<div class="metatag" tag="lang">\\2</div>')),
1013 1024
1014 1025 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1015 1026 '<div class="metatag" tag="see">see: \\1 </div>')),
1016 1027
1017 1028 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
1018 1029 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
1019 1030
1020 1031 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
1021 1032 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
1022 1033
1023 1034 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
1024 1035 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
1025 1036
1026 1037 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
1027 1038 '<div class="metatag" tag="state \\1">\\1</div>')),
1028 1039
1029 1040 # label in grey
1030 1041 ('label', (re.compile(r'\[([a-z]+)\]'),
1031 1042 '<div class="metatag" tag="label">\\1</div>')),
1032 1043
1033 1044 # generic catch all in grey
1034 1045 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
1035 1046 '<div class="metatag" tag="generic">\\1</div>')),
1036 1047 ))
1037 1048
1038 1049
1039 1050 def extract_metatags(value):
1040 1051 """
1041 1052 Extract supported meta-tags from given text value
1042 1053 """
1043 1054 tags = []
1044 1055 if not value:
1045 1056 return tags, ''
1046 1057
1047 1058 for key, val in tags_paterns.items():
1048 1059 pat, replace_html = val
1049 1060 tags.extend([(key, x.group()) for x in pat.finditer(value)])
1050 1061 value = pat.sub('', value)
1051 1062
1052 1063 return tags, value
1053 1064
1054 1065
1055 1066 def style_metatag(tag_type, value):
1056 1067 """
1057 1068 converts tags from value into html equivalent
1058 1069 """
1059 1070 if not value:
1060 1071 return ''
1061 1072
1062 1073 html_value = value
1063 1074 tag_data = tags_paterns.get(tag_type)
1064 1075 if tag_data:
1065 1076 pat, replace_html = tag_data
1066 1077 # convert to plain `unicode` instead of a markup tag to be used in
1067 1078 # regex expressions. safe_unicode doesn't work here
1068 1079 html_value = pat.sub(replace_html, unicode(value))
1069 1080
1070 1081 return html_value
1071 1082
1072 1083
1073 1084 def bool2icon(value, show_at_false=True):
1074 1085 """
1075 1086 Returns boolean value of a given value, represented as html element with
1076 1087 classes that will represent icons
1077 1088
1078 1089 :param value: given value to convert to html node
1079 1090 """
1080 1091
1081 1092 if value: # does bool conversion
1082 1093 return HTML.tag('i', class_="icon-true", title='True')
1083 1094 else: # not true as bool
1084 1095 if show_at_false:
1085 1096 return HTML.tag('i', class_="icon-false", title='False')
1086 1097 return HTML.tag('i')
1087 1098
1088 1099 #==============================================================================
1089 1100 # PERMS
1090 1101 #==============================================================================
1091 1102 from rhodecode.lib.auth import (
1092 1103 HasPermissionAny, HasPermissionAll,
1093 1104 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll,
1094 1105 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token,
1095 1106 csrf_token_key, AuthUser)
1096 1107
1097 1108
1098 1109 #==============================================================================
1099 1110 # GRAVATAR URL
1100 1111 #==============================================================================
1101 1112 class InitialsGravatar(object):
1102 1113 def __init__(self, email_address, first_name, last_name, size=30,
1103 1114 background=None, text_color='#fff'):
1104 1115 self.size = size
1105 1116 self.first_name = first_name
1106 1117 self.last_name = last_name
1107 1118 self.email_address = email_address
1108 1119 self.background = background or self.str2color(email_address)
1109 1120 self.text_color = text_color
1110 1121
1111 1122 def get_color_bank(self):
1112 1123 """
1113 1124 returns a predefined list of colors that gravatars can use.
1114 1125 Those are randomized distinct colors that guarantee readability and
1115 1126 uniqueness.
1116 1127
1117 1128 generated with: http://phrogz.net/css/distinct-colors.html
1118 1129 """
1119 1130 return [
1120 1131 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1121 1132 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1122 1133 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1123 1134 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1124 1135 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1125 1136 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1126 1137 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1127 1138 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1128 1139 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1129 1140 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1130 1141 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1131 1142 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1132 1143 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1133 1144 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1134 1145 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1135 1146 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1136 1147 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1137 1148 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1138 1149 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1139 1150 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1140 1151 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1141 1152 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1142 1153 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1143 1154 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1144 1155 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1145 1156 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1146 1157 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1147 1158 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1148 1159 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1149 1160 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1150 1161 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1151 1162 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1152 1163 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1153 1164 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1154 1165 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1155 1166 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1156 1167 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1157 1168 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1158 1169 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1159 1170 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1160 1171 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1161 1172 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1162 1173 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1163 1174 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1164 1175 '#4f8c46', '#368dd9', '#5c0073'
1165 1176 ]
1166 1177
1167 1178 def rgb_to_hex_color(self, rgb_tuple):
1168 1179 """
1169 1180 Converts an rgb_tuple passed to an hex color.
1170 1181
1171 1182 :param rgb_tuple: tuple with 3 ints represents rgb color space
1172 1183 """
1173 1184 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1174 1185
1175 1186 def email_to_int_list(self, email_str):
1176 1187 """
1177 1188 Get every byte of the hex digest value of email and turn it to integer.
1178 1189 It's going to be always between 0-255
1179 1190 """
1180 1191 digest = md5_safe(email_str.lower())
1181 1192 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1182 1193
1183 1194 def pick_color_bank_index(self, email_str, color_bank):
1184 1195 return self.email_to_int_list(email_str)[0] % len(color_bank)
1185 1196
1186 1197 def str2color(self, email_str):
1187 1198 """
1188 1199 Tries to map in a stable algorithm an email to color
1189 1200
1190 1201 :param email_str:
1191 1202 """
1192 1203 color_bank = self.get_color_bank()
1193 1204 # pick position (module it's length so we always find it in the
1194 1205 # bank even if it's smaller than 256 values
1195 1206 pos = self.pick_color_bank_index(email_str, color_bank)
1196 1207 return color_bank[pos]
1197 1208
1198 1209 def normalize_email(self, email_address):
1199 1210 import unicodedata
1200 1211 # default host used to fill in the fake/missing email
1201 1212 default_host = u'localhost'
1202 1213
1203 1214 if not email_address:
1204 1215 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1205 1216
1206 1217 email_address = safe_unicode(email_address)
1207 1218
1208 1219 if u'@' not in email_address:
1209 1220 email_address = u'%s@%s' % (email_address, default_host)
1210 1221
1211 1222 if email_address.endswith(u'@'):
1212 1223 email_address = u'%s%s' % (email_address, default_host)
1213 1224
1214 1225 email_address = unicodedata.normalize('NFKD', email_address)\
1215 1226 .encode('ascii', 'ignore')
1216 1227 return email_address
1217 1228
1218 1229 def get_initials(self):
1219 1230 """
1220 1231 Returns 2 letter initials calculated based on the input.
1221 1232 The algorithm picks first given email address, and takes first letter
1222 1233 of part before @, and then the first letter of server name. In case
1223 1234 the part before @ is in a format of `somestring.somestring2` it replaces
1224 1235 the server letter with first letter of somestring2
1225 1236
1226 1237 In case function was initialized with both first and lastname, this
1227 1238 overrides the extraction from email by first letter of the first and
1228 1239 last name. We add special logic to that functionality, In case Full name
1229 1240 is compound, like Guido Von Rossum, we use last part of the last name
1230 1241 (Von Rossum) picking `R`.
1231 1242
1232 1243 Function also normalizes the non-ascii characters to they ascii
1233 1244 representation, eg Δ„ => A
1234 1245 """
1235 1246 import unicodedata
1236 1247 # replace non-ascii to ascii
1237 1248 first_name = unicodedata.normalize(
1238 1249 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1239 1250 last_name = unicodedata.normalize(
1240 1251 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1241 1252
1242 1253 # do NFKD encoding, and also make sure email has proper format
1243 1254 email_address = self.normalize_email(self.email_address)
1244 1255
1245 1256 # first push the email initials
1246 1257 prefix, server = email_address.split('@', 1)
1247 1258
1248 1259 # check if prefix is maybe a 'first_name.last_name' syntax
1249 1260 _dot_split = prefix.rsplit('.', 1)
1250 1261 if len(_dot_split) == 2 and _dot_split[1]:
1251 1262 initials = [_dot_split[0][0], _dot_split[1][0]]
1252 1263 else:
1253 1264 initials = [prefix[0], server[0]]
1254 1265
1255 1266 # then try to replace either first_name or last_name
1256 1267 fn_letter = (first_name or " ")[0].strip()
1257 1268 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1258 1269
1259 1270 if fn_letter:
1260 1271 initials[0] = fn_letter
1261 1272
1262 1273 if ln_letter:
1263 1274 initials[1] = ln_letter
1264 1275
1265 1276 return ''.join(initials).upper()
1266 1277
1267 1278 def get_img_data_by_type(self, font_family, img_type):
1268 1279 default_user = """
1269 1280 <svg xmlns="http://www.w3.org/2000/svg"
1270 1281 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1271 1282 viewBox="-15 -10 439.165 429.164"
1272 1283
1273 1284 xml:space="preserve"
1274 1285 style="background:{background};" >
1275 1286
1276 1287 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1277 1288 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1278 1289 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1279 1290 168.596,153.916,216.671,
1280 1291 204.583,216.671z" fill="{text_color}"/>
1281 1292 <path d="M407.164,374.717L360.88,
1282 1293 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1283 1294 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1284 1295 15.366-44.203,23.488-69.076,23.488c-24.877,
1285 1296 0-48.762-8.122-69.078-23.488
1286 1297 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1287 1298 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1288 1299 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1289 1300 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1290 1301 19.402-10.527 C409.699,390.129,
1291 1302 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1292 1303 </svg>""".format(
1293 1304 size=self.size,
1294 1305 background='#979797', # @grey4
1295 1306 text_color=self.text_color,
1296 1307 font_family=font_family)
1297 1308
1298 1309 return {
1299 1310 "default_user": default_user
1300 1311 }[img_type]
1301 1312
1302 1313 def get_img_data(self, svg_type=None):
1303 1314 """
1304 1315 generates the svg metadata for image
1305 1316 """
1306 1317 fonts = [
1307 1318 '-apple-system',
1308 1319 'BlinkMacSystemFont',
1309 1320 'Segoe UI',
1310 1321 'Roboto',
1311 1322 'Oxygen-Sans',
1312 1323 'Ubuntu',
1313 1324 'Cantarell',
1314 1325 'Helvetica Neue',
1315 1326 'sans-serif'
1316 1327 ]
1317 1328 font_family = ','.join(fonts)
1318 1329 if svg_type:
1319 1330 return self.get_img_data_by_type(font_family, svg_type)
1320 1331
1321 1332 initials = self.get_initials()
1322 1333 img_data = """
1323 1334 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1324 1335 width="{size}" height="{size}"
1325 1336 style="width: 100%; height: 100%; background-color: {background}"
1326 1337 viewBox="0 0 {size} {size}">
1327 1338 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1328 1339 pointer-events="auto" fill="{text_color}"
1329 1340 font-family="{font_family}"
1330 1341 style="font-weight: 400; font-size: {f_size}px;">{text}
1331 1342 </text>
1332 1343 </svg>""".format(
1333 1344 size=self.size,
1334 1345 f_size=self.size/2.05, # scale the text inside the box nicely
1335 1346 background=self.background,
1336 1347 text_color=self.text_color,
1337 1348 text=initials.upper(),
1338 1349 font_family=font_family)
1339 1350
1340 1351 return img_data
1341 1352
1342 1353 def generate_svg(self, svg_type=None):
1343 1354 img_data = self.get_img_data(svg_type)
1344 1355 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1345 1356
1346 1357
1347 1358 def initials_gravatar(email_address, first_name, last_name, size=30):
1348 1359 svg_type = None
1349 1360 if email_address == User.DEFAULT_USER_EMAIL:
1350 1361 svg_type = 'default_user'
1351 1362 klass = InitialsGravatar(email_address, first_name, last_name, size)
1352 1363 return klass.generate_svg(svg_type=svg_type)
1353 1364
1354 1365
1355 1366 def gravatar_url(email_address, size=30, request=None):
1356 1367 request = get_current_request()
1357 1368 _use_gravatar = request.call_context.visual.use_gravatar
1358 1369 _gravatar_url = request.call_context.visual.gravatar_url
1359 1370
1360 1371 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1361 1372
1362 1373 email_address = email_address or User.DEFAULT_USER_EMAIL
1363 1374 if isinstance(email_address, unicode):
1364 1375 # hashlib crashes on unicode items
1365 1376 email_address = safe_str(email_address)
1366 1377
1367 1378 # empty email or default user
1368 1379 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1369 1380 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1370 1381
1371 1382 if _use_gravatar:
1372 1383 # TODO: Disuse pyramid thread locals. Think about another solution to
1373 1384 # get the host and schema here.
1374 1385 request = get_current_request()
1375 1386 tmpl = safe_str(_gravatar_url)
1376 1387 tmpl = tmpl.replace('{email}', email_address)\
1377 1388 .replace('{md5email}', md5_safe(email_address.lower())) \
1378 1389 .replace('{netloc}', request.host)\
1379 1390 .replace('{scheme}', request.scheme)\
1380 1391 .replace('{size}', safe_str(size))
1381 1392 return tmpl
1382 1393 else:
1383 1394 return initials_gravatar(email_address, '', '', size=size)
1384 1395
1385 1396
1386 1397 def breadcrumb_repo_link(repo):
1387 1398 """
1388 1399 Makes a breadcrumbs path link to repo
1389 1400
1390 1401 ex::
1391 1402 group >> subgroup >> repo
1392 1403
1393 1404 :param repo: a Repository instance
1394 1405 """
1395 1406
1396 1407 path = [
1397 1408 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1398 1409 title='last change:{}'.format(format_date(group.last_commit_change)))
1399 1410 for group in repo.groups_with_parents
1400 1411 ] + [
1401 1412 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1402 1413 title='last change:{}'.format(format_date(repo.last_commit_change)))
1403 1414 ]
1404 1415
1405 1416 return literal(' &raquo; '.join(path))
1406 1417
1407 1418
1408 1419 def breadcrumb_repo_group_link(repo_group):
1409 1420 """
1410 1421 Makes a breadcrumbs path link to repo
1411 1422
1412 1423 ex::
1413 1424 group >> subgroup
1414 1425
1415 1426 :param repo_group: a Repository Group instance
1416 1427 """
1417 1428
1418 1429 path = [
1419 1430 link_to(group.name,
1420 1431 route_path('repo_group_home', repo_group_name=group.group_name),
1421 1432 title='last change:{}'.format(format_date(group.last_commit_change)))
1422 1433 for group in repo_group.parents
1423 1434 ] + [
1424 1435 link_to(repo_group.name,
1425 1436 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1426 1437 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1427 1438 ]
1428 1439
1429 1440 return literal(' &raquo; '.join(path))
1430 1441
1431 1442
1432 1443 def format_byte_size_binary(file_size):
1433 1444 """
1434 1445 Formats file/folder sizes to standard.
1435 1446 """
1436 1447 if file_size is None:
1437 1448 file_size = 0
1438 1449
1439 1450 formatted_size = format_byte_size(file_size, binary=True)
1440 1451 return formatted_size
1441 1452
1442 1453
1443 1454 def urlify_text(text_, safe=True, **href_attrs):
1444 1455 """
1445 1456 Extract urls from text and make html links out of them
1446 1457 """
1447 1458
1448 1459 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1449 1460 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1450 1461
1451 1462 def url_func(match_obj):
1452 1463 url_full = match_obj.groups()[0]
1453 1464 a_options = dict(href_attrs)
1454 1465 a_options['href'] = url_full
1455 1466 a_text = url_full
1456 1467 return HTML.tag("a", a_text, **a_options)
1457 1468
1458 1469 _new_text = url_pat.sub(url_func, text_)
1459 1470
1460 1471 if safe:
1461 1472 return literal(_new_text)
1462 1473 return _new_text
1463 1474
1464 1475
1465 1476 def urlify_commits(text_, repo_name):
1466 1477 """
1467 1478 Extract commit ids from text and make link from them
1468 1479
1469 1480 :param text_:
1470 1481 :param repo_name: repo name to build the URL with
1471 1482 """
1472 1483
1473 1484 url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1474 1485
1475 1486 def url_func(match_obj):
1476 1487 commit_id = match_obj.groups()[1]
1477 1488 pref = match_obj.groups()[0]
1478 1489 suf = match_obj.groups()[2]
1479 1490
1480 1491 tmpl = (
1481 1492 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-alt="%(hovercard_alt)s" data-hovercard-url="%(hovercard_url)s">'
1482 1493 '%(commit_id)s</a>%(suf)s'
1483 1494 )
1484 1495 return tmpl % {
1485 1496 'pref': pref,
1486 1497 'cls': 'revision-link',
1487 1498 'url': route_url(
1488 1499 'repo_commit', repo_name=repo_name, commit_id=commit_id),
1489 1500 'commit_id': commit_id,
1490 1501 'suf': suf,
1491 1502 'hovercard_alt': 'Commit: {}'.format(commit_id),
1492 1503 'hovercard_url': route_url(
1493 1504 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)
1494 1505 }
1495 1506
1496 1507 new_text = url_pat.sub(url_func, text_)
1497 1508
1498 1509 return new_text
1499 1510
1500 1511
1501 1512 def _process_url_func(match_obj, repo_name, uid, entry,
1502 1513 return_raw_data=False, link_format='html'):
1503 1514 pref = ''
1504 1515 if match_obj.group().startswith(' '):
1505 1516 pref = ' '
1506 1517
1507 1518 issue_id = ''.join(match_obj.groups())
1508 1519
1509 1520 if link_format == 'html':
1510 1521 tmpl = (
1511 1522 '%(pref)s<a class="tooltip %(cls)s" href="%(url)s" title="%(title)s">'
1512 1523 '%(issue-prefix)s%(id-repr)s'
1513 1524 '</a>')
1514 1525 elif link_format == 'html+hovercard':
1515 1526 tmpl = (
1516 1527 '%(pref)s<a class="tooltip-hovercard %(cls)s" href="%(url)s" data-hovercard-url="%(hovercard_url)s">'
1517 1528 '%(issue-prefix)s%(id-repr)s'
1518 1529 '</a>')
1519 1530 elif link_format in ['rst', 'rst+hovercard']:
1520 1531 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1521 1532 elif link_format in ['markdown', 'markdown+hovercard']:
1522 1533 tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)'
1523 1534 else:
1524 1535 raise ValueError('Bad link_format:{}'.format(link_format))
1525 1536
1526 1537 (repo_name_cleaned,
1527 1538 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1528 1539
1529 1540 # variables replacement
1530 1541 named_vars = {
1531 1542 'id': issue_id,
1532 1543 'repo': repo_name,
1533 1544 'repo_name': repo_name_cleaned,
1534 1545 'group_name': parent_group_name,
1535 1546 # set dummy keys so we always have them
1536 1547 'hostname': '',
1537 1548 'netloc': '',
1538 1549 'scheme': ''
1539 1550 }
1540 1551
1541 1552 request = get_current_request()
1542 1553 if request:
1543 1554 # exposes, hostname, netloc, scheme
1544 1555 host_data = get_host_info(request)
1545 1556 named_vars.update(host_data)
1546 1557
1547 1558 # named regex variables
1548 1559 named_vars.update(match_obj.groupdict())
1549 1560 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1550 1561 desc = string.Template(entry['desc']).safe_substitute(**named_vars)
1551 1562 hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars)
1552 1563
1553 1564 def quote_cleaner(input_str):
1554 1565 """Remove quotes as it's HTML"""
1555 1566 return input_str.replace('"', '')
1556 1567
1557 1568 data = {
1558 1569 'pref': pref,
1559 1570 'cls': quote_cleaner('issue-tracker-link'),
1560 1571 'url': quote_cleaner(_url),
1561 1572 'id-repr': issue_id,
1562 1573 'issue-prefix': entry['pref'],
1563 1574 'serv': entry['url'],
1564 1575 'title': bleach.clean(desc, strip=True),
1565 1576 'hovercard_url': hovercard_url
1566 1577 }
1567 1578
1568 1579 if return_raw_data:
1569 1580 return {
1570 1581 'id': issue_id,
1571 1582 'url': _url
1572 1583 }
1573 1584 return tmpl % data
1574 1585
1575 1586
1576 1587 def get_active_pattern_entries(repo_name):
1577 1588 repo = None
1578 1589 if repo_name:
1579 1590 # Retrieving repo_name to avoid invalid repo_name to explode on
1580 1591 # IssueTrackerSettingsModel but still passing invalid name further down
1581 1592 repo = Repository.get_by_repo_name(repo_name, cache=True)
1582 1593
1583 1594 settings_model = IssueTrackerSettingsModel(repo=repo)
1584 1595 active_entries = settings_model.get_settings(cache=True)
1585 1596 return active_entries
1586 1597
1587 1598
1588 1599 pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)')
1589 1600
1590 1601
1591 1602 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1592 1603
1593 1604 allowed_formats = ['html', 'rst', 'markdown',
1594 1605 'html+hovercard', 'rst+hovercard', 'markdown+hovercard']
1595 1606 if link_format not in allowed_formats:
1596 1607 raise ValueError('Link format can be only one of:{} got {}'.format(
1597 1608 allowed_formats, link_format))
1598 1609
1599 1610 if active_entries is None:
1600 1611 log.debug('Fetch active patterns for repo: %s', repo_name)
1601 1612 active_entries = get_active_pattern_entries(repo_name)
1602 1613
1603 1614 issues_data = []
1604 1615 new_text = text_string
1605 1616
1606 1617 log.debug('Got %s entries to process', len(active_entries))
1607 1618 for uid, entry in active_entries.items():
1608 1619 log.debug('found issue tracker entry with uid %s', uid)
1609 1620
1610 1621 if not (entry['pat'] and entry['url']):
1611 1622 log.debug('skipping due to missing data')
1612 1623 continue
1613 1624
1614 1625 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1615 1626 uid, entry['pat'], entry['url'], entry['pref'])
1616 1627
1617 1628 if entry.get('pat_compiled'):
1618 1629 pattern = entry['pat_compiled']
1619 1630 else:
1620 1631 try:
1621 1632 pattern = re.compile(r'%s' % entry['pat'])
1622 1633 except re.error:
1623 1634 log.exception('issue tracker pattern: `%s` failed to compile', entry['pat'])
1624 1635 continue
1625 1636
1626 1637 data_func = partial(
1627 1638 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1628 1639 return_raw_data=True)
1629 1640
1630 1641 for match_obj in pattern.finditer(text_string):
1631 1642 issues_data.append(data_func(match_obj))
1632 1643
1633 1644 url_func = partial(
1634 1645 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1635 1646 link_format=link_format)
1636 1647
1637 1648 new_text = pattern.sub(url_func, new_text)
1638 1649 log.debug('processed prefix:uid `%s`', uid)
1639 1650
1640 1651 # finally use global replace, eg !123 -> pr-link, those will not catch
1641 1652 # if already similar pattern exists
1642 1653 server_url = '${scheme}://${netloc}'
1643 1654 pr_entry = {
1644 1655 'pref': '!',
1645 1656 'url': server_url + '/_admin/pull-requests/${id}',
1646 1657 'desc': 'Pull Request !${id}',
1647 1658 'hovercard_url': server_url + '/_hovercard/pull_request/${id}'
1648 1659 }
1649 1660 pr_url_func = partial(
1650 1661 _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None,
1651 1662 link_format=link_format+'+hovercard')
1652 1663 new_text = pr_pattern_re.sub(pr_url_func, new_text)
1653 1664 log.debug('processed !pr pattern')
1654 1665
1655 1666 return new_text, issues_data
1656 1667
1657 1668
1658 1669 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1659 1670 """
1660 1671 Parses given text message and makes proper links.
1661 1672 issues are linked to given issue-server, and rest is a commit link
1662 1673 """
1663 1674
1664 1675 def escaper(_text):
1665 1676 return _text.replace('<', '&lt;').replace('>', '&gt;')
1666 1677
1667 1678 new_text = escaper(commit_text)
1668 1679
1669 1680 # extract http/https links and make them real urls
1670 1681 new_text = urlify_text(new_text, safe=False)
1671 1682
1672 1683 # urlify commits - extract commit ids and make link out of them, if we have
1673 1684 # the scope of repository present.
1674 1685 if repository:
1675 1686 new_text = urlify_commits(new_text, repository)
1676 1687
1677 1688 # process issue tracker patterns
1678 1689 new_text, issues = process_patterns(new_text, repository or '',
1679 1690 active_entries=active_pattern_entries)
1680 1691
1681 1692 return literal(new_text)
1682 1693
1683 1694
1684 1695 def render_binary(repo_name, file_obj):
1685 1696 """
1686 1697 Choose how to render a binary file
1687 1698 """
1688 1699
1689 1700 # unicode
1690 1701 filename = file_obj.name
1691 1702
1692 1703 # images
1693 1704 for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']:
1694 1705 if fnmatch.fnmatch(filename, pat=ext):
1695 1706 src = route_path(
1696 1707 'repo_file_raw', repo_name=repo_name,
1697 1708 commit_id=file_obj.commit.raw_id,
1698 1709 f_path=file_obj.path)
1699 1710
1700 1711 return literal(
1701 1712 '<img class="rendered-binary" alt="rendered-image" src="{}">'.format(src))
1702 1713
1703 1714
1704 1715 def renderer_from_filename(filename, exclude=None):
1705 1716 """
1706 1717 choose a renderer based on filename, this works only for text based files
1707 1718 """
1708 1719
1709 1720 # ipython
1710 1721 for ext in ['*.ipynb']:
1711 1722 if fnmatch.fnmatch(filename, pat=ext):
1712 1723 return 'jupyter'
1713 1724
1714 1725 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1715 1726 if is_markup:
1716 1727 return is_markup
1717 1728 return None
1718 1729
1719 1730
1720 1731 def render(source, renderer='rst', mentions=False, relative_urls=None,
1721 1732 repo_name=None, active_pattern_entries=None):
1722 1733
1723 1734 def maybe_convert_relative_links(html_source):
1724 1735 if relative_urls:
1725 1736 return relative_links(html_source, relative_urls)
1726 1737 return html_source
1727 1738
1728 1739 if renderer == 'plain':
1729 1740 return literal(
1730 1741 MarkupRenderer.plain(source, leading_newline=False))
1731 1742
1732 1743 elif renderer == 'rst':
1733 1744 if repo_name:
1734 1745 # process patterns on comments if we pass in repo name
1735 1746 source, issues = process_patterns(
1736 1747 source, repo_name, link_format='rst',
1737 1748 active_entries=active_pattern_entries)
1738 1749
1739 1750 return literal(
1740 1751 '<div class="rst-block">%s</div>' %
1741 1752 maybe_convert_relative_links(
1742 1753 MarkupRenderer.rst(source, mentions=mentions)))
1743 1754
1744 1755 elif renderer == 'markdown':
1745 1756 if repo_name:
1746 1757 # process patterns on comments if we pass in repo name
1747 1758 source, issues = process_patterns(
1748 1759 source, repo_name, link_format='markdown',
1749 1760 active_entries=active_pattern_entries)
1750 1761
1751 1762 return literal(
1752 1763 '<div class="markdown-block">%s</div>' %
1753 1764 maybe_convert_relative_links(
1754 1765 MarkupRenderer.markdown(source, flavored=True,
1755 1766 mentions=mentions)))
1756 1767
1757 1768 elif renderer == 'jupyter':
1758 1769 return literal(
1759 1770 '<div class="ipynb">%s</div>' %
1760 1771 maybe_convert_relative_links(
1761 1772 MarkupRenderer.jupyter(source)))
1762 1773
1763 1774 # None means just show the file-source
1764 1775 return None
1765 1776
1766 1777
1767 1778 def commit_status(repo, commit_id):
1768 1779 return ChangesetStatusModel().get_status(repo, commit_id)
1769 1780
1770 1781
1771 1782 def commit_status_lbl(commit_status):
1772 1783 return dict(ChangesetStatus.STATUSES).get(commit_status)
1773 1784
1774 1785
1775 1786 def commit_time(repo_name, commit_id):
1776 1787 repo = Repository.get_by_repo_name(repo_name)
1777 1788 commit = repo.get_commit(commit_id=commit_id)
1778 1789 return commit.date
1779 1790
1780 1791
1781 1792 def get_permission_name(key):
1782 1793 return dict(Permission.PERMS).get(key)
1783 1794
1784 1795
1785 1796 def journal_filter_help(request):
1786 1797 _ = request.translate
1787 1798 from rhodecode.lib.audit_logger import ACTIONS
1788 1799 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1789 1800
1790 1801 return _(
1791 1802 'Example filter terms:\n' +
1792 1803 ' repository:vcs\n' +
1793 1804 ' username:marcin\n' +
1794 1805 ' username:(NOT marcin)\n' +
1795 1806 ' action:*push*\n' +
1796 1807 ' ip:127.0.0.1\n' +
1797 1808 ' date:20120101\n' +
1798 1809 ' date:[20120101100000 TO 20120102]\n' +
1799 1810 '\n' +
1800 1811 'Actions: {actions}\n' +
1801 1812 '\n' +
1802 1813 'Generate wildcards using \'*\' character:\n' +
1803 1814 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1804 1815 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1805 1816 '\n' +
1806 1817 'Optional AND / OR operators in queries\n' +
1807 1818 ' "repository:vcs OR repository:test"\n' +
1808 1819 ' "username:test AND repository:test*"\n'
1809 1820 ).format(actions=actions)
1810 1821
1811 1822
1812 1823 def not_mapped_error(repo_name):
1813 1824 from rhodecode.translation import _
1814 1825 flash(_('%s repository is not mapped to db perhaps'
1815 1826 ' it was created or renamed from the filesystem'
1816 1827 ' please run the application again'
1817 1828 ' in order to rescan repositories') % repo_name, category='error')
1818 1829
1819 1830
1820 1831 def ip_range(ip_addr):
1821 1832 from rhodecode.model.db import UserIpMap
1822 1833 s, e = UserIpMap._get_ip_range(ip_addr)
1823 1834 return '%s - %s' % (s, e)
1824 1835
1825 1836
1826 1837 def form(url, method='post', needs_csrf_token=True, **attrs):
1827 1838 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1828 1839 if method.lower() != 'get' and needs_csrf_token:
1829 1840 raise Exception(
1830 1841 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1831 1842 'CSRF token. If the endpoint does not require such token you can ' +
1832 1843 'explicitly set the parameter needs_csrf_token to false.')
1833 1844
1834 1845 return insecure_form(url, method=method, **attrs)
1835 1846
1836 1847
1837 1848 def secure_form(form_url, method="POST", multipart=False, **attrs):
1838 1849 """Start a form tag that points the action to an url. This
1839 1850 form tag will also include the hidden field containing
1840 1851 the auth token.
1841 1852
1842 1853 The url options should be given either as a string, or as a
1843 1854 ``url()`` function. The method for the form defaults to POST.
1844 1855
1845 1856 Options:
1846 1857
1847 1858 ``multipart``
1848 1859 If set to True, the enctype is set to "multipart/form-data".
1849 1860 ``method``
1850 1861 The method to use when submitting the form, usually either
1851 1862 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1852 1863 hidden input with name _method is added to simulate the verb
1853 1864 over POST.
1854 1865
1855 1866 """
1856 1867
1857 1868 if 'request' in attrs:
1858 1869 session = attrs['request'].session
1859 1870 del attrs['request']
1860 1871 else:
1861 1872 raise ValueError(
1862 1873 'Calling this form requires request= to be passed as argument')
1863 1874
1864 1875 _form = insecure_form(form_url, method, multipart, **attrs)
1865 1876 token = literal(
1866 1877 '<input type="hidden" name="{}" value="{}">'.format(
1867 1878 csrf_token_key, get_csrf_token(session)))
1868 1879
1869 1880 return literal("%s\n%s" % (_form, token))
1870 1881
1871 1882
1872 1883 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1873 1884 select_html = select(name, selected, options, **attrs)
1874 1885
1875 1886 select2 = """
1876 1887 <script>
1877 1888 $(document).ready(function() {
1878 1889 $('#%s').select2({
1879 1890 containerCssClass: 'drop-menu %s',
1880 1891 dropdownCssClass: 'drop-menu-dropdown',
1881 1892 dropdownAutoWidth: true%s
1882 1893 });
1883 1894 });
1884 1895 </script>
1885 1896 """
1886 1897
1887 1898 filter_option = """,
1888 1899 minimumResultsForSearch: -1
1889 1900 """
1890 1901 input_id = attrs.get('id') or name
1891 1902 extra_classes = ' '.join(attrs.pop('extra_classes', []))
1892 1903 filter_enabled = "" if enable_filter else filter_option
1893 1904 select_script = literal(select2 % (input_id, extra_classes, filter_enabled))
1894 1905
1895 1906 return literal(select_html+select_script)
1896 1907
1897 1908
1898 1909 def get_visual_attr(tmpl_context_var, attr_name):
1899 1910 """
1900 1911 A safe way to get a variable from visual variable of template context
1901 1912
1902 1913 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1903 1914 :param attr_name: name of the attribute we fetch from the c.visual
1904 1915 """
1905 1916 visual = getattr(tmpl_context_var, 'visual', None)
1906 1917 if not visual:
1907 1918 return
1908 1919 else:
1909 1920 return getattr(visual, attr_name, None)
1910 1921
1911 1922
1912 1923 def get_last_path_part(file_node):
1913 1924 if not file_node.path:
1914 1925 return u'/'
1915 1926
1916 1927 path = safe_unicode(file_node.path.split('/')[-1])
1917 1928 return u'../' + path
1918 1929
1919 1930
1920 1931 def route_url(*args, **kwargs):
1921 1932 """
1922 1933 Wrapper around pyramids `route_url` (fully qualified url) function.
1923 1934 """
1924 1935 req = get_current_request()
1925 1936 return req.route_url(*args, **kwargs)
1926 1937
1927 1938
1928 1939 def route_path(*args, **kwargs):
1929 1940 """
1930 1941 Wrapper around pyramids `route_path` function.
1931 1942 """
1932 1943 req = get_current_request()
1933 1944 return req.route_path(*args, **kwargs)
1934 1945
1935 1946
1936 1947 def route_path_or_none(*args, **kwargs):
1937 1948 try:
1938 1949 return route_path(*args, **kwargs)
1939 1950 except KeyError:
1940 1951 return None
1941 1952
1942 1953
1943 1954 def current_route_path(request, **kw):
1944 1955 new_args = request.GET.mixed()
1945 1956 new_args.update(kw)
1946 1957 return request.current_route_path(_query=new_args)
1947 1958
1948 1959
1949 1960 def curl_api_example(method, args):
1950 1961 args_json = json.dumps(OrderedDict([
1951 1962 ('id', 1),
1952 1963 ('auth_token', 'SECRET'),
1953 1964 ('method', method),
1954 1965 ('args', args)
1955 1966 ]))
1956 1967
1957 1968 return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format(
1958 1969 api_url=route_url('apiv2'),
1959 1970 args_json=args_json
1960 1971 )
1961 1972
1962 1973
1963 1974 def api_call_example(method, args):
1964 1975 """
1965 1976 Generates an API call example via CURL
1966 1977 """
1967 1978 curl_call = curl_api_example(method, args)
1968 1979
1969 1980 return literal(
1970 1981 curl_call +
1971 1982 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1972 1983 "and needs to be of `api calls` role."
1973 1984 .format(token_url=route_url('my_account_auth_tokens')))
1974 1985
1975 1986
1976 1987 def notification_description(notification, request):
1977 1988 """
1978 1989 Generate notification human readable description based on notification type
1979 1990 """
1980 1991 from rhodecode.model.notification import NotificationModel
1981 1992 return NotificationModel().make_description(
1982 1993 notification, translate=request.translate)
1983 1994
1984 1995
1985 1996 def go_import_header(request, db_repo=None):
1986 1997 """
1987 1998 Creates a header for go-import functionality in Go Lang
1988 1999 """
1989 2000
1990 2001 if not db_repo:
1991 2002 return
1992 2003 if 'go-get' not in request.GET:
1993 2004 return
1994 2005
1995 2006 clone_url = db_repo.clone_url()
1996 2007 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
1997 2008 # we have a repo and go-get flag,
1998 2009 return literal('<meta name="go-import" content="{} {} {}">'.format(
1999 2010 prefix, db_repo.repo_type, clone_url))
2000 2011
2001 2012
2002 2013 def reviewer_as_json(*args, **kwargs):
2003 2014 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2004 2015 return _reviewer_as_json(*args, **kwargs)
2005 2016
2006 2017
2007 2018 def get_repo_view_type(request):
2008 2019 route_name = request.matched_route.name
2009 2020 route_to_view_type = {
2010 2021 'repo_changelog': 'commits',
2011 2022 'repo_commits': 'commits',
2012 2023 'repo_files': 'files',
2013 2024 'repo_summary': 'summary',
2014 2025 'repo_commit': 'commit'
2015 2026 }
2016 2027
2017 2028 return route_to_view_type.get(route_name)
2018 2029
2019 2030
2020 2031 def is_active(menu_entry, selected):
2021 2032 """
2022 2033 Returns active class for selecting menus in templates
2023 2034 <li class=${h.is_active('settings', current_active)}></li>
2024 2035 """
2025 2036 if not isinstance(menu_entry, list):
2026 2037 menu_entry = [menu_entry]
2027 2038
2028 2039 if selected in menu_entry:
2029 2040 return "active"
General Comments 0
You need to be logged in to leave comments. Login now