hgkw/keyword.py
branchkwmap-templates
changeset 110 b0b85b383f36
parent 109 b2cc6a8d4a18
child 111 94315baadcaf
equal deleted inserted replaced
109:b2cc6a8d4a18 110:b0b85b383f36
    55 
    55 
    56 from mercurial.i18n import gettext as _
    56 from mercurial.i18n import gettext as _
    57 # above line for backwards compatibility; can be changed to
    57 # above line for backwards compatibility; can be changed to
    58 #   from mercurial.i18n import _
    58 #   from mercurial.i18n import _
    59 # some day
    59 # some day
    60 from mercurial import cmdutil, templater, util
    60 from mercurial import context, filelog, revlog
       
    61 from mercurial import commands, cmdutil, templater, util
    61 from mercurial.node import *
    62 from mercurial.node import *
    62 import os.path, re, sys, time
    63 import os.path, re, sys, time
    63 
    64 
    64 deftemplates = {
    65 deftemplates = {
    65         'Revision': '{node|short}',
    66         'Revision': '{node|short}',
    73 
    74 
    74 def utcdate(date):
    75 def utcdate(date):
    75     '''Returns hgdate in cvs-like UTC format.'''
    76     '''Returns hgdate in cvs-like UTC format.'''
    76     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
    77     return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
    77 
    78 
    78 def kwfmatches(ui, repo, files):
    79 def getkwconfig(ui, repo):
    79     '''Selects candidates for keyword substitution
       
    80     configured in keyword section in hgrc.'''
       
    81     inc = [pat for pat, opt in ui.configitems('keyword') if opt != 'ignore']
    80     inc = [pat for pat, opt in ui.configitems('keyword') if opt != 'ignore']
    82     if not inc:
    81     if not inc:
    83         ui.warn(_('keyword: no filename globs for expansion\n'))
    82         ui.warn(_('keyword: no filename globs for substitution\n'))
    84         return []
    83         return None, None
    85     exc = [pat for pat, opt in ui.configitems('keyword') if opt == 'ignore']
    84     exc = [pat for pat, opt in ui.configitems('keyword') if opt == 'ignore']
    86     kwfmatcher = util.matcher(repo.root, inc=inc, exc=['.hg*']+exc)[1]
    85     return inc, exc
    87     return [f for f in files if kwfmatcher(f)]
       
    88 
    86 
    89 
    87 
    90 class kwtemplater(object):
    88 class kwtemplater(object):
    91     '''
    89     '''
    92     Sets up keyword templates, corresponding keyword regex, and
    90     Sets up keyword templates, corresponding keyword regex, and
    93     provides keyword expansion function.
    91     provides keyword expansion function.
       
    92 
       
    93     If a repo is configured for keyword substitution, this class
       
    94     will be set as an (appendix) attribute to the repo.
    94     '''
    95     '''
    95     def __init__(self, ui, repo):
    96     def __init__(self, ui, repo):
    96         self.ui = ui
    97         self.ui = ui
    97         self.repo = repo
    98         self.repo = repo
    98         self.templates = dict(ui.configitems('keywordmaps')) or deftemplates
    99         self.templates = dict(ui.configitems('keywordmaps')) or deftemplates
    99         self.re_kw = re.compile(r'\$(%s)[^$]*?\$' %
   100         self.re_kw = re.compile(r'\$(%s)[^$]*?\$' %
   100                 '|'.join(re.escape(k) for k in self.templates.keys()))
   101                 '|'.join(re.escape(k) for k in self.templates.keys()))
   101         templater.common_filters['utcdate'] = utcdate
   102         templater.common_filters['utcdate'] = utcdate
   102         self.t = cmdutil.changeset_templater(ui, repo, False, '', False)
   103         self.t = cmdutil.changeset_templater(ui, repo, False, '', False)
   103             
       
   104 
   104 
   105     def expand(self, mobj, path, node):
   105     def expand(self, mobj, path, node):
   106         '''Expands keyword with corresponding template.'''
   106         '''Expands keyword with corresponding template.'''
   107         kw = mobj.group(1)
   107         kw = mobj.group(1)
   108         template = templater.parsestring(self.templates[kw], quoted=False)
   108         template = templater.parsestring(self.templates[kw], quoted=False)
   109         self.t.use_template(template)
   109         self.t.use_template(template)
   110         self.ui.pushbuffer()
   110         self.ui.pushbuffer()
   111         self.t.show(changenode=node, root=self.repo.root, file=path)
   111         self.t.show(changenode=node, root=self.repo.root, file=path)
   112         expansion = templater.firstline(self.ui.popbuffer())
   112         kwsub = templater.firstline(self.ui.popbuffer())
   113         return '$%s: %s $' % (kw, expansion)
   113         return '$%s: %s $' % (kw, kwsub)
   114 
   114 
   115 
   115 
   116 def reposetup(ui, repo):
   116 def reposetup(ui, repo):
   117     '''Sets up repo, and filelog especially, as kwrepo and kwfilelog
   117     '''Sets up repo, and filelog especially, as kwrepo and kwfilelog
   118     for keyword substitution.  This is done for local repos only.'''
   118     for keyword substitution.  This is done for local repos only.'''
   119 
       
   120     from mercurial import context, filelog, revlog
       
   121     if not repo.local():
   119     if not repo.local():
       
   120         return
       
   121 
       
   122     inc, exc = getkwconfig(ui, repo)
       
   123     if not inc:
       
   124         # no files configured for keyword substitution:
       
   125         # no need to burden repo with extra ballast
   122         return
   126         return
   123 
   127 
   124     class kwrepo(repo.__class__):
   128     class kwrepo(repo.__class__):
   125         def file(self, f):
   129         def file(self, f):
   126             if f[0] == '/':
   130             if f[0] == '/':
   135         def __init__(self, opener, path, repo,
   139         def __init__(self, opener, path, repo,
   136                      defversion=revlog.REVLOG_DEFAULT_VERSION):
   140                      defversion=revlog.REVLOG_DEFAULT_VERSION):
   137             super(kwfilelog, self).__init__(opener, path, defversion)
   141             super(kwfilelog, self).__init__(opener, path, defversion)
   138             self._repo = repo
   142             self._repo = repo
   139             self._path = path
   143             self._path = path
   140             # only init kwtemplater if needed
   144             # check at init if file configured for keyword substition
   141             if not isinstance(repo, int) and kwfmatches(ui, repo, [path]):
   145             if not isinstance(repo, int) and repo.kwfmatcher(path):
   142                 self.kwt = kwtemplater(ui, repo)
   146                 self.kwsub = True
   143             else:
   147             else:
   144                 self.kwt = None
   148                 self.kwsub = False
   145 
   149 
   146         def iskwcandidate(self, data):
   150         def iskwcandidate(self, data):
   147             '''Decides whether to act on keywords.'''
   151             '''Decides whether to act on keywords.'''
   148             return self.kwt is not None and not util.binary(data)
   152             return self.kwsub and not util.binary(data)
   149 
   153 
   150         def read(self, node):
   154         def read(self, node):
   151             '''Substitutes keywords when reading filelog.'''
   155             '''Substitutes keywords when reading filelog.'''
   152             data = super(kwfilelog, self).read(node)
   156             data = super(kwfilelog, self).read(node)
   153             if self.iskwcandidate(data):
   157             if self.iskwcandidate(data):
   154                 c = context.filectx(self._repo, self._path,
   158                 c = context.filectx(self._repo, self._path,
   155                                     fileid=node, filelog=self)
   159                                     fileid=node, filelog=self)
   156                 return self.kwt.re_kw.sub(lambda m:
   160                 return self._repo.kwt.re_kw.sub(lambda m:
   157                         self.kwt.expand(m, self._path, c.node()), data)
   161                         self._repo.kwt.expand(m, self._path, c.node()), data)
   158             return data
   162             return data
   159 
   163 
   160         def add(self, text, meta, tr, link, p1=None, p2=None):
   164         def add(self, text, meta, tr, link, p1=None, p2=None):
   161             '''Removes keyword substitutions when adding to filelog.'''
   165             '''Removes keyword substitutions when adding to filelog.'''
   162             if self.iskwcandidate(text):
   166             if self.iskwcandidate(text):
   163                 text = self.kwt.re_kw.sub(r'$\1$', text)
   167                 text = self._repo.kwt.re_kw.sub(r'$\1$', text)
   164             return super(kwfilelog, self).add(text,
   168             return super(kwfilelog, self).add(text,
   165                             meta, tr, link, p1=p1, p2=p2)
   169                             meta, tr, link, p1=p1, p2=p2)
   166 
   170 
   167         def cmp(self, node, text):
   171         def cmp(self, node, text):
   168             '''Removes keyword substitutions for comparison.'''
   172             '''Removes keyword substitutions for comparison.'''
   169             if self.iskwcandidate(text):
   173             if self.iskwcandidate(text):
   170                 text = self.kwt.re_kw.sub(r'$\1$', text)
   174                 text = self._repo.kwt.re_kw.sub(r'$\1$', text)
   171             return super(kwfilelog, self).cmp(node, text)
   175             return super(kwfilelog, self).cmp(node, text)
   172 
   176 
   173     filelog.filelog = kwfilelog
   177     filelog.filelog = kwfilelog
   174     repo.__class__ = kwrepo
   178     repo.__class__ = kwrepo
       
   179 
       
   180     # create filematching function once for repo
       
   181     setattr(repo, 'kwfmatcher',
       
   182             util.matcher(repo.root, inc=inc, exc=['.hg*']+exc)[1])
       
   183     # initialize kwtemplater once for repo
       
   184     setattr(repo, 'kwt', kwtemplater(ui, repo))
       
   185 
   175     # make pretxncommit hook import kwmodule regardless of where it's located
   186     # make pretxncommit hook import kwmodule regardless of where it's located
   176     for k, v in sys.modules.iteritems():
   187     for k, v in sys.modules.iteritems():
   177         if v is None:
   188         if v is None:
   178             continue
   189             continue
   179         if not hasattr(v, '__file__'):
   190         if not hasattr(v, '__file__'):
   183             break
   194             break
   184     else:
   195     else:
   185         sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
   196         sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
   186         mod = os.path.splitext(os.path.basename(__file__))[0]
   197         mod = os.path.splitext(os.path.basename(__file__))[0]
   187     ui.setconfig('hooks', 'pretxncommit.keyword', 'python:%s.pretxnkw' % mod)
   198     ui.setconfig('hooks', 'pretxncommit.keyword', 'python:%s.pretxnkw' % mod)
   188     del mod
   199 
       
   200     del inc, exc, mod
   189 
   201 
   190 
   202 
   191 def pretxnkw(ui, repo, hooktype, **args):
   203 def pretxnkw(ui, repo, hooktype, **args):
   192     '''pretxncommit hook that collects candidates for keyword expansion
   204     '''pretxncommit hook that collects candidates for keyword expansion
   193     on commit and expands keywords in working dir.'''
   205     on commit and expands keywords in working dir.'''
   194     from mercurial import commands
       
   195 
   206 
   196     cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:]
   207     cmd, sysargs, globalopts, cmdopts = commands.parse(ui, sys.argv[1:])[1:]
   197     if repr(cmd).split()[1] in ('tag', 'import_'):
   208     if repr(cmd).split()[1] in ('tag', 'import_'):
   198         return
   209         return
   199 
   210 
   200     files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts)
   211     files, match, anypats = cmdutil.matchpats(repo, sysargs, cmdopts)
   201     modified, added = repo.status(files=files, match=match)[:2]
   212     modified, added = repo.status(files=files, match=match)[:2]
   202     candidates = kwfmatches(ui, repo, modified+added)
   213     candidates = [f for f in modified+added if repo.kwfmatcher(f)]
   203     if not candidates:
   214     if not candidates:
   204         return
   215         return
   205 
   216 
   206     kwt = kwtemplater(ui, repo)
       
   207     node = bin(args['node'])
   217     node = bin(args['node'])
   208     for f in candidates:
   218     for f in candidates:
   209         data = repo.wfile(f).read()
   219         data = repo.wfile(f).read()
   210         if not util.binary(data):
   220         if not util.binary(data):
   211             data, kwct = kwt.re_kw.subn(lambda m: kwt.expand(m, f, node), data)
   221             data, kwct = repo.kwt.re_kw.subn(lambda m:
       
   222                     repo.kwt.expand(m, f, node), data)
   212             if kwct:
   223             if kwct:
   213                 ui.debug(_('overwriting %s expanding keywords\n' % f))
   224                 ui.debug(_('overwriting %s expanding keywords\n' % f))
   214                 repo.wfile(f, 'w').write(data)
   225                 repo.wfile(f, 'w').write(data)