filtering changelog messages - potention extension for hgweb?

Jan Capek jen at jikos.cz
Thu Dec 4 01:59:15 UTC 2008


Hi Arne,

based on the comments that came from the mailing list I pretty much gave 
up on the original draft code but came up with an improvementinterhg 
extension that allows specifying a separate filter (e.g. urllnk) for the 
string replacement. For the impatient, attached is a 'renewed' interhg 
patch (applies to the interhg in 1.0.x mercurial). I would like to post 
for approval in the following days. A sample hgrc is in the text below:

[extensions]
# enable filtering of changelog messages. This is to convert any trac
# ticket references to actual URL's - requires a modifed web template
interhg=/srv/hg/extensions/interhg.py

[interhg]
issues = s!(#)(\d+)!<a href="http://trac.testing.sit/ticket/\2">\1\2</a>!
#   bugzilla = s!((?:bug|b=|(?=#?\d{4,}))(?:\s*#?)(\d+))!<a..=\2">\1</a>!i
#   boldify = s/(^|\s)#(\d+)\b/ <b>#\2<\/b>/

[interhg_opts]
filtername = urllnk
# override =



On Thu, 4 Dec 2008, Arne Babenhauserheide wrote:

> Hi, 
> 
> Did I understand it correctly that this extension turns bug IDs into links to 
> the bugtracker? 
> 
> From what I see in its docstring it allows for replacing any kind of input 
> (regexp based) with a string built from a static string, a base url and some 
> part of the input. 
> 
> And it sounds quite interesting to me (but might have been drowned by the 1.1 
> release). 
> 
> And I like it that the code is short and clean. 
> 
> Can you post an example for a .hgrc entry which does a simple replacement? 
> 
> Best wishes, 
> Arne
> 
> Am Montag 01 Dezember 2008 22:14:17 schrieb Jan Capek:
> > Hi,
> >
> > I have come across an hgweb usecase where I needed a filtering extension:
> >
> > Use case
> > --------
> > - a mercurial repository is being used with trac for issue tracking. The
> >   repository has hooks to check that changelog messages contain valid bug
> >   ID's (similar to bugzilla extension)
> >
> > - the bug ID's that show up in changelog messages should be
> >   hyperlinks to the bug tracking system when using the hgweb interface
> >
> >
> > Implementation
> > --------------
> > To implement the above scenario a new filter extension has been created.
> > This extension is configurable via hgrc ([urllnk] section).
> >
> >
> >
> > I am attaching the extension itself and an example patch of the gitweb
> > style to show its use.
> >
> > Would this extension be of any interest to mercurial community, is it
> > possible to post it on the selenic.com wiki?
> >
> >
> > Thanks,
> >
> > Jan
> 
> -- 
> -- My stuff: http://draketo.de - stories, songs, poems, programs and stuff :)
> -- Infinite Hands: http://infinite-hands.draketo.de - singing a part of the 
> history of free software.
> -- Ein Würfel System: http://1w6.org - einfach saubere (Rollenspiel-) Regeln.
> 
> -- PGP/GnuPG: http://draketo.de/inhalt/ich/pubkey.txt
> 
-------------- next part --------------
Index: url-link-filter/interhg.py
===================================================================
--- url-link-filter.orig/interhg.py	2008-12-03 12:51:59.000000000 +0100
+++ url-link-filter/interhg.py	2008-12-03 13:58:36.000000000 +0100
@@ -4,6 +4,7 @@
 #
 # Contributor(s):
 #   Edward Lee <edward.lee at engineering.uiuc.edu>
+#   Jan Capek <jen at jikos.cz>
 #
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
@@ -24,60 +25,269 @@
 #   boldify = s/(^|\s)#(\d+)\b/ <b>#\2<\/b>/
 #
 # Add any number of names and patterns to match
+#
+# By default the extension extends the functionality of the 'escape'
+# filter. In order to extend the functionality of another filter,
+# there is an additional configuration section:
+#
+# [interhg_opts]
+# filtername = urllnk
+#
+# If an existing filter is to be explicitely overridden, there is an
+# additional configuration option. For example to redefine
+# e.g. 'escape' filter:
+#
+# [interhg_opts]
+# filtername = escape
+# override =
+
+
 
 import re
 from mercurial.hgweb import hgweb_mod
 from mercurial import templatefilters
 
-orig_escape = templatefilters.filters["escape"]
 
-interhg_table = []
 
-def interhg_escape(x):
-    escstr = orig_escape(x)
-    for regexp, format in interhg_table:
-        escstr = regexp.sub(format, escstr)
-    return escstr
-
-templatefilters.filters["escape"] = interhg_escape
-
-orig_refresh = hgweb_mod.hgweb.refresh
-
-def interhg_refresh(self):
-    interhg_table[:] = []
-    for key, pattern in self.repo.ui.configitems('interhg'):
-        # grab the delimiter from the character after the "s"
-        unesc = pattern[1]
-        delim = re.escape(unesc)
-
-        # identify portions of the pattern, taking care to avoid escaped
-        # delimiters. the replace format and flags are optional, but delimiters
-        # are required.
-        match = re.match(r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
-                         % (delim, delim, delim), pattern)
-        if not match:
-            self.repo.ui.warn("interhg: invalid pattern for %s: %s\n"
-                              % (key, pattern))
-            continue
-
-        # we need to unescape the delimiter for regexp and format
-        delim_re = re.compile(r'(?<!\\)\\%s' % delim)
-        regexp = delim_re.sub(unesc, match.group(1))
-        format = delim_re.sub(unesc, match.group(2))
-
-        # the pattern allows for 6 regexp flags, so set them if necessary
-        flagin = match.group(3)
-        flags = 0
-        if flagin:
-            for flag in flagin.upper():
-                flags |= re.__dict__[flag]
-
-        try:
-            regexp = re.compile(regexp, flags)
-            interhg_table.append((regexp, format))
-        except re.error:
-            self.repo.ui.warn("interhg: invalid regexp for %s: %s\n"
-                              % (key, regexp))
-    return orig_refresh(self)
+class InterHgFilter(object):
+    """This class represents a generic interhg filter.
+
+    This filter applies all text replacement expressions specified by
+    the user to a string being filtered. User may specify arbitray
+    filtername that is not used
+
+    """
+    def __init__(self, ui, repo, filtername):
+        """Initializes regular expressions to match user tags.
+
+        A new filter is registered with mercurial and all the user
+        specified regular expressions are parsed.
+
+        Registers a new filter with mercurial and processes a
+
+        @param self
+        @param ui - user interface of the mercurial
+        @param repo - current mercurial repository
+        @param filtername - name of the filter that is to be registered
+        """
+        self.ui = ui
+        self.interhg_table = []
+        self.name = filtername
+        for key, pattern in self.ui.configitems('interhg'):
+            # grab the delimiter from the character after the "s"
+            unesc = pattern[1]
+            delim = re.escape(unesc)
+            # print 'Processing pattern %s' %pattern
+            # identify portions of the pattern, taking care to avoid escaped
+            # delimiters. the replace format and flags are optional, but delimiters
+            # are required.
+            match = re.match(r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
+                             % (delim, delim, delim), pattern)
+            if not match:
+                self.ui.warn("interhg: invalid pattern for %s: %s\n"
+                                  % (key, pattern))
+                continue
+
+            # we need to unescape the delimiter for regexp and format
+            delim_re = re.compile(r'(?<!\\)\\%s' % delim)
+            regexp = delim_re.sub(unesc, match.group(1))
+            format = delim_re.sub(unesc, match.group(2))
+
+            # the pattern allows for 6 regexp flags, so set them if necessary
+            flagin = match.group(3)
+            flags = 0
+            if flagin:
+                for flag in flagin.upper():
+                    flags |= re.__dict__[flag]
+
+            try:
+                regexp = re.compile(regexp, flags)
+                self.interhg_table.append((regexp, format))
+            except re.error:
+                self.ui.warn("interhg: invalid regexp for %s: %s\n"
+                                  % (key, regexp))
+
+        # conditionally register this filter
+        if self._filter_registered(filtername):
+            self.ui.warn(('interhg: filter %s already registered '+
+                         'cannot override!\n') % filtername)
+        else:
+            templatefilters.filters[filtername] = self
+
+    def remove(self):
+        """Removes the filter and recovers the filter table.
+        This method is needed when configuration is being reloaded
+
+        @param self
+        """
+        # the filter is replaced with an empty lambda expression
+        self._recover_table(lambda a: a)
+
+
+
+    def __call__(self, text):
+        """The object implements a callable interface to perform filtering.
+
+        @param self
+        @param text - text to be filtered
+        """
+        return self._filter(text)
+
+
+    def _filter_registered(self, filtername):
+        """Checks whether a filter for interhg has been registered
+
+        @param self
+        @param filtername - filter to be checked
+        """
+        # this is the correct version:
+        return filtername in templatefilters.filters
+
+    def _recover_table(self, callable):
+        """Recovers the filter entry in the filter table.
+
+        @param self
+        @param callable - callable object used when replacing the
+        filter in the filter table
+        """
+        templatefilters.filters[self.name] = callable
+
+    def _filter(self, text):
+       """Performs the actual text filtering
+
+       @param self
+       @param text - text to be filtered
+       """
+       for regexp, format in self.interhg_table:
+           text = regexp.sub(format, text)
+       return text
+
+
+class InterHgExtendFilter(InterHgFilter):
+    """This class allows extending an existing filter.
+
+    The original filter implementation is called first - this is to
+    maintain the same behavior as the original interhg
+    extension. Originally, it was extending the escape filter
+    functionality.
+    """
+
+    def __init__(self, ui, repo, filtername):
+        """
+        The original filter method is saved so that the original
+        filter is applied before the interhg filter.
+
+        Registers a new filter with mercurial.
+
+        @param self
+        @param ui - user interface of the mercurial
+        @param repo - current mercurial repository
+        @param filtername - name of the filter that is to be extended
+        """
+        if not InterHgFilter._filter_registered(self, filtername):
+            self.ui.warn(("interhg: filter '%s' not registered, ignoring extension\n")
+                              % filtername)
+            # empty filter so that further processing requires no
+            # special case handling
+            self.orig_filter = lambda t: t
+
+        else:
+            self.orig_filter = templatefilters.filters[filtername]
+
+        InterHgFilter.__init__(self, ui, repo, filtername)
+
+    def remove(self):
+        """Removes the filter and recovers the filter table.
+
+        This method is needed when configuration is being reloaded.
+
+        @param self
+        """
+        self._recover_table(self.orig_filter)
+
+    def _filter_registered(self, filtername):
+       """Overrides parent method so that every filter passes
+
+       Any filter is reported as 'unregistered' since this class
+       always calls the original filter method.
+
+       @param self
+       @param filtername - filter to be checked
+
+       @return always false
+       """
+       return False
+
+
+    def _filter(self, text):
+       """Performs the actual text filtering.
+
+       The original filter is invoked, the rest of the processing is
+       delegated to the parent
+
+       @param self
+       @param text - text to be filtered
+       """
+       text = self.orig_filter(text)
+       return InterHgFilter._filter(self, text)
+
+
+class InterHg(object):
+    """This is the main class responsible for the filter setup.
+
+    It decide whether a filter is to extended or overriden based based
+    on configuration options. The default is to extend the 'escape'
+    filter.
+
+    The class instantiates a proper filter based on user options.
+
+    It also keeps track of the current filter that has been
+    setup. This is needed for uninstalling the filter when the
+    configuration is reloaded (e.g. on hgweb refresh)
+    """
+
+    # current filter instance
+    filter = None
+
+    def setup(cls, ui, repo):
+        """Extension setup - removes an existing filter
+
+        Since the setup may be invoked multiple times (hgweb does
+        that), it is necessary to remove the previously setup filter
+        if any.
+
+        @param cls
+        @param ui - user interface of the mercurial
+        @param repo - current mercurial repository
+        """
+        if InterHg.filter:
+            InterHg.filter.remove()
+            InterHg.filter = None
+
+        # default setup - escap filter is being extended
+        filtername = ui.config('interhg_opts', 'filtername', 'escape')
+        filterclass = InterHgExtendFilter
+
+        # filter override when explicitely requested or filter doesn't
+        # exist yet
+        if (not filtername in templatefilters.filters or
+            ui.config('interhg_opts', 'filteroverride')):
+            filterclass = InterHgFilter
+
+        # instantiate the actual filter
+        InterHg.filter = filterclass(ui, repo, filtername)
+
+    setup = classmethod(setup)
+
+
+def reposetup(ui, repo):
+    InterHg.setup(ui, repo)
+
+
+# This refresh hook doesn't seem to be necessary when
+# def interhg_refresh(self):
+#     InterHg.setup(self.repo.ui, self.repo)
+#     return orig_refresh(self)
 
-hgweb_mod.hgweb.refresh = interhg_refresh
+# orig_refresh = hgweb_mod.hgweb.refresh
+# hgweb_mod.hgweb.refresh = interhg_refresh


More information about the Mercurial mailing list