changeset 6236:39a59e6915bb

helpers: refactor and optimize urlify_issues Avoid parsing the configuration and compiling regexps every time a string is processed. Instead, the configuration is parsed once and turned into a function that apply the compiled regexps in a chain. A next iteration can perhaps run them in parallel and integrate with the general urlify machinery.
author Mads Kiilerich <madski@unity3d.com>
date Sun, 25 Sep 2016 17:21:07 +0200
parents 1903d3813ac4
children e55041bb3585
files kallithea/lib/helpers.py kallithea/tests/functional/test_files.py
diffstat 2 files changed, 56 insertions(+), 50 deletions(-) [+]
line wrap: on
line diff
--- a/kallithea/lib/helpers.py	Thu Sep 15 18:08:54 2016 +0200
+++ b/kallithea/lib/helpers.py	Sun Sep 25 17:21:07 2016 +0200
@@ -1352,63 +1352,65 @@
     return ''.join(links)
 
 
-def _urlify_issues_replace_f(repo_name, ISSUE_SERVER_LNK, ISSUE_PREFIX):
-    def urlify_issues_replace(match_obj):
-        pref = ''
-        if match_obj.group().startswith(' '):
-            pref = ' '
-
-        issue_id = ''.join(match_obj.groups())
-        issue_url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
-        issue_url = issue_url.replace('{repo}', repo_name)
-        issue_url = issue_url.replace('{repo_name}', repo_name.split(URL_SEP)[-1])
-
-        return (
-            '%(pref)s<a class="%(cls)s" href="%(url)s">'
-            '%(issue-prefix)s%(id-repr)s'
-            '</a>'
-            ) % {
-             'pref': pref,
-             'cls': 'issue-tracker-link',
-             'url': issue_url,
-             'id-repr': issue_id,
-             'issue-prefix': ISSUE_PREFIX,
-             'serv': ISSUE_SERVER_LNK,
-            }
-    return urlify_issues_replace
+# 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):
-    from kallithea import CONFIG as conf
+    """Urlify issue references according to .ini configuration"""
+    global _urlify_issues_f
+    if _urlify_issues_f is None:
+        from kallithea import CONFIG
+        assert CONFIG['sqlalchemy.url'] # make sure config has been loaded
+
+        # Build chain of urlify functions, starting with not doing any transformation
+        tmp_urlify_issues_f = lambda s: s
 
-    # allow multiple issue servers to be used
-    valid_indices = [
-        x.group(1)
-        for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
-        if x and 'issue_server_link%s' % x.group(1) in conf
-        and 'issue_prefix%s' % x.group(1) in conf
-    ]
-
-    if valid_indices:
-        log.debug('found issue server suffixes `%s` during valuation of: %s',
-                  ','.join(valid_indices), newtext)
+        issue_pat_re = re.compile(r'issue_pat(.*)')
+        for k in CONFIG.keys():
+            # 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 = CONFIG.get(k)
+            issue_server_link = CONFIG.get('issue_server_link%s' % suffix)
+            issue_prefix = CONFIG.get('issue_prefix%s' % suffix)
+            if issue_pat and issue_server_link and issue_prefix:
+                log.debug('issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix)
+            else:
+                log.error('skipping incomplete issue pattern %r: %r -> %r %r', suffix, issue_pat, issue_server_link, issue_prefix)
+                continue
 
-    for pattern_index in valid_indices:
-        ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
-        ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
-        ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
+            # Wrap tmp_urlify_issues_f with substitution of this pattern, while making sure all loop variables (and compiled regexpes) are bound
+            issue_re = re.compile(issue_pat)
+            def issues_replace(match_obj,
+                               issue_server_link=issue_server_link, issue_prefix=issue_prefix):
+                leadingspace = ' ' if match_obj.group().startswith(' ') else ''
+                issue_id = ''.join(match_obj.groups())
+                issue_url = issue_server_link.replace('{id}', issue_id)
+                issue_url = issue_url.replace('{repo}', repo_name)
+                issue_url = issue_url.replace('{repo_name}', repo_name.split(URL_SEP)[-1])
+                return (
+                    '%(leadingspace)s<a class="issue-tracker-link" href="%(url)s">'
+                    '%(issue-prefix)s%(id-repr)s'
+                    '</a>'
+                    ) % {
+                     'leadingspace': leadingspace,
+                     'url': issue_url,
+                     'id-repr': issue_id,
+                     'issue-prefix': issue_prefix,
+                     'serv': issue_server_link,
+                    }
+            tmp_urlify_issues_f = (lambda s,
+                                          issue_re=issue_re, issues_replace=issues_replace, chain_f=tmp_urlify_issues_f:
+                                   issue_re.sub(issues_replace, chain_f(s)))
 
-        log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s',
-                  pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
-                  ISSUE_PREFIX)
-
-        URL_PAT = re.compile(ISSUE_PATTERN)
+        # Set tmp function globally - atomically
+        _urlify_issues_f = tmp_urlify_issues_f
 
-        urlify_issues_replace = _urlify_issues_replace_f(repo_name, ISSUE_SERVER_LNK, ISSUE_PREFIX)
-        newtext = URL_PAT.sub(urlify_issues_replace, newtext)
-        log.debug('processed prefix:`%s` => %s', pattern_index, newtext)
-
-    return newtext
+    return _urlify_issues_f(newtext)
 
 
 def render_w_mentions(source, repo_name=None):
--- a/kallithea/tests/functional/test_files.py	Thu Sep 15 18:08:54 2016 +0200
+++ b/kallithea/tests/functional/test_files.py	Sun Sep 25 17:21:07 2016 +0200
@@ -92,6 +92,10 @@
             response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
 
     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.)
+        import kallithea.lib.helpers
+        kallithea.lib.helpers._urlify_issues_f = None
         self.log_user()
         response = self.app.get(url(controller='files', action='index',
                                     repo_name=HG_REPO,