(0.9.2compat) restructure based on wwread/wwrite methods as in default branch 0.9.2compat
authorChristian Ebert <blacktrash@gmx.net>
Wed, 30 Jan 2008 10:38:10 +0100
branch0.9.2compat
changeset 359 4ac7e51443fe
parent 352 a71e2086fe6e
child 360 ed06223f88fc
(0.9.2compat) restructure based on wwread/wwrite methods as in default branch
hgkw/keyword.py
tests/test-keyword
tests/test-keyword.out
--- a/hgkw/keyword.py	Fri Jan 25 21:42:52 2008 +0000
+++ b/hgkw/keyword.py	Wed Jan 30 10:38:10 2008 +0100
@@ -84,34 +84,14 @@
         Or, better, use bundle/unbundle to share changes.
 '''
 
-from mercurial import commands, cmdutil, context, fancyopts
-from mercurial import filelog, localrepo, revlog, templater, util
+from mercurial import commands, cmdutil, context, filelog, localrepo
+from mercurial import patch, revlog, templater, util
 from mercurial.node import *
 from mercurial.i18n import gettext as _
-import getopt, os.path, re, shutil, sys, tempfile, time
+import os.path, re, shutil, tempfile, time
 
 # backwards compatibility hacks
 
-try:
-    # cmdutil.parse moves to dispatch._parse in 18a9fbb5cd78
-    # also avoid name conflict with other dispatch package(s)
-    from mercurial.dispatch import _parse
-except ImportError:
-    try:
-        # commands.parse moves to cmdutil.parse in 0c61124ad877
-        _parse = cmdutil.parse
-    except AttributeError:
-        _parse = commands.parse
-
-def _wwrite(repo, f, data, mf):
-    '''Makes repo.wwrite backwards compatible.'''
-    # 656e06eebda7 removed file descriptor argument
-    # 67982d3ee76c added flags argument
-    try:
-        repo.wwrite(f, data, mf.flags(f))
-    except (AttributeError, TypeError):
-        repo.wwrite(f, data)
-
 def _normal(repo, files):
     '''Backwards compatible repo.dirstate.normal/update.'''
     # 6fd953d5faea introduced dirstate.normal()
@@ -121,6 +101,12 @@
     except AttributeError:
         repo.dirstate.update(files, 'n')
 
+def _link(repo, f):
+    try:
+        return repo._link(f)
+    except AttributeError:
+        return os.path.islink(repo.wjoin(f))
+
 def _pathto(repo, f, cwd=None):
     '''kwfiles behaves similar to status, using pathto since 78b6add1f966.'''
     try:
@@ -128,95 +114,13 @@
     except AttributeError:
         return f
 
-# commands.parse/cmdutil.parse returned nothing for
-# "hg diff --rev" before 88803a69b24a due to bug in fancyopts
-def _fancyopts(args, options, state):
-    '''Fixed fancyopts from a9b7e425674f.'''
-    namelist = []
-    shortlist = ''
-    argmap = {}
-    defmap = {}
-
-    for short, name, default, comment in options:
-        # convert opts to getopt format
-        oname = name
-        name = name.replace('-', '_')
-
-        argmap['-' + short] = argmap['--' + oname] = name
-        defmap[name] = default
-
-        # copy defaults to state
-        if isinstance(default, list):
-            state[name] = default[:]
-        elif callable(default):
-            print "whoa", name, default
-            state[name] = None
-        else:
-            state[name] = default
-
-        # does it take a parameter?
-        if not (default is None or default is True or default is False):
-            if short: short += ':'
-            if oname: oname += '='
-        if short:
-            shortlist += short
-        if name:
-            namelist.append(oname)
-
-    # parse arguments
-    opts, args = getopt.getopt(args, shortlist, namelist)
-
-    # transfer result to state
-    for opt, val in opts:
-        name = argmap[opt]
-        t = type(defmap[name])
-        if t is type(fancyopts):
-            state[name] = defmap[name](val)
-        elif t is type(1):
-            state[name] = int(val)
-        elif t is type(''):
-            state[name] = val
-        elif t is type([]):
-            state[name].append(val)
-        elif t is type(None) or t is type(False):
-            state[name] = True
-
-    # return unparsed args
-    return args
-
-fancyopts.fancyopts = _fancyopts
-
 
 commands.optionalrepo += ' kwdemo'
 
-# handle for external callers
-externalcall = None, None, {}
-
-def externalcmdhook(hgcmd, *args, **opts):
-    '''Hook for external callers to pass hg commands to keyword.
-
-    Caveat: hgcmd, args, opts are not checked for validity.
-    This is the responsibility of the caller.
-
-    hgmcd can be either the hg function object, eg diff or patch,
-    or its string represenation, eg 'diff' or 'patch'.'''
-    global externalcall
-    if not isinstance(hgcmd, str):
-        hgcmd = hgcmd.__name__.split('.')[-1]
-    externalcall = hgcmd, args, opts
-
-# hg commands that trigger expansion only when writing to working dir,
-# not when reading filelog, and unexpand when reading from working dir
-restricted = ('diff1', 'record',
-              'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
-
 def utcdate(date):
     '''Returns hgdate in cvs-like UTC format.'''
     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
 
-
-_kwtemplater = None
-
 class kwtemplater(object):
     '''
     Sets up keyword templates, corresponding keyword regex, and
