# 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 consists actually in 2 parts: 1. 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,Revision,f,date,Author):'''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.'''RCSFile=os.path.basename(f)+',v'Source=os.path.join(repo.root,f)+',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):c=context.filectx(self._repo,self._path,fileid=node,filelog=self)forpat,optinself._repo.ui.configitems('keyword'):ifopt=='expand':mf=util.matcher(self._repo.root,'',[pat],[],[])[1]ifmf(self._path):defkwexpander(matchobj):returnkwexpand(matchobj,self._repo,c.changectx(),self._path,c.date(),c.user())re_kw=re.compile(r'\$(%s)\$'%hgkeywords)returnre_kw.sub(kwexpander,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=[]# python2.4: files = set()forpat,optinrepo.ui.configitems('keyword'):ifopt=='expand':mf=util.matcher(repo.root,'',[pat],[],[])[1]forcandidateincandidates:ifmf(candidate)andcandidatenotinfiles:files.append(candidate)# python2.4:# if mf(candidate): files.add(candidate)ifnotfiles:returnFalseuser,date=repo.changelog.read(repo.changelog.tip())[1:3]# expand both expanded and unexpanded keywordsre_kw=re.compile(r'\$(%s)(: [^$]+? )?\$'%hgkeywords)forfinfiles:data=repo.wfile(f).read()ifnotutil.binary(data):defkwexpander(matchobj):returnkwexpand(matchobj,repo,args['node'][:12],f,date,user)data,kwct=re_kw.subn(kwexpander,data)ifkwct:ui.note(_('expanding keywords in %s\n'%f))# backup file?repo.wfile(f,'w').write(data)