changeset 8789:71a37439dcee

lib: move urlification to webutils Less use of helpers in model.
author Mads Kiilerich <mads@kiilerich.com>
date Mon, 09 Nov 2020 17:13:33 +0100
parents 0383ed91d4ed
children 5e8f46e868e8
files kallithea/controllers/feed.py kallithea/lib/helpers.py kallithea/lib/webutils.py kallithea/model/notification.py kallithea/model/repo.py kallithea/tests/functional/test_files.py kallithea/tests/other/test_libs.py
diffstat 7 files changed, 225 insertions(+), 226 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/controllers/feed.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/controllers/feed.py	Mon Nov 09 17:13:33 2020 +0100
@@ -89,7 +89,7 @@
         desc_msg.append('changeset: <a href="%s">%s</a>' % (_url, cs.raw_id[:8]))
 
         desc_msg.append('<pre>')
-        desc_msg.append(h.urlify_text(cs.message))
+        desc_msg.append(webutils.urlify_text(cs.message))
         desc_msg.append('\n')
         desc_msg.extend(changes)
         if asbool(kallithea.CONFIG.get('rss_include_diff', False)):
--- a/kallithea/lib/helpers.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/lib/helpers.py	Mon Nov 09 17:13:33 2020 +0100
@@ -45,9 +45,9 @@
 # SCM FILTERS available via h.
 #==============================================================================
 from kallithea.lib.vcs.utils import author_email, author_name
-from kallithea.lib.webutils import (HTML, MENTIONS_REGEX, Option, canonical_url, checkbox, chop_at, end_form, escape, form, format_byte_size, hidden,
-                                    html_escape, js, jshtml, link_to, literal, password, pop_flash_messages, radio, reset, safeid, select,
-                                    session_csrf_secret_name, session_csrf_secret_token, submit, text, textarea, truncate, url, url_re, wrap_paragraphs)
+from kallithea.lib.webutils import (HTML, Option, canonical_url, checkbox, chop_at, end_form, escape, form, format_byte_size, hidden, js, jshtml, link_to,
+                                    literal, password, pop_flash_messages, radio, render_w_mentions, reset, safeid, select, session_csrf_secret_name,
+                                    session_csrf_secret_token, submit, text, textarea, url, urlify_text, wrap_paragraphs)
 from kallithea.model import db
 from kallithea.model.changeset_status import ChangesetStatusModel
 
@@ -68,6 +68,7 @@
 assert password
 assert pop_flash_messages
 assert radio
+assert render_w_mentions
 assert reset
 assert safeid
 assert select
@@ -76,6 +77,7 @@
 assert submit
 assert text
 assert textarea
+assert urlify_text
 assert wrap_paragraphs
 # from kallithea.lib.auth
 assert HasPermissionAny
@@ -849,212 +851,6 @@
     return literal('<div class="progress" style="width:%spx">%s%s</div>' % (width, d_a, d_d))
 
 
