"""Rules based url rewriter component, to get configurable RESTful urls:organization: Logilab:copyright: 2007-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses"""importrefromcubicwebimporttyped_eidfromcubicweb.appobjectimportAppObjectdefrgx(pattern,flags=0):"""this is just a convenient shortcout to add the $ sign"""returnre.compile(pattern+'$',flags)classmetarewriter(type):"""auto-extend rules dictionnary"""def__new__(mcs,name,bases,classdict):# collect baseclass' rulesrules=[]ignore_baseclass_rules=classdict.get('ignore_baseclass_rules',False)ifnotignore_baseclass_rules:forbaseinbases:rules[0:0]=getattr(base,'rules',[])rules[0:0]=classdict.get('rules',[])inputs=set()fordatainrules[:]:try:input,output,groups=dataexceptValueError:input,output=dataifinputininputs:rules.remove((input,output))else:inputs.add(input)classdict['rules']=rulesreturnsuper(metarewriter,mcs).__new__(mcs,name,bases,classdict)classURLRewriter(AppObject):"""base class for URL rewriters url rewriters should have a `rules` dict that maps an input URI to something that should be used for rewriting. The actual logic that defines how the rules dict is used is implemented in the `rewrite` method A `priority` attribute might be used to indicate which rewriter should be tried first. The higher the priority is, the earlier the rewriter will be tried """__metaclass__=metarewriter__registry__='urlrewriting'__abstract__=Truepriority=1defrewrite(self,req,uri):raiseNotImplementedErrorclassSimpleReqRewriter(URLRewriter):"""The SimpleReqRewriters uses a `rules` dict that maps input URI (regexp or plain string) to a dictionary to update the request's form If the input uri is a regexp, group substitution is allowed """__regid__='simple'rules=[('/_',dict(vid='manage')),('/_registry',dict(vid='registry')),# (rgx('/_([^/]+?)/?'), dict(vid=r'\1')),('/schema',dict(vid='schema')),('/index',dict(vid='index')),('/myprefs',dict(vid='propertiesform')),('/siteconfig',dict(vid='systempropertiesform')),('/siteinfo',dict(vid='info')),('/manage',dict(vid='manage')),('/notfound',dict(vid='404')),('/error',dict(vid='error')),('/sparql',dict(vid='sparql')),(rgx('/schema/([^/]+?)/?'),dict(vid='eschema',rql=r'Any X WHERE X is CWEType, X name "\1"')),(rgx('/add/([^/]+?)/?'),dict(vid='creation',etype=r'\1')),(rgx('/doc/images/(.+?)/?'),dict(vid='wdocimages',fid=r'\1')),(rgx('/doc/?'),dict(vid='wdoc',fid=r'main')),(rgx('/doc/(.+?)/?'),dict(vid='wdoc',fid=r'\1')),(rgx('/changelog/?'),dict(vid='changelog')),]defrewrite(self,req,uri):"""for each `input`, `output `in rules, if `uri` matches `input`, req's form is updated with `output` """fordatainself.rules:try:inputurl,infos,required_groups=dataexceptValueError:inputurl,infos=datarequired_groups=Noneifrequired_groupsandnotreq.user.matching_groups(required_groups):continueifisinstance(inputurl,basestring):ifinputurl==uri:req.form.update(infos)breakelifinputurl.match(uri):# it's a regexp# XXX what about i18n ? (vtitle for instance)forparam,valueininfos.items():ifisinstance(value,basestring):req.form[param]=inputurl.sub(value,uri)else:req.form[param]=valuebreakelse:self.debug("no simple rewrite rule found for %s",uri)raiseKeyError(uri)returnNone,Nonedefbuild_rset(rql,rgxgroups=None,cachekey=None,setuser=False,vid=None,vtitle=None,form={},**kwargs):defdo_build_rset(inputurl,uri,req,schema):ifrgxgroups:match=inputurl.match(uri)forarg,groupinrgxgroups:kwargs[arg]=match.group(group)req.form.update(form)ifsetuser:kwargs['u']=req.user.eidifvid:req.form['vid']=vidifvtitle:req.form['vtitle']=req._(vtitle)%kwargsreturnNone,req.execute(rql,kwargs,cachekey)returndo_build_rsetdefupdate_form(**kwargs):defdo_build_rset(inputurl,uri,req,schema):match=inputurl.match(uri)kwargs.update(match.groupdict())req.form.update(kwargs)returnNone,Nonereturndo_build_rsetdefrgx_action(rql=None,args=None,cachekey=None,argsgroups=(),setuser=False,form=None,formgroups=(),transforms={},controller=None):defdo_build_rset(inputurl,uri,req,schema,cachekey=cachekey# necessary to avoid UnboundLocalError):ifrql:kwargs=argsandargs.copy()or{}ifargsgroups:ifcachekeyisnotNoneandisinstance(cachekey,basestring):cachekey=(cachekey,)match=inputurl.match(uri)forkeyinargsgroups:value=match.group(key)try:kwargs[key]=transforms[key](value)exceptKeyError:kwargs[key]=valueifcachekeyisnotNoneandkeyincachekey:kwargs[key]=typed_eid(value)ifsetuser:kwargs['u']=req.user.eidrset=req.execute(rql,kwargs,cachekey)else:rset=Noneform2=formandform.copy()or{}ifformgroups:match=inputurl.match(uri)forkeyinformgroups:form2[key]=match.group(key)if"vtitle"inform2:form2['vtitle']=req.__(form2['vtitle'])ifform2:req.form.update(form2)returncontroller,rsetreturndo_build_rsetclassSchemaBasedRewriter(URLRewriter):"""Here, the rules dict maps regexps or plain strings to callbacks that will be called with (input, uri, req, schema) """__regid__='schemabased'rules=[# rgxp : callback(rgx('/search/(.+)'),build_rset(rql=r'Any X WHERE X has_text %(text)s',rgxgroups=[('text',1)])),]defrewrite(self,req,uri):# XXX this could be refacted with SimpleReqRewriterfordatainself.rules:try:inputurl,callback,required_groups=dataexceptValueError:inputurl,callback=datarequired_groups=Noneifrequired_groupsandnotreq.user.matching_groups(required_groups):continueifisinstance(inputurl,basestring):ifinputurl==uri:returncallback(inputurl,uri,req,self._cw.vreg.schema)elifinputurl.match(uri):# it's a regexpreturncallback(inputurl,uri,req,self._cw.vreg.schema)else:self.debug("no schemabased rewrite rule found for %s",uri)raiseKeyError(uri)