diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py index 4bd4876c..1283a71c 100644 --- a/rhodecode/lib/helpers.py +++ b/rhodecode/lib/helpers.py @@ -22,6 +22,7 @@ Helper functions Consists of functions to typically be used within templates, but also available to Controllers. This module is available to both as 'h'. """ + import base64 import collections @@ -48,31 +49,58 @@ import fnmatch from datetime import datetime from functools import partial from pygments.formatters.html import HtmlFormatter -from pygments.lexers import ( - get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype) +from pygments.lexers import get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype from tempita import looper from webhelpers2.html import literal, HTML, escape from webhelpers2.html._autolink import _auto_link_urls -from webhelpers2.html.tools import ( - button_to, highlight, js_obfuscate, strip_links, strip_tags) +from webhelpers2.html.tools import button_to, highlight, js_obfuscate, strip_links, strip_tags from webhelpers2.text import ( - chop_at, collapse, convert_accented_entities, - convert_misc_entities, lchop, plural, rchop, remove_formatting, - replace_whitespace, urlify, truncate, wrap_paragraphs) + chop_at, + collapse, + convert_accented_entities, + convert_misc_entities, + lchop, + plural, + rchop, + remove_formatting, + replace_whitespace, + urlify, + truncate, + wrap_paragraphs, +) from webhelpers2.date import time_ago_in_words from webhelpers2.html.tags import ( - _input, NotGiven, _make_safe_id_component as safeid, + _input, + NotGiven, + _make_safe_id_component as safeid, form as insecure_form, - auto_discovery_link, checkbox, end_form, file, - hidden, image, javascript_link, link_to, link_to_if, link_to_unless, ol, - stylesheet_link, submit, text, password, textarea, - ul, radio, Options) + auto_discovery_link, + checkbox, + end_form, + file, + hidden, + image, + javascript_link, + link_to, + link_to_if, + link_to_unless, + ol, + stylesheet_link, + submit, + text, + password, + textarea, + ul, + radio, + Options, +) from webhelpers2.number import format_byte_size + # python3.11 backport fixes for webhelpers2 from rhodecode import ConfigGet from rhodecode.lib._vendor.webhelpers_backports import raw_select @@ -82,15 +110,22 @@ from rhodecode.lib.action_parser import action_parser from rhodecode.lib.html_filters import sanitize_html from rhodecode.lib.pagination import Page, RepoPage, SqlPage from rhodecode.lib import ext_json -from rhodecode.lib.ext_json import json, formatted_str_json +from rhodecode.lib.ext_json import json from rhodecode.lib.str_utils import safe_bytes, convert_special_chars, base64_to_str from rhodecode.lib.utils import repo_name_slug, get_custom_lexer from rhodecode.lib.str_utils import safe_str from rhodecode.lib.utils2 import ( str2bool, - get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, - AttributeDict, safe_int, md5, md5_safe, get_host_info) -from rhodecode.lib.pyramid_utils import get_current_request + get_commit_safe, + datetime_to_time, + time_to_datetime, + time_to_utcdatetime, + AttributeDict, + safe_int, + md5, + md5_safe, + get_host_info, +) from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit @@ -123,23 +158,22 @@ def asset(path, ver=None, **kwargs): query = {} query.update(kwargs) if ver: - query = {'ver': ver} - return request.static_path( - f'rhodecode:public/{path}', _query=query) + query = {"ver": ver} + return request.static_path(f"rhodecode:public/{path}", _query=query) default_html_escape_table = { - ord('&'): '&', - ord('<'): '<', - ord('>'): '>', - ord('"'): '"', - ord("'"): ''', + ord("&"): "&", + ord("<"): "<", + ord(">"): ">", + ord('"'): """, + ord("'"): "'", } -def html_escape(text, html_escape_table=default_html_escape_table): +def html_escape(input_text, html_escape_table=default_html_escape_table): """Produce entities within text.""" - return text.translate(html_escape_table) + return input_text.translate(html_escape_table) def str_json(*args, **kwargs): @@ -156,7 +190,7 @@ def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None): If ``inclusive`` is true, truncate just after ``sub`` rather than at it. """ - suffix_if_chopped = suffix_if_chopped or '' + suffix_if_chopped = suffix_if_chopped or "" pos = s.find(sub) if pos == -1: return s @@ -173,15 +207,15 @@ def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None): return chopped -def shorter(text, size=20, prefix=False): - postfix = '...' - if len(text) > size: +def shorter(input_text, size=20, prefix=False): + postfix = "..." + if len(input_text) > size: if prefix: # shorten in front - return postfix + text[-(size - len(postfix)):] + return postfix + input_text[-(size - len(postfix)) :] else: - return text[:size - len(postfix)] + postfix - return text + return input_text[: size - len(postfix)] + postfix + return input_text def reset(name, value=None, id=NotGiven, type="reset", **attrs): @@ -192,7 +226,6 @@ def reset(name, value=None, id=NotGiven, type="reset", **attrs): def select(name, selected_values, options, id=NotGiven, **attrs): - if isinstance(options, (list, tuple)): options_iter = options # Handle old value,label lists ... where value also can be value,label lists @@ -203,7 +236,7 @@ def select(name, selected_values, options, id=NotGiven, **attrs): elif isinstance(opt, str): value = label = opt else: - raise ValueError('invalid select option type %r' % type(opt)) + raise ValueError("invalid select option type %r" % type(opt)) if isinstance(value, (list, tuple)): option_group = options.add_optgroup(label) @@ -213,7 +246,7 @@ def select(name, selected_values, options, id=NotGiven, **attrs): elif isinstance(opt2, str): group_value = group_label = opt2 else: - raise ValueError('invalid select option type %r' % type(opt2)) + raise ValueError("invalid select option type %r" % type(opt2)) option_group.add_option(group_label, group_value) else: @@ -235,7 +268,7 @@ def FID(raw_id, path): :param path: """ - return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12]) + return "c-%s-%s" % (short_id(raw_id), md5_safe(path)[:12]) class _GetError(object): @@ -256,7 +289,6 @@ get_error = _GetError() class _ToolTip(object): - def __call__(self, tooltip_title, trim_at=50): """ Special function just to wrap our text into nice formatted @@ -265,7 +297,7 @@ class _ToolTip(object): :param tooltip_title: """ tooltip_title = escape(tooltip_title) - tooltip_title = tooltip_title.replace('<', '<').replace('>', '>') + tooltip_title = tooltip_title.replace("<", "<").replace(">", ">") return tooltip_title @@ -274,12 +306,20 @@ tooltip = _ToolTip() files_icon = '' -def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_name=None, at_ref=None, - limit_items=False, linkify_last_item=False, hide_last_item=False, - copy_path_icon=True): - +def files_breadcrumbs( + repo_name, + repo_type, + commit_id, + file_path, + landing_ref_name=None, + at_ref=None, + limit_items=False, + linkify_last_item=False, + hide_last_item=False, + copy_path_icon=True, +): if at_ref: - route_qry = {'at': at_ref} + route_qry = {"at": at_ref} default_landing_ref = at_ref or landing_ref_name or commit_id else: route_qry = None @@ -295,14 +335,15 @@ def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_na repo_name, repo_type, f_path=None, # None here is a special case for SVN repos, - # that won't prefix with a ref + # that won't prefix with a ref ref_name=default_landing_ref, commit_id=commit_id, - query=route_qry - ) - )] + query=route_qry, + ), + ) + ] - path_segments = file_path.split('/') + path_segments = file_path.split("/") last_cnt = len(path_segments) - 1 for cnt, segment in enumerate(path_segments): if not segment: @@ -325,14 +366,15 @@ def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_na repo_files_by_ref_url( repo_name, repo_type, - f_path='/'.join(path_segments[:cnt + 1]), + f_path="/".join(path_segments[: cnt + 1]), ref_name=default_landing_ref, commit_id=commit_id, - query=route_qry + query=route_qry, ), - )) + ) + ) - limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:] + limited_url_segments = url_segments[:1] + ["..."] + url_segments[-5:] if limit_items and len(limited_url_segments) < len(url_segments): url_segments = limited_url_segments @@ -340,28 +382,35 @@ def files_breadcrumbs(repo_name, repo_type, commit_id, file_path, landing_ref_na if copy_path_icon: icon = files_icon.format(escape(full_path)) else: - icon = '' + icon = "" - if file_path == '': + if file_path == "": return root_name else: - return literal(' / '.join(url_segments) + icon) + return literal(" / ".join(url_segments) + icon) def files_url_data(request): matchdict = request.matchdict - if 'f_path' not in matchdict: - matchdict['f_path'] = '' + if "f_path" not in matchdict: + matchdict["f_path"] = "" else: - matchdict['f_path'] = urllib.parse.quote(safe_str(matchdict['f_path'])) - if 'commit_id' not in matchdict: - matchdict['commit_id'] = 'tip' + matchdict["f_path"] = urllib.parse.quote(safe_str(matchdict["f_path"])) + if "commit_id" not in matchdict: + matchdict["commit_id"] = "tip" return ext_json.str_json(matchdict) -def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_id, query=None, ): +def repo_files_by_ref_url( + db_repo_name, + db_repo_type, + f_path, + ref_name, + commit_id, + query=None, +): _is_svn = is_svn(db_repo_type) final_f_path = f_path @@ -374,13 +423,13 @@ def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_i like: /1000/tags/1.0.0/?at=tags/1.0.0 """ - if ref_name and ref_name != 'tip': + if ref_name and ref_name != "tip": # NOTE(marcink): for svn the ref_name is actually the stored path, so we prefix it # for SVN we only do this magic prefix if it's root, .eg landing revision # of files link. If we are in the tree we don't need this since we traverse the url # that has everything stored - if f_path in ['', '/']: - final_f_path = '/'.join([ref_name, f_path]) + if f_path in ["", "/"]: + final_f_path = "/".join([ref_name, f_path]) # SVN always needs a commit_id explicitly, without a named REF default_commit_id = commit_id @@ -390,21 +439,17 @@ def repo_files_by_ref_url(db_repo_name, db_repo_type, f_path, ref_name, commit_i like: /master/some_path?at=master """ # We currently do not support branches with slashes - if '/' in ref_name: + if "/" in ref_name: default_commit_id = commit_id else: default_commit_id = ref_name # sometimes we pass f_path as None, to indicate explicit no prefix, # we translate it to string to not have None - final_f_path = final_f_path or '' + final_f_path = final_f_path or "" files_url = route_path( - 'repo_files', - repo_name=db_repo_name, - commit_id=default_commit_id, - f_path=final_f_path, - _query=query + "repo_files", repo_name=db_repo_name, commit_id=default_commit_id, f_path=final_f_path, _query=query ) return files_url @@ -420,6 +465,7 @@ def code_highlight(code, lexer, formatter, use_hl_filter=False): if use_hl_filter: # add HL filter from rhodecode.lib.index import search_utils + lexer.add_filter(search_utils.ElasticSearchHLFilter()) return pygments.format(pygments.lex(code, lexer), formatter) @@ -460,18 +506,17 @@ class CodeHtmlFormatter(HtmlFormatter): if i % st == 0: if i % sp == 0: if aln: - lines.append('%*d' % - (la, i, mw, i)) + lines.append('%*d' % (la, i, mw, i)) else: lines.append('%*d' % (mw, i)) else: if aln: lines.append('%*d' % (la, i, mw, i)) else: - lines.append('%*d' % (mw, i)) + lines.append("%*d" % (mw, i)) else: - lines.append('') - ls = '\n'.join(lines) + lines.append("") + ls = "\n".join(lines) else: lines = [] for i in range(fl, fl + lncount): @@ -479,43 +524,52 @@ class CodeHtmlFormatter(HtmlFormatter): if aln: lines.append('%*d' % (la, i, mw, i)) else: - lines.append('%*d' % (mw, i)) + lines.append("%*d" % (mw, i)) else: - lines.append('') - ls = '\n'.join(lines) + lines.append("") + ls = "\n".join(lines) # in case you wonder about the seemingly redundant
here: since the # content in the other cell also is wrapped in a div, some browsers in # some configurations seem to mess up the formatting... if nocls: - yield 0, ('' % self.cssclass + - '
' - '
' +
-                      ls + '
') + yield ( + 0, + ( + '' % self.cssclass + '' + yield 0, "" yield 0, '' yield 0, '' - yield 0, '' + yield 0, "" - yield 0, '' + yield 0, "" if self.url: - yield 0, '' % ( - self.url, current_line_number, current_line_number) + yield ( + 0, + '' + % (self.url, current_line_number, current_line_number), + ) else: - yield 0, '' % ( - current_line_number) - yield 0, '' - yield 0, '' + yield 0, '' % (current_line_number) + yield 0, '" + yield 0, "" last_shown_line_number = current_line_number current_line_number += 1 - yield 0, '
' + '
' + ls + '
' + ), + ) else: - yield 0, ('' % self.cssclass + - '
' +
-                      ls + '
') + yield ( + 0, + ( + '' % self.cssclass + + '
'
+                    + ls
+                    + '
' + ), + ) yield 0, dummyoutfile.getvalue() - yield 0, '
' + yield 0, "
" class SearchContentCodeHtmlFormatter(CodeHtmlFormatter): def __init__(self, **kw): # only show these line numbers if set - self.only_lines = kw.pop('only_line_numbers', []) - self.query_terms = kw.pop('query_terms', []) - self.max_lines = kw.pop('max_lines', 5) - self.line_context = kw.pop('line_context', 3) - self.url = kw.pop('url', None) + self.only_lines = kw.pop("only_line_numbers", []) + self.query_terms = kw.pop("query_terms", []) + self.max_lines = kw.pop("max_lines", 5) + self.line_context = kw.pop("line_context", 3) + self.url = kw.pop("url", None) super(CodeHtmlFormatter, self).__init__(**kw) def _wrap_code(self, source): for cnt, it in enumerate(source): i, t = it - t = '
%s
' % t + t = "
%s
" % t yield i, t def _wrap_tablelinenos(self, inner): @@ -531,30 +585,32 @@ class SearchContentCodeHtmlFormatter(CodeHtmlFormatter): if current_line_number in self.only_lines: if last_shown_line_number + 1 != current_line_number: - yield 0, '
...
%i%i%i' + line + '
%i' + line + "
' + yield 0, "
" def hsv_to_rgb(h, s, v): - """ Convert hsv color values to rgb """ + """Convert hsv color values to rgb""" if s == 0.0: return v, v, v @@ -620,15 +676,14 @@ def color_hasher(n=10000, saturation=0.10, lightness=0.95): """ color_dict = {} - cgenerator = unique_color_generator( - saturation=saturation, lightness=lightness) + cgenerator = unique_color_generator(saturation=saturation, lightness=lightness) def get_color_string(thing): if thing in color_dict: col = color_dict[thing] else: col = color_dict[thing] = next(cgenerator) - return "rgb(%s)" % (', '.join(col)) + return "rgb(%s)" % (", ".join(col)) return get_color_string @@ -648,7 +703,7 @@ def get_lexer_safe(mimetype=None, filepath=None): pass if not lexer: - lexer = get_lexer_by_name('text') + lexer = get_lexer_by_name("text") return lexer @@ -665,12 +720,12 @@ def pygmentize(filenode, **kwargs): :param filenode: """ lexer = get_lexer_for_filenode(filenode) - return literal(code_highlight(filenode.content, lexer, - CodeHtmlFormatter(**kwargs))) + return literal(code_highlight(filenode.content, lexer, CodeHtmlFormatter(**kwargs))) def is_following_repo(repo_name, user_id): from rhodecode.model.scm import ScmModel + return ScmModel().is_following_repo(repo_name, user_id) @@ -705,8 +760,7 @@ class Flash(object): # Default category if none is specified. default_category = "notice" - def __init__(self, session_key="flash", categories=None, - default_category=None): + def __init__(self, session_key="flash", categories=None, default_category=None): """ Instantiate a ``Flash`` object. @@ -725,8 +779,7 @@ class Flash(object): if default_category is not None: self.default_category = default_category if self.categories and self.default_category not in self.categories: - raise ValueError( - "unrecognized default category %r" % (self.default_category,)) + raise ValueError("unrecognized default category %r" % (self.default_category,)) def pop_messages(self, session=None, request=None): """ @@ -751,8 +804,8 @@ class Flash(object): for cat in self.categories: for msg in session.pop_flash(queue=cat): sub_data = {} - if hasattr(msg, 'rsplit'): - flash_data = msg.rsplit('|DELIM|', 1) + if hasattr(msg, "rsplit"): + flash_data = msg.rsplit("|DELIM|", 1) org_message = flash_data[0] if len(flash_data) > 1: sub_data = json.loads(flash_data[1]) @@ -763,7 +816,7 @@ class Flash(object): # Map messages from the default queue to the 'notice' category. for msg in session.pop_flash(): - messages.append(_Message('notice', msg)) + messages.append(_Message("notice", msg)) session.save() return messages @@ -772,33 +825,32 @@ class Flash(object): payloads = [] messages = flash.pop_messages(session=session, request=request) or [] for message in messages: - payloads.append({ - 'message': { - 'message': '{}'.format(message.message), - 'level': message.category, - 'force': True, - 'subdata': message.sub_data + payloads.append( + { + "message": { + "message": "{}".format(message.message), + "level": message.category, + "force": True, + "subdata": message.sub_data, + } } - }) + ) return safe_str(json.dumps(payloads)) - def __call__(self, message, category=None, ignore_duplicate=True, - session=None, request=None): - + def __call__(self, message, category=None, ignore_duplicate=True, session=None, request=None): if not session: if not request: request = get_current_request() session = request.session - session.flash( - message, queue=category, allow_duplicate=not ignore_duplicate) + session.flash(message, queue=category, allow_duplicate=not ignore_duplicate) flash = Flash() -#============================================================================== +# ============================================================================== # SCM FILTERS available via h. -#============================================================================== +# ============================================================================== from rhodecode.lib.vcs.utils import author_name, author_email from rhodecode.lib.utils2 import age, age_from_seconds from rhodecode.model.db import User, ChangesetStatus @@ -817,25 +869,28 @@ def short_id(long_id): def hide_credentials(url): from rhodecode.lib.utils2 import credentials_filter + return credentials_filter(url) + import zoneinfo import tzlocal + local_timezone = tzlocal.get_localzone() def get_timezone(datetime_iso, time_is_local=False): - tzinfo = '+00:00' + tzinfo = "+00:00" # detect if we have a timezone info, otherwise, add it if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo: - force_timezone = os.environ.get('RC_TIMEZONE', '') + force_timezone = os.environ.get("RC_TIMEZONE", "") if force_timezone: force_timezone = zoneinfo.ZoneInfo(force_timezone) timezone = force_timezone or local_timezone - offset = datetime_iso.replace(tzinfo=timezone).strftime('%z') - tzinfo = '{}:{}'.format(offset[:-2], offset[-2:]) + offset = datetime_iso.replace(tzinfo=timezone).strftime("%z") + tzinfo = "{}:{}".format(offset[:-2], offset[-2:]) return tzinfo @@ -845,10 +900,13 @@ def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True): return literal( ''.format( - cls='tooltip' if tooltip else '', - tt_title=('{title}{tzinfo}'.format(title=title, tzinfo=tzinfo)) if tooltip else '', - title=title, dt=datetime_iso, tzinfo=tzinfo - )) + cls="tooltip" if tooltip else "", + tt_title=("{title}{tzinfo}".format(title=title, tzinfo=tzinfo)) if tooltip else "", + title=title, + dt=datetime_iso, + tzinfo=tzinfo, + ) + ) def _shorten_commit_id(commit_id, commit_len=None): @@ -871,9 +929,9 @@ def show_id(commit, show_idx=None, commit_len=None): raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len) if show_idx: - return 'r%s:%s' % (commit.idx, raw_id) + return "r%s:%s" % (commit.idx, raw_id) else: - return '%s' % (raw_id, ) + return "%s" % (raw_id,) def format_date(date): @@ -892,23 +950,22 @@ def format_date(date): class _RepoChecker(object): - def __init__(self, backend_alias): self._backend_alias = backend_alias def __call__(self, repository): - if hasattr(repository, 'alias'): + if hasattr(repository, "alias"): _type = repository.alias - elif hasattr(repository, 'repo_type'): + elif hasattr(repository, "repo_type"): _type = repository.repo_type else: _type = repository return _type == self._backend_alias -is_git = _RepoChecker('git') -is_hg = _RepoChecker('hg') -is_svn = _RepoChecker('svn') +is_git = _RepoChecker("git") +is_hg = _RepoChecker("hg") +is_svn = _RepoChecker("svn") def get_repo_type_by_name(repo_name): @@ -919,7 +976,7 @@ def get_repo_type_by_name(repo_name): def is_svn_without_proxy(repository): if is_svn(repository): - return not ConfigGet().get_bool('vcs.svn.proxy.enabled') + return not ConfigGet().get_bool("vcs.svn.proxy.enabled") return False @@ -935,7 +992,7 @@ def discover_user(author): # Valid email in the attribute passed, see if they're in the system _email = author_email(author) - if _email != '': + if _email != "": user = User.get_by_email(_email, case_insensitive=True, cache=True) if user is not None: return user @@ -955,14 +1012,13 @@ def email_or_none(author): # If we have an email, use it, otherwise # see if it contains a username we can get an email from - if _email != '': + if _email != "": return _email else: - user = User.get_by_username( - author_name(author), case_insensitive=True, cache=True) + user = User.get_by_username(author_name(author), case_insensitive=True, cache=True) if user is not None: - return user.email + return user.email # No valid email, not a valid user in the system, none! return None @@ -975,24 +1031,20 @@ def link_to_user(author, length=0, **kwargs): if user: author = user - display_person = person(author, 'username_or_name_or_email') + display_person = person(author, "username_or_name_or_email") if length: display_person = shorter(display_person, length) if user and user.username != user.DEFAULT_USER: - return link_to( - escape(display_person), - route_path('user_profile', username=user.username), - **kwargs) + return link_to(escape(display_person), route_path("user_profile", username=user.username), **kwargs) else: return escape(display_person) def link_to_group(users_group_name, **kwargs): return link_to( - escape(users_group_name), - route_path('user_group_profile', user_group_name=users_group_name), - **kwargs) + escape(users_group_name), route_path("user_group_profile", user_group_name=users_group_name), **kwargs + ) def person(author, show_attr="username_and_name"): @@ -1010,8 +1062,7 @@ def author_string(email): user = User.get_by_email(email, case_insensitive=True, cache=True) if user: if user.first_name or user.last_name: - return '%s %s <%s>' % ( - user.first_name, user.last_name, email) + return "%s %s <%s>" % (user.first_name, user.last_name, email) else: return email else: @@ -1025,7 +1076,7 @@ def person_by_id(id_, show_attr="username_and_name"): def person_getter(usr): return getattr(usr, show_attr) - #maybe it's an ID ? + # maybe it's an ID ? if str(id_).isdigit() or isinstance(id_, int): id_ = int(id_) user = User.get(id_) @@ -1035,8 +1086,8 @@ def person_by_id(id_, show_attr="username_and_name"): def gravatar_with_user(request, author, show_disabled=False, tooltip=False): - _render = request.get_partial_renderer('rhodecode:templates/base/base.mako') - return _render('gravatar_with_user', author, show_disabled=show_disabled, tooltip=tooltip) + _render = request.get_partial_renderer("rhodecode:templates/base/base.mako") + return _render("gravatar_with_user", author, show_disabled=show_disabled, tooltip=tooltip) tags_patterns = OrderedDict( @@ -1045,42 +1096,36 @@ tags_patterns = OrderedDict( "lang", ( re.compile(r"\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+\.]*)\]"), - '
\\2
', + r'
\\2
', ), ), ( "see", ( re.compile(r"\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]"), - '
see: \\1
', + r'
see: \\1
', ), ), ( "url", ( - re.compile( - r"\[url\ \=\>\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]" - ), - '
\\1
', + re.compile(r"\[url\ \=\>\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]"), + r'
\\1
', ), ), ( "license", ( - re.compile( - r"\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]" - ), + re.compile(r"\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]"), # don't make it a raw string here... - '
\\1
', + r'
\\1
', ), ), ( "ref", ( - re.compile( - r"\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]" - ), - '
\\1: \\2
', + re.compile(r"\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]"), + r'
\\1: \\2
', ), ), ( @@ -1113,12 +1158,12 @@ def extract_metatags(value): """ tags = [] if not value: - return tags, '' + return tags, "" for key, val in list(tags_patterns.items()): pat, replace_html = val tags.extend([(key, x.group()) for x in pat.finditer(value)]) - value = pat.sub('', value) + value = pat.sub("", value) return tags, value @@ -1128,7 +1173,7 @@ def style_metatag(tag_type, value): converts tags from value into html equivalent """ if not value: - return '' + return "" html_value = value tag_data = tags_patterns.get(tag_type) @@ -1150,32 +1195,39 @@ def bool2icon(value, show_at_false=True): """ if value: # does bool conversion - return HTML.tag('i', class_="icon-true", title='True') + return HTML.tag("i", class_="icon-true", title="True") else: # not true as bool if show_at_false: - return HTML.tag('i', class_="icon-false", title='False') - return HTML.tag('i') + return HTML.tag("i", class_="icon-false", title="False") + return HTML.tag("i") def b64(inp): return base64.b64encode(safe_bytes(inp)) -#============================================================================== + +# ============================================================================== # PERMS -#============================================================================== +# ============================================================================== from rhodecode.lib.auth import ( - HasPermissionAny, HasPermissionAll, - HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, - HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, - csrf_token_key, AuthUser) + HasPermissionAny, + HasPermissionAll, + HasRepoPermissionAny, + HasRepoPermissionAll, + HasRepoGroupPermissionAll, + HasRepoGroupPermissionAny, + HasRepoPermissionAnyApi, + get_csrf_token, + csrf_token_key, + AuthUser, +) -#============================================================================== +# ============================================================================== # GRAVATAR URL -#============================================================================== +# ============================================================================== class InitialsGravatar(object): - def __init__(self, email_address, first_name, last_name, size=30, - background=None, text_color='#fff'): + def __init__(self, email_address, first_name, last_name, size=30, background=None, text_color="#fff"): self.size = size self.first_name = first_name self.last_name = last_name @@ -1192,51 +1244,273 @@ class InitialsGravatar(object): generated with: http://phrogz.net/css/distinct-colors.html """ return [ - '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000', - '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320', - '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300', - '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140', - '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c', - '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020', - '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039', - '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f', - '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340', - '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98', - '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c', - '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200', - '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a', - '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959', - '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3', - '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626', - '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000', - '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362', - '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3', - '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a', - '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939', - '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39', - '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953', - '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9', - '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1', - '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900', - '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00', - '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3', - '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59', - '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079', - '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700', - '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d', - '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2', - '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff', - '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20', - '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626', - '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23', - '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff', - '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6', - '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a', - '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c', - '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600', - '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff', - '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539', - '#4f8c46', '#368dd9', '#5c0073' + "#bf3030", + "#a67f53", + "#00ff00", + "#5989b3", + "#392040", + "#d90000", + "#402910", + "#204020", + "#79baf2", + "#a700b3", + "#bf6060", + "#7f5320", + "#008000", + "#003059", + "#ee00ff", + "#ff0000", + "#8c4b00", + "#007300", + "#005fb3", + "#de73e6", + "#ff4040", + "#ffaa00", + "#3df255", + "#203140", + "#47004d", + "#591616", + "#664400", + "#59b365", + "#0d2133", + "#83008c", + "#592d2d", + "#bf9f60", + "#73e682", + "#1d3f73", + "#73006b", + "#402020", + "#b2862d", + "#397341", + "#597db3", + "#e600d6", + "#a60000", + "#736039", + "#00b318", + "#79aaf2", + "#330d30", + "#ff8080", + "#403010", + "#16591f", + "#002459", + "#8c4688", + "#e50000", + "#ffbf40", + "#00732e", + "#102340", + "#bf60ac", + "#8c4646", + "#cc8800", + "#00a642", + "#1d3473", + "#b32d98", + "#660e00", + "#ffd580", + "#80ffb2", + "#7391e6", + "#733967", + "#d97b6c", + "#8c5e00", + "#59b389", + "#3967e6", + "#590047", + "#73281d", + "#665200", + "#00e67a", + "#2d50b3", + "#8c2377", + "#734139", + "#b2982d", + "#16593a", + "#001859", + "#ff00aa", + "#a65e53", + "#ffcc00", + "#0d3321", + "#2d3959", + "#731d56", + "#401610", + "#4c3d00", + "#468c6c", + "#002ca6", + "#d936a3", + "#d94c36", + "#403920", + "#36d9a3", + "#0d1733", + "#592d4a", + "#993626", + "#cca300", + "#00734d", + "#46598c", + "#8c005e", + "#7f1100", + "#8c7000", + "#00a66f", + "#7382e6", + "#b32d74", + "#d9896c", + "#ffe680", + "#1d7362", + "#364cd9", + "#73003d", + "#d93a00", + "#998a4d", + "#59b3a1", + "#5965b3", + "#e5007a", + "#73341d", + "#665f00", + "#00b38f", + "#0018b3", + "#59163a", + "#b2502d", + "#bfb960", + "#00ffcc", + "#23318c", + "#a6537f", + "#734939", + "#b2a700", + "#104036", + "#3d3df2", + "#402031", + "#e56739", + "#736f39", + "#79f2ea", + "#000059", + "#401029", + "#4c1400", + "#ffee00", + "#005953", + "#101040", + "#990052", + "#402820", + "#403d10", + "#00ffee", + "#0000d9", + "#ff80c4", + "#a66953", + "#eeff00", + "#00ccbe", + "#8080ff", + "#e673a1", + "#a62c00", + "#474d00", + "#1a3331", + "#46468c", + "#733950", + "#662900", + "#858c23", + "#238c85", + "#0f0073", + "#b20047", + "#d9986c", + "#becc00", + "#396f73", + "#281d73", + "#ff0066", + "#ff6600", + "#dee673", + "#59adb3", + "#6559b3", + "#590024", + "#b2622d", + "#98b32d", + "#36ced9", + "#332d59", + "#40001a", + "#733f1d", + "#526600", + "#005359", + "#242040", + "#bf6079", + "#735039", + "#cef23d", + "#007780", + "#5630bf", + "#66001b", + "#b24700", + "#acbf60", + "#1d6273", + "#25008c", + "#731d34", + "#a67453", + "#50592d", + "#00ccff", + "#6600ff", + "#ff0044", + "#4c1f00", + "#8a994d", + "#79daf2", + "#a173e6", + "#d93662", + "#402310", + "#aaff00", + "#2d98b3", + "#8c40ff", + "#592d39", + "#ff8c40", + "#354020", + "#103640", + "#1a0040", + "#331a20", + "#331400", + "#334d00", + "#1d5673", + "#583973", + "#7f0022", + "#4c3626", + "#88cc00", + "#36a3d9", + "#3d0073", + "#d9364c", + "#33241a", + "#698c23", + "#5995b3", + "#300059", + "#e57382", + "#7f3300", + "#366600", + "#00aaff", + "#3a1659", + "#733941", + "#663600", + "#74b32d", + "#003c59", + "#7f53a6", + "#73000f", + "#ff8800", + "#baf279", + "#79caf2", + "#291040", + "#a6293a", + "#b2742d", + "#587339", + "#0077b3", + "#632699", + "#400009", + "#d9a66c", + "#294010", + "#2d4a59", + "#aa00ff", + "#4c131b", + "#b25f00", + "#5ce600", + "#267399", + "#a336d9", + "#990014", + "#664e33", + "#86bf60", + "#0088ff", + "#7700b3", + "#593a16", + "#073300", + "#1d4b73", + "#ac60bf", + "#e59539", + "#4f8c46", + "#368dd9", + "#5c0073", ] def rgb_to_hex_color(self, rgb_tuple): @@ -1245,7 +1519,7 @@ class InitialsGravatar(object): :param rgb_tuple: tuple with 3 ints represents rgb color space """ - return '#' + ("".join(map(chr, rgb_tuple)).encode('hex')) + return "#" + ("".join(map(chr, rgb_tuple)).encode("hex")) def email_to_int_list(self, email_str): """ @@ -1253,7 +1527,7 @@ class InitialsGravatar(object): It's going to be always between 0-255 """ digest = md5_safe(email_str.lower()) - return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)] + return [int(digest[i * 2 : i * 2 + 2], 16) for i in range(16)] def pick_color_bank_index(self, email_str, color_bank): return self.email_to_int_list(email_str)[0] % len(color_bank) @@ -1272,18 +1546,18 @@ class InitialsGravatar(object): def normalize_email(self, email_address): # default host used to fill in the fake/missing email - default_host = 'localhost' + default_host = "localhost" if not email_address: - email_address = f'{User.DEFAULT_USER}@{default_host}' + email_address = f"{User.DEFAULT_USER}@{default_host}" email_address = safe_str(email_address) - if '@' not in email_address: - email_address = f'{email_address}@{default_host}' + if "@" not in email_address: + email_address = f"{email_address}@{default_host}" - if email_address.endswith('@'): - email_address = f'{email_address}{default_host}' + if email_address.endswith("@"): + email_address = f"{email_address}{default_host}" email_address = convert_special_chars(email_address) @@ -1310,16 +1584,16 @@ class InitialsGravatar(object): first_name = convert_special_chars(self.first_name) last_name = convert_special_chars(self.last_name) # multi word last names, Guido Von Rossum, we take the last part only - last_name = last_name.split(' ', 1)[-1] + last_name = last_name.split(" ", 1)[-1] # do NFKD encoding, and also make sure email has proper format email_address = self.normalize_email(self.email_address) # first push the email initials - prefix, server = email_address.split('@', 1) + prefix, server = email_address.split("@", 1) # check if prefix is maybe a 'first_name.last_name' syntax - _dot_split = prefix.rsplit('.', 1) + _dot_split = prefix.rsplit(".", 1) if len(_dot_split) == 2 and _dot_split[1]: initials = [_dot_split[0][0], _dot_split[1][0]] else: @@ -1335,7 +1609,7 @@ class InitialsGravatar(object): if ln_letter: initials[1] = ln_letter - return ''.join(initials).upper() + return "".join(initials).upper() def get_img_data_by_type(self, font_family, img_type): default_user = """ @@ -1365,30 +1639,29 @@ class InitialsGravatar(object): 410.355,381.902,407.164,374.717z" fill="{text_color}"/> """.format( size=self.size, - background='#979797', # @grey4 + background="#979797", # @grey4 text_color=self.text_color, - font_family=font_family) + font_family=font_family, + ) - return { - "default_user": default_user - }[img_type] + return {"default_user": default_user}[img_type] def get_img_data(self, svg_type=None): """ generates the svg metadata for image """ fonts = [ - '-apple-system', - 'BlinkMacSystemFont', - 'Segoe UI', - 'Roboto', - 'Oxygen-Sans', - 'Ubuntu', - 'Cantarell', - 'Helvetica Neue', - 'sans-serif' + "-apple-system", + "BlinkMacSystemFont", + "Segoe UI", + "Roboto", + "Oxygen-Sans", + "Ubuntu", + "Cantarell", + "Helvetica Neue", + "sans-serif", ] - font_family = ','.join(fonts) + font_family = ",".join(fonts) if svg_type: return self.get_img_data_by_type(font_family, svg_type) @@ -1405,11 +1678,12 @@ class InitialsGravatar(object): """.format( size=self.size, - f_size=self.size/2.05, # scale the text inside the box nicely + f_size=self.size / 2.05, # scale the text inside the box nicely background=self.background, text_color=self.text_color, text=initials.upper(), - font_family=font_family) + font_family=font_family, + ) return img_data @@ -1419,22 +1693,23 @@ class InitialsGravatar(object): def initials_gravatar(request, email_address, first_name, last_name, size=30): - svg_type = None if email_address == User.DEFAULT_USER_EMAIL: - svg_type = 'default_user' + svg_type = "default_user" klass = InitialsGravatar(email_address, first_name, last_name, size) return klass.generate_svg(svg_type=svg_type) def gravatar_external(request, gravatar_url_tmpl, email_address, size=30): - return safe_str(gravatar_url_tmpl)\ - .replace('{email}', email_address) \ - .replace('{md5email}', md5_safe(email_address.lower())) \ - .replace('{netloc}', request.host) \ - .replace('{scheme}', request.scheme) \ - .replace('{size}', safe_str(size)) + return ( + safe_str(gravatar_url_tmpl) + .replace("{email}", email_address) + .replace("{md5email}", md5_safe(email_address.lower())) + .replace("{netloc}", request.host) + .replace("{scheme}", request.scheme) + .replace("{size}", safe_str(size)) + ) def gravatar_url(email_address, size=30, request=None): @@ -1448,15 +1723,14 @@ def gravatar_url(email_address, size=30, request=None): # empty email or default user if not email_address or email_address == User.DEFAULT_USER_EMAIL: - return initials_gravatar(request, User.DEFAULT_USER_EMAIL, '', '', size=size) + return initials_gravatar(request, User.DEFAULT_USER_EMAIL, "", "", size=size) if _use_gravatar: - gravatar_url_tmpl = request.call_context.visual.gravatar_url \ - or User.DEFAULT_GRAVATAR_URL + gravatar_url_tmpl = request.call_context.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL return gravatar_external(request, gravatar_url_tmpl, email_address, size=size) else: - return initials_gravatar(request, email_address, '', '', size=size) + return initials_gravatar(request, email_address, "", "", size=size) def breadcrumb_repo_link(repo): @@ -1470,15 +1744,21 @@ def breadcrumb_repo_link(repo): """ path = [ - link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name), - title='last change:{}'.format(format_date(group.last_commit_change))) + link_to( + group.name, + route_path("repo_group_home", repo_group_name=group.group_name), + title="last change:{}".format(format_date(group.last_commit_change)), + ) for group in repo.groups_with_parents ] + [ - link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name), - title='last change:{}'.format(format_date(repo.last_commit_change))) + link_to( + repo.just_name, + route_path("repo_summary", repo_name=repo.repo_name), + title="last change:{}".format(format_date(repo.last_commit_change)), + ) ] - return literal(' » '.join(path)) + return literal(" » ".join(path)) def breadcrumb_repo_group_link(repo_group): @@ -1492,17 +1772,21 @@ def breadcrumb_repo_group_link(repo_group): """ path = [ - link_to(group.name, - route_path('repo_group_home', repo_group_name=group.group_name), - title='last change:{}'.format(format_date(group.last_commit_change))) + link_to( + group.name, + route_path("repo_group_home", repo_group_name=group.group_name), + title="last change:{}".format(format_date(group.last_commit_change)), + ) for group in repo_group.parents ] + [ - link_to(repo_group.name, - route_path('repo_group_home', repo_group_name=repo_group.group_name), - title='last change:{}'.format(format_date(repo_group.last_commit_change))) + link_to( + repo_group.name, + route_path("repo_group_home", repo_group_name=repo_group.group_name), + title="last change:{}".format(format_date(repo_group.last_commit_change)), + ) ] - return literal(' » '.join(path)) + return literal(" » ".join(path)) def format_byte_size_binary(file_size): @@ -1521,13 +1805,14 @@ def urlify_text(text_, safe=True, **href_attrs): Extract urls from text and make html links out of them """ - url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]''' - r'''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''') + url_pat = re.compile( + r"""(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]""" r"""|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)""" + ) def url_func(match_obj): url_full = match_obj.groups()[0] a_options = dict(href_attrs) - a_options['href'] = url_full + a_options["href"] = url_full a_text = url_full return HTML.tag("a", a_text, **a_options) @@ -1546,7 +1831,7 @@ def urlify_commits(text_, repo_name): :param repo_name: repo name to build the URL with """ - url_pat = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)') + url_pat = re.compile(r"(^|\s)([0-9a-fA-F]{12,40})($|\s)") def url_func(match_obj): commit_id = match_obj.groups()[1] @@ -1555,18 +1840,16 @@ def urlify_commits(text_, repo_name): tmpl = ( '%(pref)s' - '%(commit_id)s%(suf)s' + "%(commit_id)s%(suf)s" ) return tmpl % { - 'pref': pref, - 'cls': 'revision-link', - 'url': route_url( - 'repo_commit', repo_name=repo_name, commit_id=commit_id), - 'commit_id': commit_id, - 'suf': suf, - 'hovercard_alt': 'Commit: {}'.format(commit_id), - 'hovercard_url': route_url( - 'hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id) + "pref": pref, + "cls": "revision-link", + "url": route_url("repo_commit", repo_name=repo_name, commit_id=commit_id), + "commit_id": commit_id, + "suf": suf, + "hovercard_alt": "Commit: {}".format(commit_id), + "hovercard_url": route_url("hovercard_repo_commit", repo_name=repo_name, commit_id=commit_id), } new_text = url_pat.sub(url_func, text_) @@ -1574,44 +1857,42 @@ def urlify_commits(text_, repo_name): return new_text -def _process_url_func(match_obj, repo_name, uid, entry, - return_raw_data=False, link_format='html'): - pref = '' - if match_obj.group().startswith(' '): - pref = ' ' +def _process_url_func(match_obj, repo_name, uid, entry, return_raw_data=False, link_format="html"): + pref = "" + if match_obj.group().startswith(" "): + pref = " " - issue_id = ''.join(match_obj.groups()) + issue_id = "".join(match_obj.groups()) - if link_format == 'html': + if link_format == "html": tmpl = ( - '%(pref)s' - '%(issue-prefix)s%(id-repr)s' - '') - elif link_format == 'html+hovercard': + '%(pref)s' "%(issue-prefix)s%(id-repr)s" "" + ) + elif link_format == "html+hovercard": tmpl = ( '%(pref)s' - '%(issue-prefix)s%(id-repr)s' - '') - elif link_format in ['rst', 'rst+hovercard']: - tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_' - elif link_format in ['markdown', 'markdown+hovercard']: - tmpl = '[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)' + "%(issue-prefix)s%(id-repr)s" + "" + ) + elif link_format in ["rst", "rst+hovercard"]: + tmpl = "`%(issue-prefix)s%(id-repr)s <%(url)s>`_" + elif link_format in ["markdown", "markdown+hovercard"]: + tmpl = "[%(pref)s%(issue-prefix)s%(id-repr)s](%(url)s)" else: - raise ValueError('Bad link_format:{}'.format(link_format)) + raise ValueError("Bad link_format:{}".format(link_format)) - (repo_name_cleaned, - parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name) + (repo_name_cleaned, parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name) # variables replacement named_vars = { - 'id': issue_id, - 'repo': repo_name, - 'repo_name': repo_name_cleaned, - 'group_name': parent_group_name, + "id": issue_id, + "repo": repo_name, + "repo_name": repo_name_cleaned, + "group_name": parent_group_name, # set dummy keys so we always have them - 'hostname': '', - 'netloc': '', - 'scheme': '' + "hostname": "", + "netloc": "", + "scheme": "", } request = get_current_request() @@ -1622,30 +1903,27 @@ def _process_url_func(match_obj, repo_name, uid, entry, # named regex variables named_vars.update(match_obj.groupdict()) - _url = string.Template(entry['url']).safe_substitute(**named_vars) - desc = string.Template(escape(entry['desc'])).safe_substitute(**named_vars) - hovercard_url = string.Template(entry.get('hovercard_url', '')).safe_substitute(**named_vars) + _url = string.Template(entry["url"]).safe_substitute(**named_vars) + desc = string.Template(escape(entry["desc"])).safe_substitute(**named_vars) + hovercard_url = string.Template(entry.get("hovercard_url", "")).safe_substitute(**named_vars) def quote_cleaner(input_str): """Remove quotes as it's HTML""" - return input_str.replace('"', '') + return input_str.replace('"', "") data = { - 'pref': pref, - 'cls': quote_cleaner('issue-tracker-link'), - 'url': quote_cleaner(_url), - 'id-repr': issue_id, - 'issue-prefix': entry['pref'], - 'serv': entry['url'], - 'title': sanitize_html(desc, strip=True), - 'hovercard_url': hovercard_url + "pref": pref, + "cls": quote_cleaner("issue-tracker-link"), + "url": quote_cleaner(_url), + "id-repr": issue_id, + "issue-prefix": entry["pref"], + "serv": entry["url"], + "title": sanitize_html(desc, strip=True), + "hovercard_url": hovercard_url, } if return_raw_data: - return { - 'id': issue_id, - 'url': _url - } + return {"id": issue_id, "url": _url} return tmpl % data @@ -1661,95 +1939,87 @@ def get_active_pattern_entries(repo_name): return active_entries -pr_pattern_re = regex.compile(r'(?:(?:^!)|(?: !))(\d+)') +pr_pattern_re = regex.compile(r"(?:(?:^!)|(?: !))(\d+)") -allowed_link_formats = [ - 'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] +allowed_link_formats = ["html", "rst", "markdown", "html+hovercard", "rst+hovercard", "markdown+hovercard"] -compile_cache = { - -} +compile_cache = {} -def process_patterns(text_string, repo_name, link_format='html', active_entries=None): - +def process_patterns(text_string, repo_name, link_format="html", active_entries=None): if link_format not in allowed_link_formats: - raise ValueError('Link format can be only one of:{} got {}'.format( - allowed_link_formats, link_format)) + raise ValueError("Link format can be only one of:{} got {}".format(allowed_link_formats, link_format)) issues_data = [] errors = [] new_text = text_string if active_entries is None: - log.debug('Fetch active issue tracker patterns for repo: %s', repo_name) + log.debug("Fetch active issue tracker patterns for repo: %s", repo_name) active_entries = get_active_pattern_entries(repo_name) - log.debug('Got %s pattern entries to process', len(active_entries)) + log.debug("Got %s pattern entries to process", len(active_entries)) for uid, entry in list(active_entries.items()): - - if not (entry['pat'] and entry['url']): - log.debug('skipping due to missing data') + if not (entry["pat"] and entry["url"]): + log.debug("skipping due to missing data") continue - log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s', - uid, entry['pat'], entry['url'], entry['pref']) + log.debug( + "issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s", uid, entry["pat"], entry["url"], entry["pref"] + ) - if entry.get('pat_compiled'): - pattern = entry['pat_compiled'] - elif entry['pat'] in compile_cache: - pattern = compile_cache[entry['pat']] + if entry.get("pat_compiled"): + pattern = entry["pat_compiled"] + elif entry["pat"] in compile_cache: + pattern = compile_cache[entry["pat"]] else: try: - pattern = regex.compile(r'%s' % entry['pat']) + pattern = regex.compile(r"%s" % entry["pat"]) except regex.error as e: - regex_err = ValueError('{}:{}'.format(entry['pat'], e)) - log.exception('issue tracker pattern: `%s` failed to compile', regex_err) + regex_err = ValueError("{}:{}".format(entry["pat"], e)) + log.exception("issue tracker pattern: `%s` failed to compile", regex_err) errors.append(regex_err) continue - compile_cache[entry['pat']] = pattern + compile_cache[entry["pat"]] = pattern - data_func = partial( - _process_url_func, repo_name=repo_name, entry=entry, uid=uid, - return_raw_data=True) + data_func = partial(_process_url_func, repo_name=repo_name, entry=entry, uid=uid, return_raw_data=True) for match_obj in pattern.finditer(text_string): issues_data.append(data_func(match_obj)) - url_func = partial( - _process_url_func, repo_name=repo_name, entry=entry, uid=uid, - link_format=link_format) + url_func = partial(_process_url_func, repo_name=repo_name, entry=entry, uid=uid, link_format=link_format) new_text = pattern.sub(url_func, new_text) - log.debug('processed prefix:uid `%s`', uid) + log.debug("processed prefix:uid `%s`", uid) # finally use global replace, eg !123 -> pr-link, those will not catch # if already similar pattern exists - server_url = '${scheme}://${netloc}' + server_url = "${scheme}://${netloc}" pr_entry = { - 'pref': '!', - 'url': server_url + '/_admin/pull-requests/${id}', - 'desc': 'Pull Request !${id}', - 'hovercard_url': server_url + '/_hovercard/pull_request/${id}' + "pref": "!", + "url": server_url + "/_admin/pull-requests/${id}", + "desc": "Pull Request !${id}", + "hovercard_url": server_url + "/_hovercard/pull_request/${id}", } pr_url_func = partial( - _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None, - link_format=link_format+'+hovercard') + _process_url_func, repo_name=repo_name, entry=pr_entry, uid=None, link_format=link_format + "+hovercard" + ) new_text = pr_pattern_re.sub(pr_url_func, new_text) - log.debug('processed !pr pattern') + log.debug("processed !pr pattern") return new_text, issues_data, errors -def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None, - issues_container_callback=None, error_container=None): +def urlify_commit_message( + commit_text, repository=None, active_pattern_entries=None, issues_container_callback=None, error_container=None +): """ Parses given text message and makes proper links. issues are linked to given issue-server, and rest is a commit link """ def escaper(_text): - return _text.replace('<', '<').replace('>', '>') + return _text.replace("<", "<").replace(">", ">") new_text = escaper(commit_text) @@ -1762,8 +2032,7 @@ def urlify_commit_message(commit_text, repository=None, active_pattern_entries=N new_text = urlify_commits(new_text, repository) # process issue tracker patterns - new_text, issues, errors = process_patterns( - new_text, repository or '', active_entries=active_pattern_entries) + new_text, issues, errors = process_patterns(new_text, repository or "", active_entries=active_pattern_entries) if issues_container_callback is not None: for issue in issues: @@ -1784,15 +2053,13 @@ def render_binary(repo_name, file_obj): filename = file_obj.name # images - for ext in ['*.png', '*.jpeg', '*.jpg', '*.ico', '*.gif']: + for ext in ["*.png", "*.jpeg", "*.jpg", "*.ico", "*.gif"]: if fnmatch.fnmatch(filename, pat=ext): src = route_path( - 'repo_file_raw', repo_name=repo_name, - commit_id=file_obj.commit.raw_id, - f_path=file_obj.path) + "repo_file_raw", repo_name=repo_name, commit_id=file_obj.commit.raw_id, f_path=file_obj.path + ) - return literal( - 'rendered-image'.format(src)) + return literal('rendered-image'.format(src)) def renderer_from_filename(filename, exclude=None): @@ -1801,9 +2068,9 @@ def renderer_from_filename(filename, exclude=None): """ # ipython - for ext in ['*.ipynb']: + for ext in ["*.ipynb"]: if fnmatch.fnmatch(filename, pat=ext): - return 'jupyter' + return "jupyter" is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude) if is_markup: @@ -1811,50 +2078,52 @@ def renderer_from_filename(filename, exclude=None): return None -def render(source, renderer='rst', mentions=False, relative_urls=None, - repo_name=None, active_pattern_entries=None, issues_container_callback=None): - +def render( + source, + renderer="rst", + mentions=False, + relative_urls=None, + repo_name=None, + active_pattern_entries=None, + issues_container_callback=None, +): def maybe_convert_relative_links(html_source): if relative_urls: return relative_links(html_source, relative_urls) return html_source - if renderer == 'plain': - return literal( - MarkupRenderer.plain(source, leading_newline=False)) + if renderer == "plain": + return literal(MarkupRenderer.plain(source, leading_newline=False)) - elif renderer == 'rst': + elif renderer == "rst": if repo_name: # process patterns on comments if we pass in repo name source, issues, errors = process_patterns( - source, repo_name, link_format='rst', - active_entries=active_pattern_entries) + source, repo_name, link_format="rst", active_entries=active_pattern_entries + ) if issues_container_callback is not None: for issue in issues: issues_container_callback(issue) - rendered_block = maybe_convert_relative_links( - MarkupRenderer.rst(source, mentions=mentions)) + rendered_block = maybe_convert_relative_links(MarkupRenderer.rst(source, mentions=mentions)) return literal(f'
{rendered_block}
') - elif renderer == 'markdown': + elif renderer == "markdown": if repo_name: # process patterns on comments if we pass in repo name source, issues, errors = process_patterns( - source, repo_name, link_format='markdown', - active_entries=active_pattern_entries) + source, repo_name, link_format="markdown", active_entries=active_pattern_entries + ) if issues_container_callback is not None: for issue in issues: issues_container_callback(issue) - rendered_block = maybe_convert_relative_links( - MarkupRenderer.markdown(source, flavored=True, mentions=mentions)) + rendered_block = maybe_convert_relative_links(MarkupRenderer.markdown(source, flavored=True, mentions=mentions)) return literal(f'
{rendered_block}
') - elif renderer == 'jupyter': - rendered_block = maybe_convert_relative_links( - MarkupRenderer.jupyter(source)) + elif renderer == "jupyter": + rendered_block = maybe_convert_relative_links(MarkupRenderer.jupyter(source)) return literal(f'
{rendered_block}
') # None means just show the file-source @@ -1882,51 +2151,61 @@ def get_permission_name(key): def journal_filter_help(request): _ = request.translate from rhodecode.lib.audit_logger import ACTIONS - actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80)) + + actions = "\n".join(textwrap.wrap(", ".join(sorted(ACTIONS.keys())), 80)) return _( - 'Example filter terms:\n' + - ' repository:vcs\n' + - ' username:marcin\n' + - ' username:(NOT marcin)\n' + - ' action:*push*\n' + - ' ip:127.0.0.1\n' + - ' date:20120101\n' + - ' date:[20120101100000 TO 20120102]\n' + - '\n' + - 'Actions: {actions}\n' + - '\n' + - 'Generate wildcards using \'*\' character:\n' + - ' "repository:vcs*" - search everything starting with \'vcs\'\n' + - ' "repository:*vcs*" - search for repository containing \'vcs\'\n' + - '\n' + - 'Optional AND / OR operators in queries\n' + - ' "repository:vcs OR repository:test"\n' + - ' "username:test AND repository:test*"\n' + "Example filter terms:\n" + + " repository:vcs\n" + + " username:marcin\n" + + " username:(NOT marcin)\n" + + " action:*push*\n" + + " ip:127.0.0.1\n" + + " date:20120101\n" + + " date:[20120101100000 TO 20120102]\n" + + "\n" + + "Actions: {actions}\n" + + "\n" + + "Generate wildcards using '*' character:\n" + + " \"repository:vcs*\" - search everything starting with 'vcs'\n" + + " \"repository:*vcs*\" - search for repository containing 'vcs'\n" + + "\n" + + "Optional AND / OR operators in queries\n" + + ' "repository:vcs OR repository:test"\n' + + ' "username:test AND repository:test*"\n' ).format(actions=actions) def not_mapped_error(repo_name): from rhodecode.translation import _ - flash(_('%s repository is not mapped to db perhaps' - ' it was created or renamed from the filesystem' - ' please run the application again' - ' in order to rescan repositories') % repo_name, category='error') + + flash( + _( + "%s repository is not mapped to db perhaps" + " it was created or renamed from the filesystem" + " please run the application again" + " in order to rescan repositories" + ) + % repo_name, + category="error", + ) def ip_range(ip_addr): from rhodecode.model.db import UserIpMap + s, e = UserIpMap._get_ip_range(ip_addr) - return '%s - %s' % (s, e) + return "%s - %s" % (s, e) -def form(url, method='post', needs_csrf_token=True, **attrs): +def form(url, method="post", needs_csrf_token=True, **attrs): """Wrapper around webhelpers.tags.form to prevent CSRF attacks.""" - if method.lower() != 'get' and needs_csrf_token: + if method.lower() != "get" and needs_csrf_token: raise Exception( - 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' + - 'CSRF token. If the endpoint does not require such token you can ' + - 'explicitly set the parameter needs_csrf_token to false.') + "Forms to POST/PUT/DELETE endpoints should have (in general) a " + + "CSRF token. If the endpoint does not require such token you can " + + "explicitly set the parameter needs_csrf_token to false." + ) return insecure_form(url, method=method, **attrs) @@ -1951,17 +2230,14 @@ def secure_form(form_url, method="POST", multipart=False, **attrs): """ - if 'request' in attrs: - session = attrs['request'].session - del attrs['request'] + if "request" in attrs: + session = attrs["request"].session + del attrs["request"] else: - raise ValueError( - 'Calling this form requires request= to be passed as argument') + raise ValueError("Calling this form requires request= to be passed as argument") _form = insecure_form(form_url, method, multipart, **attrs) - token = literal( - ''.format( - csrf_token_key, get_csrf_token(session))) + token = literal(''.format(csrf_token_key, get_csrf_token(session))) return literal("%s\n%s" % (_form, token)) @@ -1984,12 +2260,12 @@ def dropdownmenu(name, selected, options, enable_filter=False, **attrs): filter_option = """, minimumResultsForSearch: -1 """ - input_id = attrs.get('id') or name - extra_classes = ' '.join(attrs.pop('extra_classes', [])) + input_id = attrs.get("id") or name + extra_classes = " ".join(attrs.pop("extra_classes", [])) filter_enabled = "" if enable_filter else filter_option select_script = literal(select2 % (input_id, extra_classes, filter_enabled)) - return literal(select_html+select_script) + return literal(select_html + select_script) def get_visual_attr(tmpl_context_var, attr_name): @@ -1999,7 +2275,7 @@ def get_visual_attr(tmpl_context_var, attr_name): :param tmpl_context_var: instance of tmpl_context, usually present as `c` :param attr_name: name of the attribute we fetch from the c.visual """ - visual = getattr(tmpl_context_var, 'visual', None) + visual = getattr(tmpl_context_var, "visual", None) if not visual: return else: @@ -2008,10 +2284,10 @@ def get_visual_attr(tmpl_context_var, attr_name): def get_last_path_part(file_node): if not file_node.path: - return '/' + return "/" - path = safe_str(file_node.path.split('/')[-1]) - return '../' + path + path = safe_str(file_node.path.split("/")[-1]) + return "../" + path def route_url(*args, **kwargs): @@ -2044,16 +2320,10 @@ def current_route_path(request, **kw): def curl_api_example(method, args): - args_json = json.dumps(OrderedDict([ - ('id', 1), - ('auth_token', 'SECRET'), - ('method', method), - ('args', args) - ])) + args_json = json.dumps(OrderedDict([("id", 1), ("auth_token", "SECRET"), ("method", method), ("args", args)])) return "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{args_json}'".format( - api_url=route_url('apiv2'), - args_json=args_json + api_url=route_url("apiv2"), args_json=args_json ) @@ -2064,10 +2334,10 @@ def api_call_example(method, args): curl_call = curl_api_example(method, args) return literal( - curl_call + - "

SECRET can be found in auth-tokens page, " - "and needs to be of `api calls` role." - .format(token_url=route_url('my_account_auth_tokens'))) + curl_call + + '

SECRET can be found in auth-tokens page, ' + "and needs to be of `api calls` role.".format(token_url=route_url("my_account_auth_tokens")) + ) def notification_description(notification, request): @@ -2075,8 +2345,8 @@ def notification_description(notification, request): Generate notification human readable description based on notification type """ from rhodecode.model.notification import NotificationModel - return NotificationModel().make_description( - notification, translate=request.translate) + + return NotificationModel().make_description(notification, translate=request.translate) def go_import_header(request, db_repo=None): @@ -2086,29 +2356,29 @@ def go_import_header(request, db_repo=None): if not db_repo: return - if 'go-get' not in request.GET: + if "go-get" not in request.GET: return clone_url = db_repo.clone_url() - prefix = re.split(r'^https?:\/\/', clone_url)[-1] + prefix = re.split(r"^https?:\/\/", clone_url)[-1] # we have a repo and go-get flag, - return literal(''.format( - prefix, db_repo.repo_type, clone_url)) + return literal(''.format(prefix, db_repo.repo_type, clone_url)) def reviewer_as_json(*args, **kwargs): from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json + return _reviewer_as_json(*args, **kwargs) def get_repo_view_type(request): route_name = request.matched_route.name route_to_view_type = { - 'repo_changelog': 'commits', - 'repo_commits': 'commits', - 'repo_files': 'files', - 'repo_summary': 'summary', - 'repo_commit': 'commit' + "repo_changelog": "commits", + "repo_commits": "commits", + "repo_files": "files", + "repo_summary": "summary", + "repo_commit": "commit", } return route_to_view_type.get(route_name) @@ -2139,9 +2409,10 @@ class IssuesRegistry(object): def __call__(self, commit_dict=None): def callback(issue): if commit_dict and issue: - issue['commit'] = commit_dict + issue["commit"] = commit_dict self.issues.append(issue) - self.unique_issues[issue['id']].append(issue) + self.unique_issues[issue["id"]].append(issue) + return callback def get_issues(self): @@ -2149,7 +2420,7 @@ class IssuesRegistry(object): @property def issues_unique_count(self): - return len(set(i['id'] for i in self.issues)) + return len(set(i["id"] for i in self.issues)) def get_directory_statistics(start_path): @@ -2177,7 +2448,7 @@ def get_directory_statistics(start_path): file_size = os.path.getsize(filepath) dir_size += file_size - directory_stats[dir_path] = {'file_count': file_count, 'size': dir_size} + directory_stats[dir_path] = {"file_count": file_count, "size": dir_size} total_files += file_count total_size += dir_size diff --git a/rhodecode/lib/middleware/utils/scm_app_http.py b/rhodecode/lib/middleware/utils/scm_app_http.py index f9551ba9..ddf20032 100644 --- a/rhodecode/lib/middleware/utils/scm_app_http.py +++ b/rhodecode/lib/middleware/utils/scm_app_http.py @@ -117,12 +117,6 @@ class VcsHttpProxy(object): log.debug("http-app: preparing %s request to: %s", method, url) - import ipdb; - import rich - from rich import print as pprint, inspect - pprint("** STARTING DEBUG **") - ipdb.set_trace() - response = session.request( method, url, data=_maybe_stream_request(environ), headers=request_headers, stream=True )