-_URLIFY_RE = re.compile(r'''
-# URL markup
-(?P<url>%s) |
-# @mention markup
-(?P<mention>%s) |
-# Changeset hash markup
-(?<!\w|[-_])
-  (?P<hash>[0-9a-f]{12,40})
-(?!\w|[-_]) |
-# Markup of *bold text*
-(?:
-  (?:^|(?<=\s))
-  (?P<bold> [*] (?!\s) [^*\n]* (?<!\s) [*] )
-  (?![*\w])
-) |
-# "Stylize" markup
-\[see\ \=&gt;\ *(?P<seen>[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] |
-\[license\ \=&gt;\ *(?P<license>[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] |
-\[(?P<tagtype>requires|recommends|conflicts|base)\ \=&gt;\ *(?P<tagvalue>[a-zA-Z0-9\-\/]*)\] |
-\[(?:lang|language)\ \=&gt;\ *(?P<lang>[a-zA-Z\-\/\#\+]*)\] |
-\[(?P<tag>[a-z]+)\]
-''' % (url_re.pattern, MENTIONS_REGEX.pattern),
-    re.VERBOSE | re.MULTILINE | re.IGNORECASE)
-
-
-def urlify_text(s, repo_name=None, link_=None, truncate=None, stylize=False, truncatef=truncate):
-    """
-    Parses given text message and make literal html with markup.
-    The text will be truncated to the specified length.
-    Hashes are turned into changeset links to specified repository.
-    URLs links to what they say.
-    Issues are linked to given issue-server.
-    If link_ is provided, all text not already linking somewhere will link there.
-    >>> urlify_text("Urlify http://example.com/ and 'https://example.com' *and* <b>markup/b>")
-    literal('Urlify <a href="http://example.com/">http://example.com/</a> and &#39;<a href="https://example.com&apos">https://example.com&apos</a>; <b>*and*</b> &lt;b&gt;markup/b&gt;')
-    """
-
-    def _replace(match_obj):
-        match_url = match_obj.group('url')
-        if match_url is not None:
-            return '<a href="%(url)s">%(url)s</a>' % {'url': match_url}
-        mention = match_obj.group('mention')
-        if mention is not None:
-            return '<b>%s</b>' % mention
-        hash_ = match_obj.group('hash')
-        if hash_ is not None and repo_name is not None:
-            return '<a class="changeset_hash" href="%(url)s">%(hash)s</a>' % {
-                 'url': url('changeset_home', repo_name=repo_name, revision=hash_),
-                 'hash': hash_,
-                }
-        bold = match_obj.group('bold')
-        if bold is not None:
-            return '<b>*%s*</b>' % _urlify(bold[1:-1])
-        if stylize:
-            seen = match_obj.group('seen')
-            if seen:
-                return '<div class="label label-meta" data-tag="see">see =&gt; %s</div>' % seen
-            license = match_obj.group('license')
-            if license:
-                return '<div class="label label-meta" data-tag="license"><a href="http://www.opensource.org/licenses/%s">%s</a></div>' % (license, license)
-            tagtype = match_obj.group('tagtype')
-            if tagtype:
-                tagvalue = match_obj.group('tagvalue')
-                return '<div class="label label-meta" data-tag="%s">%s =&gt; <a href="/%s">%s</a></div>' % (tagtype, tagtype, tagvalue, tagvalue)
-            lang = match_obj.group('lang')
-            if lang:
-                return '<div class="label label-meta" data-tag="lang">%s</div>' % lang
-            tag = match_obj.group('tag')
-            if tag:
-                return '<div class="label label-meta" data-tag="%s">%s</div>' % (tag, tag)
-        return match_obj.group(0)
-
-    def _urlify(s):
-        """
-        Extract urls from text and make html links out of them
-        """
-        return _URLIFY_RE.sub(_replace, s)
-
-    if truncate is None:
-        s = s.rstrip()
-    else:
-        s = truncatef(s, truncate, whole_word=True)
-    s = html_escape(s)
-    s = _urlify(s)
-    if repo_name is not None:
-        s = urlify_issues(s, repo_name)
-    if link_ is not None:
-        # make href around everything that isn't a href already
-        s = linkify_others(s, link_)
-    s = s.replace('\r\n', '<br/>').replace('\n', '<br/>')
-    # Turn HTML5 into more valid HTML4 as required by some mail readers.
-    # (This is not done in one step in html_escape, because character codes like
-    # &#123; risk to be seen as an issue reference due to the presence of '#'.)
-    s = s.replace("&apos;", "&#39;")
-    return literal(s)
-
-
-def linkify_others(t, l):
-    """Add a default link to html with links.
-    HTML doesn't allow nesting of links, so the outer link must be broken up
-    in pieces and give space for other links.
-    """
-    urls = re.compile(r'(\<a.*?\<\/a\>)',)
-    links = []
-    for e in urls.split(t):
-        if e.strip() and not urls.match(e):
-            links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
-        else:
-            links.append(e)
-
-    return ''.join(links)
-
-
-# Global variable that will hold the actual urlify_issues function body.
-# Will be set on first use when the global configuration has been read.
-_urlify_issues_f = None
-
-
-def urlify_issues(newtext, repo_name):
-    """Urlify issue references according to .ini configuration"""
-    global _urlify_issues_f
-    if _urlify_issues_f is None:
-        assert kallithea.CONFIG['sqlalchemy.url'] # make sure config has been loaded
-
-        # Build chain of urlify functions, starting with not doing any transformation
-        def tmp_urlify_issues_f(s):
-            return s
-
-        issue_pat_re = re.compile(r'issue_pat(.*)')
-        for k in kallithea.CONFIG:
-            # Find all issue_pat* settings that also have corresponding server_link and prefix configuration
-            m = issue_pat_re.match(k)
-            if m is None:
-                continue
-            suffix = m.group(1)
-            issue_pat = kallithea.CONFIG.get(k)
-            issue_server_link = kallithea.CONFIG.get('issue_server_link%s' % suffix)
-            issue_sub = kallithea.CONFIG.get('issue_sub%s' % suffix)
-            issue_prefix = kallithea.CONFIG.get('issue_prefix%s' % suffix)
-            if issue_prefix:
-                log.error('found unsupported issue_prefix%s = %r - use issue_sub%s instead', suffix, issue_prefix, suffix)
-            if not issue_pat:
-                log.error('skipping incomplete issue pattern %r: it needs a regexp', k)
-                continue
-            if not issue_server_link:
-                log.error('skipping incomplete issue pattern %r: it needs issue_server_link%s', k, suffix)
-                continue
-            if issue_sub is None: # issue_sub can be empty but should be present
-                log.error('skipping incomplete issue pattern %r: it needs (a potentially empty) issue_sub%s', k, suffix)
-                continue
-
-            # Wrap tmp_urlify_issues_f with substitution of this pattern, while making sure all loop variables (and compiled regexpes) are bound
-            try:
-                issue_re = re.compile(issue_pat)
-            except re.error as e:
-                log.error('skipping invalid issue pattern %r: %r -> %r %r. Error: %s', k, issue_pat, issue_server_link, issue_sub, str(e))
-                continue
-
-            log.debug('issue pattern %r: %r -> %r %r', k, issue_pat, issue_server_link, issue_sub)
-
-            def issues_replace(match_obj,
-                               issue_server_link=issue_server_link, issue_sub=issue_sub):
-                try:
-                    issue_url = match_obj.expand(issue_server_link)
-                except (IndexError, re.error) as e:
-                    log.error('invalid issue_url setting %r -> %r %r. Error: %s', issue_pat, issue_server_link, issue_sub, str(e))
-                    issue_url = issue_server_link
-                issue_url = issue_url.replace('{repo}', repo_name)
-                issue_url = issue_url.replace('{repo_name}', repo_name.split(kallithea.URL_SEP)[-1])
-                # if issue_sub is empty use the matched issue reference verbatim
-                if not issue_sub:
-                    issue_text = match_obj.group()
-                else:
-                    try:
-                        issue_text = match_obj.expand(issue_sub)
-                    except (IndexError, re.error) as e:
-                        log.error('invalid issue_sub setting %r -> %r %r. Error: %s', issue_pat, issue_server_link, issue_sub, str(e))
-                        issue_text = match_obj.group()
-
-                return (
-                    '<a class="issue-tracker-link" href="%(url)s">'
-                    '%(text)s'
-                    '</a>'
-                    ) % {
-                     'url': issue_url,
-                     'text': issue_text,
-                    }
-
-            def tmp_urlify_issues_f(s, issue_re=issue_re, issues_replace=issues_replace, chain_f=tmp_urlify_issues_f):
-                return issue_re.sub(issues_replace, chain_f(s))
-
-        # Set tmp function globally - atomically
-        _urlify_issues_f = tmp_urlify_issues_f
-
-    return _urlify_issues_f(newtext)
-
-
-def render_w_mentions(source, repo_name=None):
-    """
-    Render plain text with revision hashes and issue references urlified
-    and with @mention highlighting.
-    """
-    s = urlify_text(source, repo_name=repo_name)
-    return literal('<div class="formatted-fixed">%s</div>' % s)
-
-
 def changeset_status(repo, revision):
     return ChangesetStatusModel().get_status(repo, revision)
 