@@ -232,13 +136,11 @@
         'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
     }
 
-    def __init__(self, ui, repo, inc, exc, hgcmd):
+    def __init__(self, ui, repo, inc, exc):
         self.ui = ui
         self.repo = repo
         self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
-        self.hgcmd = hgcmd
-        self.commitnode = None
-        self.path = ''
+        self.ctx = None
 
         kwmaps = self.ui.configitems('keywordmaps')
         if kwmaps: # override default templates
@@ -262,86 +164,78 @@
             return cmdutil.changeset_templater(self.ui, self.repo,
                                                False, None, '', False)
 
-    def substitute(self, node, data, subfunc):
-        '''Obtains file's changenode if commit node not given,
+    def substitute(self, path, data, node, subfunc):
+        '''Obtains file's changenode if node not given,
         and calls given substitution function.'''
-        if self.commitnode:
-            fnode = self.commitnode
-        else:
-            c = context.filectx(self.repo, self.path, fileid=node)
-            fnode = c.node()
+        if node is None:
+            # kwrepo.wwrite except when overwriting on commit
+            if self.ctx is None:
+                self.ctx = self.repo.changectx()
+            try:
+                fnode = self.ctx.filenode(path)
+                fl = self.repo.file(path)
+                c = context.filectx(self.repo, path, fileid=fnode, filelog=fl)
+                node = c.node()
+            except revlog.LookupError:
+                # eg: convert
+                return subfunc == self.re_kw.sub and data or (data, None)
+        elif subfunc == self.re_kw.sub:
+            # hg kwcat using kwfilelog.read
+            c = context.filectx(self.repo, path, fileid=node)
+            node = c.node()
 
         def kwsub(mobj):
             '''Substitutes keyword using corresponding template.'''
             kw = mobj.group(1)
             self.ct.use_template(self.templates[kw])
             self.ui.pushbuffer()
-            self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
+            self.ct.show(changenode=node, root=self.repo.root, file=path)
             return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
 
         return subfunc(kwsub, data)
 
-    def expand(self, node, data):
+    def expand(self, path, data, node):
         '''Returns data with keywords expanded.'''
-        if util.binary(data) or self.hgcmd in restricted:
+        if util.binary(data):
             return data
-        return self.substitute(node, data, self.re_kw.sub)
+        return self.substitute(path, data, node, self.re_kw.sub)
 
-    def process(self, node, data, expand):
+    def process(self, path, data, expand, ctx, node):
         '''Returns a tuple: data, count.
         Count is number of keywords/keyword substitutions,
         telling caller whether to act on file containing data.'''
         if util.binary(data):
             return data, None
         if expand:
-            return self.substitute(node, data, self.re_kw.subn)
-        return data, self.re_kw.search(data)
+            self.ctx = ctx
+            return self.substitute(path, data, node, self.re_kw.subn)
+        return self.re_kw.subn(r'$\1$', data)
 
-    def shrink(self, text):
+    def shrink(self, data):
         '''Returns text with all keyword substitutions removed.'''
-        if util.binary(text):
-            return text
-        return self.re_kw.sub(r'$\1$', text)
+        if util.binary(data):
+            return data
+        return self.re_kw.sub(r'$\1$', data)
 
 class kwfilelog(filelog.filelog):
     '''
