# keyword.py - keyword expansion for Mercurial## Copyright 2007 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.## $Id$## Keyword expansion hack against the grain of a DSCM## There are many good reasons why this is not needed in a distributed# SCM, still it may be useful in very small projects based on single# files (like LaTeX packages), that are mostly addressed to an audience# not running a version control system.## For in-depth discussion refer to# <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.## Keyword expansion is based on Mercurial's changeset template mappings.# The extension provides an additional UTC-date filter ({date|utcdate}).## The user has the choice either to create his own keywords and their# expansions or to use the CVS-like default ones.## Default $keywords$ and their $keyword: substition $ are:# Revision: changeset id# Author: username# Date: %Y/%m/%d %H:%M:%S [UTC]# RCSFile: basename,v# Source: /path/to/basename,v# Id: basename,v csetid %Y/%m/%d %H:%M:%S username# Header: /path/to/basename,v csetid %Y/%m/%d %H:%M:%S username## Expansions spanning more than one line are truncated to their first line.# Incremental expansion (like CVS' $Log$) is not supported.## Simple setup in hgrc:## # enable extension# keyword = /full/path/to/keyword.py# # or, if script in hgext folder:# # hgext.keyword =## # filename patterns for expansion are configured in this section# # files matching patterns with value 'ignore' are ignored# [keyword]# **.py =# x* = ignore# ...# # in case you prefer your own keyword maps over the cvs-like defaults:# [keywordmaps]# HGdate = {date|rfc822date}# lastlog = {desc} ## same as {desc|firstline} in this context# checked in by = {author}# ...'''keyword expansion in local repositoriesThis extension expands RCS/CVS-like or self-customized keywords inthe text files selected by your configuration.Keywords are only expanded in local repositories and not logged byMercurial internally. The mechanism can be regarded as a conveniencefor the current user and may be turned off anytime.Substitution takes place on every commit and update of the workingrepository.Configuration is done in the [keyword] and [keywordmaps] sections ofhgrc files.'''frommercurial.i18nimportgettextas_# above line for backwards compatibility of standalone versionfrommercurialimportcommands,cmdutil,templater,utilfrommercurialimportcontext,filelog,revlogfrommercurial.nodeimportbinimportos.path,re,sys,timedeftemplates={'Revision':'{node|short}','Author':'{author|user}','Date':'{date|utcdate}','RCSFile':'{file|basename},v','Source':'{root}/{file},v','Id':'{file|basename},v {node|short} {date|utcdate} {author|user}','Header':'{root}/{file},v {node|short} {date|utcdate} {author|user}',}defutcdate(date):'''Returns hgdate in cvs-like UTC format.'''returntime.strftime('%Y/%m/%d %H:%M:%S',time.gmtime(date[0]))defgetmodulename():'''Makes sure pretxncommit-hook can import keyword module regardless of where its located.'''fork,vinsys.modules.iteritems():ifvisNoneornothasattr(v,'__file__'):continueifv.__file__.startswith(__file__):returnkelse:sys.path.insert(0,os.path.abspath(os.path.dirname(__file__)))returnos.path.splitext(os.path.basename(__file__))[0]classkwtemplater(object):''' Sets up keyword templates, corresponding keyword regex, and provides keyword expansion function. '''def__init__(self,ui,repo):self.ui=uiself.repo=repoself.templates=dict(ui.configitems('keywordmaps'))ordeftemplatesself.re_kw=re.compile(r'\$(%s)[^$]*?\$'%'|'.join(re.escape(k)forkinself.templates.keys()))templater.common_filters['utcdate']=utcdateself.t=cmdutil.changeset_templater(ui,repo,False,'',False)defexpand(self,mobj,path,node):'''Expands keyword using corresponding template.'''kw=mobj.group(1)template=templater.parsestring(self.templates[kw],quoted=False)self.t.use_template(template)self.ui.pushbuffer()self.t.show(changenode=node,root=self.repo.root,file=path)kwsub=templater.firstline(self.ui.popbuffer())return'$%s: %s $'%(kw,kwsub)defreposetup(ui,repo):'''Sets up repo, and filelog especially, as kwrepo and kwfilelog for keyword substitution. This is done for local repos only.'''ifnotrepo.local():returninc,exc=[],['.hg*']forpat,optinrepo.ui.configitems('keyword'):ifopt!='ignore':inc.append(pat)else:exc.append(pat)ifnotinc:returnrepo.kwfmatcher=util.matcher(repo.root,inc=inc,exc=exc)[1]classkwrepo(repo.__class__):deffile(self,f):iff[0]=='/':f=f[1:]returnfilelog.filelog(self.sopener,f,self,self.revlogversion)classkwfilelog(filelog.filelog):''' Superclass over filelog to customize it's read, add, cmp methods. Keywords are "stored" unexpanded, and expanded on reading. '''def__init__(self,opener,path,repo,defversion=revlog.REVLOG_DEFAULT_VERSION):super(kwfilelog,self).__init__(opener,path,defversion)self._repo=repoself._path=path# only init kwtemplater if neededifnotisinstance(repo,int)andrepo.kwfmatcher(path):self.kwt=kwtemplater(repo.ui,repo)else:self.kwt=Nonedefiskwcandidate(self,data):'''Decides whether to act on keywords.'''returnself.kwtisnotNoneandnotutil.binary(data)defread(self,node):'''Substitutes keywords when reading filelog.'''data=super(kwfilelog,self).read(node)ifself.iskwcandidate(data):c=context.filectx(self._repo,self._path,fileid=node,filelog=self)returnself.kwt.re_kw.sub(lambdam:self.kwt.expand(m,self._path,c.node()),data)returndatadefadd(self,text,meta,tr,link,p1=None,p2=None):'''Removes keyword substitutions when adding to filelog.'''ifself.iskwcandidate(text):text=self.kwt.re_kw.sub(r'$\1$',text)returnsuper(kwfilelog,self).add(text,meta,tr,link,p1=p1,p2=p2)defcmp(self,node,text):'''Removes keyword substitutions for comparison.'''ifself.iskwcandidate(text):text=self.kwt.re_kw.sub(r'$\1$',text)returnsuper(kwfilelog,self).cmp(node,text)filelog.filelog=kwfilelogrepo.__class__=kwrepo# configure pretxncommit hookrepo.ui.setconfig('hooks','pretxncommit.keyword','python:%s.pretxnkw'%getmodulename())defpretxnkw(ui,repo,hooktype,**args):'''pretxncommit hook that collects candidates for keyword expansion on commit and expands keywords in working dir.'''cmd,sysargs,globalopts,cmdopts=commands.parse(ui,sys.argv[1:])[1:]ifrepr(cmd).split()[1]in('tag','import_'):returnfiles,match,anypats=cmdutil.matchpats(repo,sysargs,cmdopts)modified,added=repo.status(files=files,match=match)[:2]candidates=[fforfinmodified+addedifrepo.kwfmatcher(f)]ifnotcandidates:returnkwt=kwtemplater(ui,repo)node=bin(args['node'])forfincandidates:data=repo.wfile(f).read()ifnotutil.binary(data):data,kwct=kwt.re_kw.subn(lambdam:kwt.expand(m,f,node),data)ifkwct:ui.debug(_('overwriting %s expanding keywords\n'%f))repo.wfile(f,'w').write(data)