# keyword.py - keyword expansion for mercurial## Copyright 2006 Christian Ebert <blacktrash@gmx.net>## This software may be used and distributed according to the terms# of the GNU General Public License, incorporated herein by reference.'''keyword expansion hack against the grain of a DSCMThis extension lets you expand RCS/CVS-like keywords in a Mercurialrepository.There are many good reasons why this is not needed in a distributedSCM, still it may be useful in very small projects based on singlefiles (like LaTeX packages), that are mostly addressed to an audiencenot running a version control system.The extension, according to its hackish nature, is a hybrid and consistsactually in 2 parts: 1. pure extension code (reposetup) that is triggered on checkout and logging of changes. 2. a pretxncommit hook (hgrc (5)) that expands keywords immediately at commit time in the working directory.Simple setup in hgrc: # enable extension hgext.keyword = # filename patterns for expansion are configured in this section [keyword] *.sty = expand ... # set up pretxncommit hook [hooks] pretxncommit = pretxncommit.keyword = python:hgext.keyword.pretxnkw'''frommercurial.i18nimport_frommercurialimportcmdutil,commands,context,filelog,revlog,utilimportos.path,re,sys# supported keywords for use in regexeshgkeywords='Id|Header|Author|Date|Revision|RCSFile|Source'defkwexpand(matchobj,repo,path,changeid=None,fileid=None,filelog=None):'''Called by kwfilelog.read and pretxnkw. Sets supported keywords as local variables and evaluates them to their expansion if matchobj is equal to string representation.'''c=context.filectx(repo,path,changeid=changeid,fileid=fileid,filelog=filelog)date=c.date()Revision=c.changectx()Author=c.user()RCSFile=os.path.basename(path)+',v'Source=repo.wjoin(path)+',v'Date=util.datestr(date=date)revdateauth='%s%s%s'%(Revision,util.datestr(date=date,format=util.defaultdateformats[0]),util.shortuser(Author))# %Y-%m-%d %H:%M:%SHeader='%s%s'%(Source,revdateauth)Id='%s%s'%(RCSFile,revdateauth)return'$%s: %s $'%(matchobj.group(1),eval(matchobj.group(1)))defreposetup(ui,repo):ifnotrepo.local():returnclasskwrepo(repo.__class__):deffile(self,f):iff[0]=='/':f=f[1:]returnfilelog.filelog(self.sopener,f,self,self.revlogversion)classkwfilelog(filelog.filelog):def__init__(self,opener,path,repo,defversion=revlog.REVLOG_DEFAULT_VERSION):super(kwfilelog,self).__init__(opener,path,defversion)self._repo=repoself._path=pathdefread(self,node):data=super(kwfilelog,self).read(node)ifnotself._path.startswith('.hg')andnotutil.binary(data):forpat,optinself._repo.ui.configitems('keyword'):ifopt=='expand':mf=util.matcher(self._repo.root,'',[pat],[],[])[1]ifmf(self._path):re_kw=re.compile(r'\$(%s)\$'%hgkeywords)returnre_kw.sub(lambdam:kwexpand(m,self._repo,self._path,fileid=node,filelog=self),data)returndatadefadd(self,text,meta,tr,link,p1=None,p2=None):ifnotutil.binary(text):re_kw=re.compile(r'\$(%s): [^$]+? \$'%hgkeywords)text=re_kw.sub(r'$\1$',text)returnsuper(kwfilelog,self).add(text,meta,tr,link,p1,p2)defsize(self,rev):'''Overrides filelog's size() to use kwfilelog.read().'''node=revlog.node(self,rev)ifsuper(kwfilelog,self).renamed(node):returnlen(self.read(node))returnrevlog.size(self,rev)defcmp(self,node,text):'''Overrides filelog's cmp() to use kwfilelog.read().'''ifsuper(kwfilelog,self).renamed(node):t2=self.read(node)returnt2!=textfilelog.filelog=kwfilelogrepo.__class__=kwrepodefpretxnkw(ui,repo,hooktype,**args):'''pretxncommit hook that collects candidates for keyword expansion on commit and expands keywords in working dir.'''ifhooktype!='pretxncommit':# bail out with errorreturnTrue# reparse args, opts again as pretxncommit hook is silent about themcmd,sysargs,globalopts,cmdopts=commands.parse(ui,sys.argv[1:])[1:]# exclude tag and importifrepr(cmd).split()[1]in('tag','import_'):returnFalsefiles,match,anypats=cmdutil.matchpats(repo,sysargs,cmdopts)# validity checks should have been done alreadymodified,added=repo.status(files=files,match=match)[:2]candidates=[fforfinmodified+addedifnotf.startswith('.hg')]ifnotcandidates:returnFalse# only check files that are configured in keyword sectionfiles=[]forpat,optinrepo.ui.configitems('keyword'):ifopt=='expand':mf=util.matcher(repo.root,'',[pat],[],[])[1]forcandidateincandidates:ifmf(candidate)andcandidatenotinfiles:files.append(candidate)ifnotfiles:returnFalse# update both expanded and unexpanded keywordsre_kw=re.compile(r'\$(%s)(: [^$]+? )?\$'%hgkeywords)forfinfiles:data=repo.wfile(f).read()ifnotutil.binary(data):data,kwct=re_kw.subn(lambdam:kwexpand(m,repo,f,changeid=args['node']),data)ifkwct:ui.note(_('expanding keywords in %s\n'%f))repo.wfile(f,'w').write(data)