-    Subclass of filelog to hook into its read, add, cmp methods.
-    Keywords are "stored" unexpanded, and processed on reading.
+    Subclass of filelog to hook into its read method for kwcat.
     '''
-    def __init__(self, opener, path):
+    def __init__(self, opener, path, kwt):
         super(kwfilelog, self).__init__(opener, path)
-        _kwtemplater.path = path
-
-    def kwctread(self, node, expand):
-        '''Reads expanding and counting keywords, called from _overwrite.'''
-        data = super(kwfilelog, self).read(node)
-        return _kwtemplater.process(node, data, expand)
+        self._kwt = kwt
+        self._path = path
 
     def read(self, node):
         '''Expands keywords when reading filelog.'''
         data = super(kwfilelog, self).read(node)
-        return _kwtemplater.expand(node, data)
-
-    def add(self, text, meta, tr, link, p1=None, p2=None):
-        '''Removes keyword substitutions when adding to filelog.'''
-        text = _kwtemplater.shrink(text)
-        return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
+        return self._kwt.expand(self._path, data, node)
 
-    def cmp(self, node, text):
-        '''Removes keyword substitutions for comparison.'''
-        text = _kwtemplater.shrink(text)
-        if self.renamed(node):
-            t2 = super(kwfilelog, self).read(node)
-            return t2 != text
-        return revlog.revlog.cmp(self, node, text)
-
-def _iskwfile(f, link):
-    return not link(f) and _kwtemplater.matcher(f)
 
 def _status(ui, repo, *pats, **opts):
     '''Bails out if [keyword] configuration is not active.
     Returns status of working directory.'''
-    if _kwtemplater:
+    if hasattr(repo, '_kwt'):
         files, match, anypats = cmdutil.matchpats(repo, pats, opts)
         return repo.status(files=files, match=match, list_clean=True)
     if ui.configitems('keyword'):
@@ -352,23 +246,23 @@
     '''Overwrites selected files expanding/shrinking keywords.'''
     ctx = repo.changectx(node)
     mf = ctx.manifest()
-    if node is not None:   # commit
-        _kwtemplater.commitnode = node
+    if node is not None:
+        # commit
         files = [f for f in ctx.files() if f in mf]
         notify = ui.debug
-    else:                  # kwexpand/kwshrink
+    else:
+        # kwexpand/kwshrink
         notify = ui.note
-    candidates = [f for f in files if _iskwfile(f, mf.linkf)]
+    candidates = [f for f in files if not mf.linkf(f) and repo._kwt.matcher(f)]
     if candidates:
         overwritten = []
         candidates.sort()
         action = expand and 'expanding' or 'shrinking'
         for f in candidates:
-            fp = repo.file(f, kwmatch=True)
-            data, kwfound = fp.kwctread(mf[f], expand)
+            data, kwfound = repo._wreadkwct(f, expand, ctx, node)
             if kwfound:
                 notify(_('overwriting %s %s keywords\n') % (f, action))
-                _wwrite(repo, f, data, mf)
+                repo.wwrite(f, data, mf.flags(f), overwrite=True)
                 overwritten.append(f)
         _normal(repo, overwritten)
 
@@ -386,6 +280,26 @@
     finally:
         del wlock, lock
 
+def cat(ui, repo, file1, *pats, **opts):
+    '''output the current or given revision of files expanding keywords
+
+    Print the specified files as they were at the given revision.
+    If no revision is given, the parent of the working directory is used,
+    or tip if no revision is checked out.
+
+    Output may be to a file, in which case the name of the file is
+    given using a format string.  The formatting rules are the same as
+    for the export command, with the following additions:
+
+    %s   basename of file being printed
+    %d   dirname of file being printed, or '.' if in repo root
+    %p   root-relative path name of file being printed
+    '''
+    try:
+        repo.file = repo._kwfile
+    except AttributeError:
+        pass
+    commands.cat(ui, repo, file1, *pats, **opts)
 
 def demo(ui, repo, *args, **opts):
     '''print [keywordmaps] configuration and an expansion example
@@ -463,7 +377,7 @@
     repo.commit(text=msg)
     format = ui.verbose and ' in %s' % path or ''
     demostatus('%s keywords expanded%s' % (kwstatus, format))
-    ui.write(repo.wread(fn))
+    ui.write(repo.wopener(fn).read())
     ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
     shutil.rmtree(tmpdir, ignore_errors=True)
 
@@ -490,9 +404,7 @@
     if opts.get('untracked'):
         files += unknown
     files.sort()
-    # use the full definition of repo._link for backwards compatibility
-    kwfiles = [f for f in files if _kwtemplater.matcher(f)
-               and not os.path.islink(repo.wjoin(f))]
+    kwfiles = [f for f in files if not _link(repo, f) and repo._kwt.matcher(f)]
     cwd = pats and repo.getcwd() or ''
     kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
     if opts.get('all') or opts.get('ignore'):
@@ -515,44 +427,10 @@
 
 
 def reposetup(ui, repo):
