Implement custom keyword map templates in hgrc kwmap-templates
authorChristian Ebert <blacktrash@gmx.net>
Sun, 14 Jan 2007 12:56:52 +0100
branchkwmap-templates
changeset 95 9e4cbe64fb4f
parent 93 9f70f953dd3b
child 97 9353e7ce6d9b
Implement custom keyword map templates in hgrc More expensive than hardcoded keywords. But, of course, more flexible. ATM templates can only be set in hgrc, and perhaps this is enough.
hgkw/keyword.py
--- a/hgkw/keyword.py	Sat Jan 13 13:27:13 2007 +0100
+++ b/hgkw/keyword.py	Sun Jan 14 12:56:52 2007 +0100
@@ -11,38 +11,55 @@
 files (like LaTeX packages), that are mostly addressed to an audience
 not running a version control system.
 
-Supported $keywords$ and their $keyword: substition $ are:
+You can either use the default cvs-like keywords or provide your
+own in hgrc.
+
+Default $keywords$ and their $keyword: substition $ are:
     Revision: changeset id
-    Author:   short username
+    Author:   username
     Date:     %Y/%m/%d %H:%M:%S [UTC]
     RCSFile:  basename,v
     Source:   /path/to/basename,v
-    Id:       basename,v csetid %Y/%m/%d %H:%M:%S shortname
-    Header:   /path/to/basename,v csetid %Y/%m/%d %H:%M:%S shortname
+    Id:       basename,v csetid %Y/%m/%d %H:%M:%S username
+    Header:   /path/to/basename,v csetid %Y/%m/%d %H:%M:%S username
 
 Simple setup in hgrc:
 
     # enable extension
-    hgext.keyword =
-    # or, if script not in hgext folder:
-    # hgext.keyword = /full/path/to/script
+    hgext.keyword = /full/path/to/script
+    # or, if script in hgext folder:
+    # hgext.keyword =
     
     # filename patterns for expansion are configured in this section
     [keyword]
     **.py = expand
     ...
+    # in case you prefer you own keyword maps over the cvs-like defaults:
+    [keywordmaps]
+    hgdate = {date|rfc822date}
+    lastlog = {desc}
+    checked in by = {author}
+    ...
 '''
 
-from mercurial import context, util
+from mercurial import cmdutil, templater, util
 import os.path, re, sys, time
 
-re_kw = re.compile(
-        r'\$(Id|Header|Author|Date|Revision|RCSFile|Source)[^$]*?\$')
+deftemplates = {
+        'Revision': '{node|short}',
+        'Author': '{author|user}',
+        'Date': '{date|utcdate}',
+        'RCSFile': '{file|basename},v',
+        'Source': '{root}/{file},v',
+        'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
+        'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
+        }
 
-def kwexpand(mobj, kwfctx):
-    '''Called by kwfilelog.read and pretxnkw.
-    Expands keywords according to file context.'''
-    return '$%s: %s $' % (mobj.group(1), kwfctx.expand(mobj.group(1)))
+def utcdate(date):
+    '''Returns hgdate in cvs-like UTC format.'''
+    return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
+
+templater.common_filters['utcdate'] = utcdate
 
 def kwfmatches(ui, repo, files):
     '''Selects candidates for keyword substitution
@@ -60,41 +77,38 @@
                 break
     return candidates
 
-def utcdate(date):
-    '''Returns hgdate in cvs-like UTC format.'''
-    return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
 
-
-class kwfilectx(context.filectx):
+class kwtemplater(object):
     '''
-    Provides keyword expansion functions based on file context.
+    Sets up keyword templates, corresponding keyword regex, and
+    provides keyword expansion function.
     '''