--- a/kallithea/lib/webutils.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/lib/webutils.py	Mon Nov 09 17:13:33 2020 +0100
@@ -327,3 +327,208 @@
     ['1-2.a_X', '1234', 'ddd', 'ee', 'ff', 'gg', 'gg', 'hh', 'zz']
     """
     return MENTIONS_REGEX.findall(text)
+
+
+_URLIFY_RE = re.compile(r'''
+# URL markup
+(?P<url>%s) |
+# @mention markup
+(?P<mention>%s) |
+# Changeset hash markup
+(?<!\w|[-_])
+  (?P<hash>[0-9a-f]{12,40})
+(?!\w|[-_]) |
+# Markup of *bold text*
+(?:
+  (?:^|(?<=\s))
+  (?P<bold> [*] (?!\s) [^*\n]* (?<!\s) [*] )
+  (?![*\w])
+) |
+# "Stylize" markup
+\[see\ \=&gt;\ *(?P<seen>[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] |
+\[license\ \=&gt;\ *(?P<license>[a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\] |
+\[(?P<tagtype>requires|recommends|conflicts|base)\ \=&gt;\ *(?P<tagvalue>[a-zA-Z0-9\-\/]*)\] |
+\[(?:lang|language)\ \=&gt;\ *(?P<lang>[a-zA-Z\-\/\#\+]*)\] |
+\[(?P<tag>[a-z]+)\]
+''' % (url_re.pattern, MENTIONS_REGEX.pattern),
+    re.VERBOSE | re.MULTILINE | re.IGNORECASE)
+
+
+def urlify_text(s, repo_name=None, link_=None, truncate=None, stylize=False, truncatef=truncate):
+    """
+    Parses given text message and make literal html with markup.
+    The text will be truncated to the specified length.
+    Hashes are turned into changeset links to specified repository.
+    URLs links to what they say.
+    Issues are linked to given issue-server.
+    If link_ is provided, all text not already linking somewhere will link there.
+    >>> urlify_text("Urlify http://example.com/ and 'https://example.com' *and* <b>markup/b>")
+    literal('Urlify <a href="http://example.com/">http://example.com/</a> and &#39;<a href="https://example.com&apos">https://example.com&apos</a>; <b>*and*</b> &lt;b&gt;markup/b&gt;')
+    """
+
+    def _replace(match_obj):
+        match_url = match_obj.group('url')
+        if match_url is not None:
+            return '<a href="%(url)s">%(url)s</a>' % {'url': match_url}
+        mention = match_obj.group('mention')
+        if mention is not None:
+            return '<b>%s</b>' % mention
+        hash_ = match_obj.group('hash')
+        if hash_ is not None and repo_name is not None:
+            return '<a class="changeset_hash" href="%(url)s">%(hash)s</a>' % {
+                 'url': url('changeset_home', repo_name=repo_name, revision=hash_),
+                 'hash': hash_,
+                }
+        bold = match_obj.group('bold')
+        if bold is not None:
+            return '<b>*%s*</b>' % _urlify(bold[1:-1])
+        if stylize:
+            seen = match_obj.group('seen')
+            if seen:
+                return '<div class="label label-meta" data-tag="see">see =&gt; %s</div>' % seen
+            license = match_obj.group('license')
+            if license:
+                return '<div class="label label-meta" data-tag="license"><a href="http://www.opensource.org/licenses/%s">%s</a></div>' % (license, license)
+            tagtype = match_obj.group('tagtype')
+            if tagtype:
+                tagvalue = match_obj.group('tagvalue')
+                return '<div class="label label-meta" data-tag="%s">%s =&gt; <a href="/%s">%s</a></div>' % (tagtype, tagtype, tagvalue, tagvalue)
+            lang = match_obj.group('lang')
+            if lang:
+                return '<div class="label label-meta" data-tag="lang">%s</div>' % lang
+            tag = match_obj.group('tag')
+            if tag:
+                return '<div class="label label-meta" data-tag="%s">%s</div>' % (tag, tag)
+        return match_obj.group(0)
+
+    def _urlify(s):
+        """
+        Extract urls from text and make html links out of them
+        """
+        return _URLIFY_RE.sub(_replace, s)
+
+    if truncate is None:
+        s = s.rstrip()
+    else:
+        s = truncatef(s, truncate, whole_word=True)
+    s = html_escape(s)
+    s = _urlify(s)
+    if repo_name is not None:
+        s = _urlify_issues(s, repo_name)
+    if link_ is not None:
+        # make href around everything that isn't a href already
+        s = _linkify_others(s, link_)
+    s = s.replace('\r\n', '<br/>').replace('\n', '<br/>')
+    # Turn HTML5 into more valid HTML4 as required by some mail readers.
+    # (This is not done in one step in html_escape, because character codes like
+    # &#123; risk to be seen as an issue reference due to the presence of '#'.)
+    s = s.replace("&apos;", "&#39;")
+    return literal(s)
+
+
+def _linkify_others(t, l):
+    """Add a default link to html with links.
+    HTML doesn't allow nesting of links, so the outer link must be broken up
+    in pieces and give space for other links.
+    """
+    urls = re.compile(r'(\<a.*?\<\/a\>)',)
+    links = []
+    for e in urls.split(t):
+        if e.strip() and not urls.match(e):
+            links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
+        else:
+            links.append(e)
+    return ''.join(links)
+
+
+# Global variable that will hold the actual _urlify_issues function body.
+# Will be set on first use when the global configuration has been read.
+_urlify_issues_f = None
+
+
+def _urlify_issues(newtext, repo_name):
+    """Urlify issue references according to .ini configuration"""
+    global _urlify_issues_f
+    if _urlify_issues_f is None:
+        assert kallithea.CONFIG['sqlalchemy.url'] # make sure config has been loaded
+
+        # Build chain of urlify functions, starting with not doing any transformation
+        def tmp_urlify_issues_f(s):
+            return s
+
+        issue_pat_re = re.compile(r'issue_pat(.*)')
+        for k in kallithea.CONFIG:
+            # Find all issue_pat* settings that also have corresponding server_link and prefix configuration
+            m = issue_pat_re.match(k)
+            if m is None:
+                continue
+            suffix = m.group(1)
+            issue_pat = kallithea.CONFIG.get(k)
+            issue_server_link = kallithea.CONFIG.get('issue_server_link%s' % suffix)
+            issue_sub = kallithea.CONFIG.get('issue_sub%s' % suffix)
+            issue_prefix = kallithea.CONFIG.get('issue_prefix%s' % suffix)
+            if issue_prefix:
+                log.error('found unsupported issue_prefix%s = %r - use issue_sub%s instead', suffix, issue_prefix, suffix)
+            if not issue_pat:
+                log.error('skipping incomplete issue pattern %r: it needs a regexp', k)
+                continue
+            if not issue_server_link:
+                log.error('skipping incomplete issue pattern %r: it needs issue_server_link%s', k, suffix)
+                continue
+            if issue_sub is None: # issue_sub can be empty but should be present
+                log.error('skipping incomplete issue pattern %r: it needs (a potentially empty) issue_sub%s', k, suffix)
+                continue
+
+            # Wrap tmp_urlify_issues_f with substitution of this pattern, while making sure all loop variables (and compiled regexpes) are bound
+            try:
+                issue_re = re.compile(issue_pat)
+            except re.error as e:
+                log.error('skipping invalid issue pattern %r: %r -> %r %r. Error: %s', k, issue_pat, issue_server_link, issue_sub, str(e))
+                continue
+
+            log.debug('issue pattern %r: %r -> %r %r', k, issue_pat, issue_server_link, issue_sub)
+
+            def issues_replace(match_obj,
+                               issue_server_link=issue_server_link, issue_sub=issue_sub):
+                try:
+                    issue_url = match_obj.expand(issue_server_link)
+                except (IndexError, re.error) as e:
+                    log.error('invalid issue_url setting %r -> %r %r. Error: %s', issue_pat, issue_server_link, issue_sub, str(e))
+                    issue_url = issue_server_link
+                issue_url = issue_url.replace('{repo}', repo_name)
+                issue_url = issue_url.replace('{repo_name}', repo_name.split(kallithea.URL_SEP)[-1])
+                # if issue_sub is empty use the matched issue reference verbatim
+                if not issue_sub:
+                    issue_text = match_obj.group()
+                else:
+                    try:
+                        issue_text = match_obj.expand(issue_sub)
+                    except (IndexError, re.error) as e:
+                        log.error('invalid issue_sub setting %r -> %r %r. Error: %s', issue_pat, issue_server_link, issue_sub, str(e))
+                        issue_text = match_obj.group()
+
+                return (
+                    '<a class="issue-tracker-link" href="%(url)s">'
+                    '%(text)s'
+                    '</a>'
+                    ) % {
+                     'url': issue_url,
+                     'text': issue_text,
+                    }
+
+            def tmp_urlify_issues_f(s, issue_re=issue_re, issues_replace=issues_replace, chain_f=tmp_urlify_issues_f):
+                return issue_re.sub(issues_replace, chain_f(s))
+
+        # Set tmp function globally - atomically
+        _urlify_issues_f = tmp_urlify_issues_f
+
+    return _urlify_issues_f(newtext)
+
+
+def render_w_mentions(source, repo_name=None):
+    """
+    Render plain text with revision hashes and issue references urlified
+    and with @mention highlighting.
+    """
+    s = urlify_text(source, repo_name=repo_name)
+    return literal('<div class="formatted-fixed">%s</div>' % s)
--- a/kallithea/model/notification.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/model/notification.py	Mon Nov 09 17:13:33 2020 +0100
@@ -33,6 +33,7 @@
 from tg import tmpl_context as c
 from tg.i18n import ugettext as _
 
+from kallithea.lib import webutils
 from kallithea.lib.utils2 import fmt_date
 from kallithea.model import async_tasks, db
 
@@ -65,7 +66,6 @@
         :param with_email: send email with this notification
         :param email_kwargs: additional dict to pass as args to email template
         """
