# 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.Supported keywords are (changeset 000000000000): $Revision: 000000000000 $ $Author: Your Name <address@example.com> $ $Date: %a %b %d %H:%M:%S %Y %z $ $RCSFile: basename,v $ $Source: /path/to/basename,v $ $Id: basename,v 000000000000 %Y-%m-%d %H:%M:%S %z shortname $ $Header: /path/to/basename,v 000000000000 %Y-%m-%d %H:%M:%S %z shortname $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_frommercurialimportcontext,utilimportos.path,rere_kw=re.compile(r'\$(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))Header='%s%s'%(Source,revdateauth)Id='%s%s'%(RCSFile,revdateauth)return'$%s: %s $'%(matchobj.group(1),eval(matchobj.group(1)))defkwfmatchers(ui,repo):'''Returns filename matchers from ui keyword section.'''return[util.matcher(repo.root,'',[pat],[],[])[1]forpat,optinui.configitems('keyword')ifopt=='expand']defreposetup(ui,repo):frommercurialimportfilelog,revlogifnotrepo.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):formfinkwfmatchers(ui,self._repo):ifmf(self._path):ui.debug(_('expanding keywords in %s\n'%self._path))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):ifnotself._path.startswith('.hg')andnotutil.binary(text):formfinkwfmatchers(ui,self._repo):ifmf(self._path):ui.debug(_('removing keyword substitutions in %s\n')%self._path)text=re_kw.sub(r'$\1$',text)breakreturnsuper(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.'''frommercurialimportcmdutil,commandsimportsysifhooktype!='pretxncommit':returnTruecmd,sysargs,globalopts,cmdopts=commands.parse(ui,sys.argv[1:])[1:]ifrepr(cmd).split()[1]in('tag','import_'):returnFalsefiles,match,anypats=cmdutil.matchpats(repo,sysargs,cmdopts)modified,added=repo.status(files=files,match=match)[:2]candidates=[fforfinmodified+addedifnotf.startswith('.hg')]ifnotcandidates:returnFalsefmatchers=kwfmatchers(ui,repo)forfincandidates:formfinfmatchers:ifmf(f):data=repo.wfile(f).read()ifnotutil.binary(data):data,kwct=re_kw.subn(lambdam:kwexpand(m,repo,f,changeid=args['node']),data)ifkwct:ui.debug(_('overwriting %s expanding keywords\n'%f))repo.wfile(f,'w').write(data)break