-    '''Sets up repo as kwrepo for keyword substitution.
-    Overrides file method to return kwfilelog instead of filelog
-    if file matches user configuration.
-    Wraps commit to overwrite configured files with updated
-    keyword substitutions.
-    This is done for local repos only, and only if there are
-    files configured at all for keyword substitution.'''
-
     if not repo.local():
         return
 
-    nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
-                    'export', 'grep', 'identify', 'incoming', 'init',
-                    'log', 'outgoing', 'push', 'remove', 'rename',
-                    'rollback', 'tip',
-                    'convert')
-    try:
-        hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
-    except (cmdutil.UnknownCommand, dispatch.ParseError):
-        # must be an external caller, otherwise Exception would have been
-        # raised at core command line parsing
-        hgcmd, args, cmdopts = externalcall
-        if hgcmd is None:
-            # not an "official" hg command as from command line
-            return
-    if hgcmd in nokwcommands:
-        return
-
-    if hgcmd == 'diff':
-        # only expand if comparing against working dir
-        node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
-        if node2 is not None:
-            return
-        # shrink if rev is not current node
-        if node1 is not None and node1 != repo.changectx().node():
-            hgcmd = 'diff1'
-
-    inc, exc = [], ['.hgtags']
+    inc, exc = [], ['.hgtags', '.hg_archival.txt']
     for pat, opt in ui.configitems('keyword'):
         if opt != 'ignore':
             inc.append(pat)
@@ -561,25 +439,44 @@
     if not inc:
         return
 
-    global _kwtemplater
-    _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
-
     class kwrepo(repo.__class__):
-        def file(self, f, kwmatch=False):
+        def _kwfile(self, f):
+            '''Returns filelog expanding keywords on read (for kwcat).'''
             if f[0] == '/':
                 f = f[1:]
-            if kwmatch or _kwtemplater.matcher(f):
-                return kwfilelog(self.sopener, f)
+            if self._kwt.matcher(f):
+                return kwfilelog(self.sopener, f, self._kwt)
             return filelog.filelog(self.sopener, f)
 
+        def _wreadkwct(self, filename, expand, ctx, node):
+            '''Reads filename and returns tuple of data with keywords
+            expanded/shrunk and count of keywords (for _overwrite).'''
+            data = super(kwrepo, self).wread(filename)
+            return self._kwt.process(filename, data, expand, ctx, node)
+
         def wread(self, filename):
             data = super(kwrepo, self).wread(filename)
-            if hgcmd in restricted and _kwtemplater.matcher(filename):
-                return _kwtemplater.shrink(data)
+            if self._kwt.matcher(filename):
+                return self._kwt.shrink(data)
             return data
 
+        def wwrite(self, filename, data, flags=None, overwrite=False):
+            if not overwrite and self._kwt.matcher(filename):
+                data = self._kwt.expand(filename, data, None)
+            try:
+                super(kwrepo, self).wwrite(filename, data, flags)
+            except (AttributeError, TypeError):
+                # 656e06eebda7 removed file descriptor argument
+                # 67982d3ee76c added flags argument
+                super(kwrepo, self).wwrite(filename, data)
+
+        def wwritedata(self, filename, data):
+            if self._kwt.matcher(filename):
+                data = self._kwt.expand(filename, data, None)
+            return super(kwrepo, self).wwritedata(filename, data)
+
         def _commit(self, files, text, user, date, match, force, lock, wlock,
-                    force_editor, p1, p2, extra):
+                    force_editor, p1, p2, extra, empty_ok):
             '''Private commit wrapper for backwards compatibility.'''
             try:
                 return super(kwrepo, self).commit(files=files, text=text,
@@ -589,15 +486,25 @@
                                                   force_editor=force_editor,
                                                   p1=p1, p2=p2, extra=extra)
             except TypeError:
-                return super(kwrepo, self).commit(files=files, text=text,
-                                                  user=user, date=date,
-                                                  match=match, force=force,
-                                                  force_editor=force_editor,
-                                                  p1=p1, p2=p2, extra=extra)
+                try:
+                    return super(kwrepo, self).commit(files=files, text=text,
+                                                      user=user, date=date,
+                                                      match=match, force=force,
+                                                      force_editor=force_editor,
+                                                      p1=p1, p2=p2,
+                                                      extra=extra,
+                                                      empty_ok=empty_ok)
+                except TypeError:
+                    return super(kwrepo, self).commit(files=files, text=text,
+                                                      user=user, date=date,
+                                                      match=match, force=force,
+                                                      force_editor=force_editor,
+                                                      p1=p1, p2=p2, extra=extra)
 
         def commit(self, files=None, text='', user=None, date=None,
                    match=util.always, force=False, lock=None, wlock=None,
-                   force_editor=False, p1=None, p2=None, extra={}):
+                   force_editor=False, p1=None, p2=None, extra={},
+                   empty_ok=False):
             # (w)lock arguments removed in 126f527b3ba3
             # so they are None or what was passed to commit
             # use private _(w)lock for deletion