-        import kallithea.lib.helpers as h
         email_kwargs = email_kwargs or {}
         if recipients and not getattr(recipients, '__iter__', False):
             raise Exception('recipients must be a list or iterable')
@@ -103,7 +103,7 @@
         # this is passed into template
         created_on = fmt_date(datetime.datetime.now())
         html_kwargs = {
-                  'body': None if body is None else h.render_w_mentions(body, repo_name),
+                  'body': None if body is None else webutils.render_w_mentions(body, repo_name),
                   'when': created_on,
                   'user': created_by_obj.username,
                   }
--- a/kallithea/model/repo.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/model/repo.py	Mon Nov 09 17:13:33 2020 +0100
@@ -140,8 +140,7 @@
                            cs_cache.get('message'))
 
         def desc(desc):
-            import kallithea.lib.helpers as h
-            return h.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
+            return webutils.urlify_text(desc, truncate=80, stylize=c.visual.stylify_metalabels)
 
         def state(repo_state):
             return _render("repo_state", repo_state)
--- a/kallithea/tests/functional/test_files.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/tests/functional/test_files.py	Mon Nov 09 17:13:33 2020 +0100
@@ -3,7 +3,7 @@
 import mimetypes
 import posixpath
 
-import kallithea.lib.helpers
+from kallithea.lib import webutils
 from kallithea.model import db, meta
 from kallithea.tests import base
 from kallithea.tests.fixture import Fixture