-    def __init__(self, repo, path, changeid=None, fileid=None, filelog=None):
-        context.filectx.__init__(self, repo, path, changeid, fileid, filelog)
-    def Revision(self):
-        return str(self.changectx())
-    def Author(self):
-        return util.shortuser(self.user())
-    def Date(self):
-        return utcdate(self.date())
-    def RCSFile(self):
-        return os.path.basename(self._path)+',v'
-    def Source(self):
-        return self._repo.wjoin(self._path)+',v'
-    def Header(self):
-        return ' '.join(
-                [self.Source(), self.Revision(), self.Date(), self.Author()])
-    def Id(self):
-        return ' '.join(
-                [self.RCSFile(), self.Revision(), self.Date(), self.Author()])
-    def expand(self, kw):
-        '''Called from kwexpand, evaluates keyword.'''
-        return eval('self.%s()' % kw)
+    def __init__(self, ui, repo, node=None):
+        self.ui = ui
+        self.repo = repo
+        self.node = node 
+        templates = {}
+        for k, v in self.ui.configitems('keywordmaps'):
+            templates[k] = v
+        self.templates = templates or deftemplates
+        self.re_kw = re.compile(r'\$(%s)[^$]*?\$' %
+                '|'.join(re.escape(k) for k in self.templates.keys()))
+        self.t = cmdutil.changeset_templater(self.ui, self.repo,
+                False, '', False)
+
+    def expand(self, mobj, path):
+        kw = mobj.group(1)
+        template = templater.parsestring(self.templates[kw], quoted=False)
+        self.t.use_template(template)
+        self.ui.pushbuffer()
+        self.t.show(changenode=self.node,
+                changes=self.repo.changelog.read(self.node),
+                root=self.repo.root, file=path)
+        return '$%s: %s $' % (kw, self.ui.popbuffer())
 
 
 def reposetup(ui, repo):
-    from mercurial import filelog, revlog
-
+    from mercurial import context, filelog, revlog
     if not repo.local():
         return
 
@@ -114,6 +128,7 @@
             super(kwfilelog, self).__init__(opener, path, defversion)
             self._repo = repo
             self._path = path
+            self.kwt = kwtemplater(ui, self._repo)
 
         def iskwcandidate(self, data):
             '''Decides whether to act on keywords.'''
@@ -124,22 +139,24 @@
             '''Substitutes keywords when reading filelog.'''
             data = super(kwfilelog, self).read(node)
             if self.iskwcandidate(data):
-                kwfctx = kwfilectx(self._repo, self._path,
-                            fileid=node, filelog=self)
-                return re_kw.sub(lambda m: kwexpand(m, kwfctx), data)
+                c = context.filectx(self._repo, self._path,
+                         fileid=node, filelog=self)
+                self.kwt.node = c.node()
+                return self.kwt.re_kw.sub(lambda m:
+                        self.kwt.expand(m, self._path), data)
             return data
 
         def add(self, text, meta, tr, link, p1=None, p2=None):
             '''Removes keyword substitutions when adding to filelog.'''
             if self.iskwcandidate(text):
-                text = re_kw.sub(r'$\1$', text)
+                text = self.kwt.re_kw.sub(r'$\1$', text)
             return super(kwfilelog, self).add(text,
                     meta, tr, link, p1=p1, p2=p2)
 
         def cmp(self, node, text):
             '''Removes keyword substitutions for comparison.'''
             if self.iskwcandidate(text):
-                text = re_kw.sub(r'$\1$', text)
+                text = self.kwt.re_kw.sub(r'$\1$', text)
             return super(kwfilelog, self).cmp(node, text)
 
     filelog.filelog = kwfilelog
@@ -167,7 +184,7 @@
     # above line for backwards compatibility; can be changed to
     #   from mercurial.i18n import _
     # some day
-    from mercurial import cmdutil, commands
+    from mercurial import commands
 
     if hooktype != 'pretxncommit':
         return True
@@ -178,12 +195,15 @@
 
     files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts)
     modified, added = repo.status(files=files, match=match)[:2]
+    candidates = kwfmatches(ui, repo, modified+added)
+    if not candidates:
+        return False
 
-    for f in kwfmatches(ui, repo, modified+added):
+    kwt = kwtemplater(ui, repo, node=repo.changelog.tip())
+    for f in candidates:
         data = repo.wfile(f).read()
         if not util.binary(data):
-            kwfctx = kwfilectx(repo, f, changeid=args['node'])
-            data, kwct = re_kw.subn(lambda m: kwexpand(m, kwfctx), data)
+            data, kwct = kwt.re_kw.subn(lambda m: kwt.expand(m, f), data)
             if kwct:
                 ui.debug(_('overwriting %s expanding keywords\n' % f))
                 repo.wfile(f, 'w').write(data)