@@ -629,7 +536,8 @@
                         _p2 = hex(_p2)
 
                 node = self._commit(files, text, user, date, match, force,
-                                    _lock, _wlock, force_editor, p1, p2, extra)
+                                    _lock, _wlock, force_editor, p1, p2, extra,
+                                    empty_ok)
 
                 # restore commit hooks
                 for name, cmd in commithooks.iteritems():
@@ -641,10 +549,14 @@
             finally:
                 del _wlock, _lock
 
+    kwrepo._kwt = kwtemplater(ui, repo, inc, exc)
     repo.__class__ = kwrepo
 
 
 cmdtable = {
+    'kwcat':
+        (cat, commands.table['cat'][1],
+         _('hg kwcat [OPTION]... FILE...')),
     'kwdemo':
         (demo,
          [('d', 'default', None, _('show default keyword template maps')),
--- a/tests/test-keyword	Fri Jan 25 21:42:52 2008 +0000
+++ b/tests/test-keyword	Wed Jan 30 10:38:10 2008 +0100
@@ -58,8 +58,8 @@
 hg --quiet identify
 echo % cat
 cat a b
-echo % hg cat
-hg cat a b
+echo % hg kwcat
+hg kwcat a b
 
 echo
 echo % diff a hooktest
@@ -145,8 +145,8 @@
 
 echo % cat
 cat a b
-echo % hg cat
-hg cat a b
+echo % hg kwcat
+hg kwcat a b
 
 echo '$Xinfo$' >> a
 cat <<EOF >> log
@@ -167,8 +167,8 @@
 
 echo % cat
 cat a b
-echo % hg cat
-hg cat a b
+echo % hg kwcat
+hg kwcat a b
 
 echo % remove
 hg remove a
@@ -240,11 +240,5 @@
 hg --verbose kwshrink
 echo % cat
 cat a b
-echo % hg cat
-hg cat a b
-
-rm $HGRCPATH
-echo % cat
-cat a b
-echo % hg cat
-hg cat a b
+echo % hg kwcat
+hg kwcat a b
--- a/tests/test-keyword.out	Fri Jan 25 21:42:52 2008 +0000
+++ b/tests/test-keyword.out	Wed Jan 30 10:38:10 2008 +0100
@@ -33,6 +33,7 @@
 Caveat: "hg import" fails if the patch context contains an active
         keyword. In that case run "hg kwshrink", and then reimport.
         Or, better, use bundle/unbundle to share changes.
+ kwcat      output the current or given revision of files expanding keywords
  kwdemo     print [keywordmaps] configuration and an expansion example
  kwexpand   expand keywords in working directory
  kwfiles    print files currently configured for keyword expansion
@@ -95,7 +96,7 @@
 do not process $Id:
 xxx $
 ignore $Id$
-% hg cat
+% hg kwcat
 expand $Id: a,v 7f0665a496fd 1970/01/01 00:00:00 user $
 do not process $Id:
 xxx $
@@ -198,7 +199,7 @@
 do not process $Id:
 xxx $
 ignore $Id$
-% hg cat
+% hg kwcat
 expand $Id: a 7f0665a496fd Thu, 01 Jan 1970 00:00:00 +0000 user $
 do not process $Id:
 xxx $
@@ -213,7 +214,7 @@
 xxx $
 $Xinfo: User Name <user@example.com>: firstline $
 ignore $Id$
-% hg cat
+% hg kwcat
 expand $Id: a 576a35651b0a Thu, 01 Jan 1970 00:00:02 +0000 user $
 do not process $Id:
 xxx $
@@ -279,21 +280,9 @@
 xxx $
 $Xinfo$
 ignore $Id$
-% hg cat
+% hg kwcat
 expand $Id: a 576a35651b0a Thu, 01 Jan 1970 00:00:02 +0000 user $
 do not process $Id:
 xxx $
 $Xinfo: User Name <user@example.com>: firstline $
 ignore $Id$
-% cat
-expand $Id$
-do not process $Id:
-xxx $
-$Xinfo$
-ignore $Id$
-% hg cat
-expand $Id$
-do not process $Id:
-xxx $
-$Xinfo$
-ignore $Id$