diff extensions/kwarchive.py @ 33:dc8db9693262

kwarchive expands keyword on given files (.hgkwarchive with generic Mercurial patterns)
author Franz Glasner <hg@dom66.de>
date Sat, 11 Nov 2017 23:05:37 +0100
parents cfa53f8a6607
children 7c4addd60935
line wrap: on
line diff
--- a/extensions/kwarchive.py	Sat Nov 11 17:17:00 2017 +0100
+++ b/extensions/kwarchive.py	Sat Nov 11 23:05:37 2017 +0100
@@ -1,5 +1,27 @@
 # -*- coding: utf-8 -*-
 """archive with keyword expansion into selected files
+
+The patterns of files to to expanded are configured in an versioned
+``.hgkwarchive`` configuration file found in the root of the working
+directory.
+
+The ``.hgkwarchive`` file uses the same syntax as all other Mercurial
+configuration files.
+
+The ``[patterns]`` section specifies which files should have the keywords
+expanded.
+
+
+Example::
+
+   [patterns]
+    # expand keywords in every python file except those matching "x*"
+    **.py =
+    x*    = NO
+
+A non-existing ``.hgkwarchive`` file deactivates keyword expansion as does
+an empty ``[patterns]`` section.
+
 """
 
 from __future__ import absolute_import
@@ -11,10 +33,11 @@
 
 
 import os
+import itertools
 
 from mercurial.i18n import _
-from mercurial import (archival, commands, cmdutil, error, pycompat,
-                       scmutil, util)
+from mercurial import (archival, commands, config, cmdutil, error, match,
+                       pycompat, scmutil, templatefilters, util)
 
 
 testedwith = "4.3.2"
@@ -101,7 +124,14 @@
     # expands keywords
     #
     for ac in ("fileit", "tarit", "zipit",):
-        patch_archiver_class(ac)
+        patch_archiver_class(
+            ac,
+            make_keyword_filter(
+                ui,
+                repo,
+                ctx,
+                ac,
+                archival.tidyprefix(dest, kind, prefix)))
 
     archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
                      matchfn, prefix, subrepos=opts.get('subrepos'))
@@ -109,20 +139,80 @@
     # XXX FIXME: Should the original methods be restored here?
 
 
-def patch_archiver_class(archivername):
+def patch_archiver_class(archivername, filter):
     """Patch an archiver class and return the original unbound method"""
 
     archiver_class = getattr(archival, archivername)
     orig_addfile = getattr(archiver_class, "addfile")
 
     def new_addfile(self, name, mode, isline, data):
-        print "==> ARCHIVER(%s)" % archivername, name, mode, isline
-        return orig_addfile(self, name, mode, isline, data)
+        return orig_addfile(self, name, mode, isline, filter(name, data))
 
     setattr(archiver_class, "addfile", new_addfile)
     return orig_addfile
 
 
-def filter2(s, params, ui, **kwargs):
-    print "=============>", repr(params), repr(kwargs)
-    return s
+def make_keyword_filter(ui, repo, ctx, archive_class, prefix,
+                        hgpath="default"):
+
+    # node specific keywords
+    keywords = {
+        "HGpath": ui.config("paths", hgpath) or repo.root,
+        "HGrevision": ctx.hex(),
+        "Revision": templatefilters.short(ctx.hex()),
+        "Author": templatefilters.person(ctx.user()),
+        "Date": templatefilters.isodatesec(ctx.date()),
+    }
+    # .hgkwarchive
+    try:
+        kwconfig = ctx[".hgkwarchive"]
+    except (IOError, LookupError):
+        def _filter(name, data):
+            return data
+    else:
+        cfg = config.config()
+        cfg.parse(".kwarchive", kwconfig.data())
+        include = []
+        exclude = []
+        patterns = []
+        if cfg.items("patterns"):
+            for pattern, style in cfg.items("patterns"):
+                style = style.upper()
+                if style in ("YES", "INCLUDE",):
+                    include.append(pattern)
+                if style in ("NO", "EXCLUDE",):
+                    exclude.append(pattern)
+                else:
+                    patterns.append(pattern)
+            matcher = match.match(repo.root, '', patterns=patterns,
+                                  include=include, exclude=exclude)
+        else:
+            matcher = match.never(repo.root, '')
+
+        def _filter(name, data):
+            real_name = name
+            if archive_class != "fileit":
+                if prefix:
+                    assert name.startswith(prefix)
+                    real_name = name[len(prefix):]
+            if not matcher(real_name):
+                return data
+            # file specific keywords
+            file_keywords = {
+                "HGsource": keywords["HGpath"] + '/' + real_name,
+                "Source": real_name,
+                "File": templatefilters.basename(real_name),
+                "Header": "%s %s %s %s" % (real_name, keywords["Revision"], keywords["Date"], keywords["Author"]),
+                "HGid": "%s %s %s %s" % (keywords["HGpath"] + '/' + real_name, keywords["Revision"], keywords["Date"], keywords["Author"]),
+                "HGheader": "%s %s %s %s" % (keywords["HGpath"] + '/' + real_name, keywords["HGrevision"], keywords["Date"], keywords["Author"]),
+                "Id": "%s %s %s %s" % (templatefilters.basename(real_name), keywords["Revision"], keywords["Date"], keywords["Author"]),
+            }
+            # This prevents unwanted keyword expansion here
+            _MARKER = '$'
+            for kw, value in itertools.chain(keywords.items(), file_keywords.items()):
+                filekw = _MARKER + kw + _MARKER
+                filevalue = "%s%s: %s %s" % (_MARKER, kw, value, _MARKER)
+                data = data.replace(filekw, filevalue)
+            return data
+
+    return _filter