@@ -96,7 +96,7 @@
     def test_file_source(self):
         # Force the global cache to be populated now when we know the right .ini has been loaded.
         # (Without this, the test would fail.)
-        kallithea.lib.helpers._urlify_issues_f = None
+        webutils._urlify_issues_f = None
         self.log_user()
         response = self.app.get(base.url(controller='files', action='index',
                                     repo_name=base.HG_REPO,
--- a/kallithea/tests/other/test_libs.py	Mon Nov 09 16:42:43 2020 +0100
+++ b/kallithea/tests/other/test_libs.py	Mon Nov 09 17:13:33 2020 +0100
@@ -111,7 +111,6 @@
         assert asbool(str_bool) == expected
 
     def test_mention_extractor(self):
-        from kallithea.lib.webutils import extract_mentioned_usernames
         sample = (
             "@first hi there @world here's my email username@example.com "
             "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
@@ -123,7 +122,7 @@
         expected = set([
             '2one_more22', 'first', 'lukaszb', 'one', 'one_more22', 'UPPER', 'cAmEL', 'john',
             'marian.user', 'marco-polo', 'marco_polo', 'world'])
-        assert expected == set(extract_mentioned_usernames(sample))
+        assert expected == set(webutils.extract_mentioned_usernames(sample))
 
     @base.parametrize('age_args,expected', [
         (dict(), 'just now'),
@@ -197,7 +196,7 @@
             "[requires => url] [lang => python] [just a tag]"
             "[,d] [ => ULR ] [obsolete] [desc]]"
         )
-        res = h.urlify_text(sample, stylize=True)
+        res = webutils.urlify_text(sample, stylize=True)
         assert '<div class="label label-meta" data-tag="tag">tag</div>' in res
         assert '<div class="label label-meta" data-tag="obsolete">obsolete</div>' in res
         assert '<div class="label label-meta" data-tag="stale">stale</div>' in res
@@ -315,7 +314,7 @@
         with mock.patch('kallithea.lib.webutils.UrlGenerator.__call__',
             lambda self, name, **kwargs: dict(changeset_home='/%(repo_name)s/changeset/%(revision)s')[name] % kwargs,
         ):
-            assert h.urlify_text(sample, 'repo_name') == expected
+            assert webutils.urlify_text(sample, 'repo_name') == expected
 
     @base.parametrize('sample,expected,url_', [
       ("",
@@ -370,7 +369,7 @@
         with mock.patch('kallithea.lib.webutils.UrlGenerator.__call__',
             lambda self, name, **kwargs: dict(changeset_home='/%(repo_name)s/changeset/%(revision)s')[name] % kwargs,
         ):
-            assert h.urlify_text(sample, 'repo_name', stylize=True) == expected
+            assert webutils.urlify_text(sample, 'repo_name', stylize=True) == expected
 
     @base.parametrize('sample,expected', [
       ("deadbeefcafe @mention, and http://foo.bar/ yo",
@@ -383,7 +382,7 @@
         with mock.patch('kallithea.lib.webutils.UrlGenerator.__call__',
             lambda self, name, **kwargs: dict(changeset_home='/%(repo_name)s/changeset/%(revision)s')[name] % kwargs,
         ):
-            assert h.urlify_text(sample, 'repo_name', link_='#the-link') == expected
+            assert webutils.urlify_text(sample, 'repo_name', link_='#the-link') == expected
 
     @base.parametrize('issue_pat,issue_server,issue_sub,sample,expected', [
         (r'#(\d+)', 'http://foo/{repo}/issue/\\1', '#\\1',
@@ -471,9 +470,9 @@
             'issue_sub': issue_sub,
         }
         # force recreation of lazy function
-        with mock.patch('kallithea.lib.helpers._urlify_issues_f', None):
+        with mock.patch('kallithea.lib.webutils._urlify_issues_f', None):
             with mock.patch('kallithea.CONFIG', config_stub):
-                assert h.urlify_text(sample, 'repo_name') == expected
+                assert webutils.urlify_text(sample, 'repo_name') == expected
 
     @base.parametrize('sample,expected', [
         ('abc X5', 'abc <a class="issue-tracker-link" href="http://main/repo_name/main/5/">#5</a>'),
@@ -503,9 +502,9 @@
             'issue_server_link_absent_prefix': r'http://failmore/{repo}/\1',
         }
         # force recreation of lazy function
-        with mock.patch('kallithea.lib.helpers._urlify_issues_f', None):
+        with mock.patch('kallithea.lib.webutils._urlify_issues_f', None):
             with mock.patch('kallithea.CONFIG', config_stub):
-                assert h.urlify_text(sample, 'repo_name') == expected
+                assert webutils.urlify_text(sample, 'repo_name') == expected
 
     @base.parametrize('test,expected', [
       ("", None),