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