# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""rest publishing functionscontains some functions and setup of docutils for cubicweb. Provides thefollowing ReST directives:* `eid`, create link to entity in the repository by their eid* `card`, create link to card entity in the repository by their wikiid (proposing to create it when the refered card doesn't exist yet)* `winclude`, reference to a web documentation file (in wdoc/ directories)* `sourcecode` (if pygments is installed), source code colorization"""__docformat__="restructuredtext en"fromcStringIOimportStringIOfromitertoolsimportchainfromloggingimportgetLoggerfromos.pathimportjoinfromdocutilsimportstatemachine,nodes,utils,iofromdocutils.coreimportPublisherfromdocutils.parsers.rstimportParser,states,directivesfromdocutils.parsers.rst.rolesimportregister_canonical_role,set_classesfromlogilab.mtconverterimportESC_UCAR_TABLE,ESC_CAR_TABLE,xml_escapefromcubicwebimportUnknownEidfromcubicweb.ext.html4zopeimportWriterfromcubicweb.web.viewsimportvid_from_rset# XXX better not to import c.w.views here...# We provide our own parser as an attempt to get rid of# state machine reinstanciationimportre# compile states.Body patternsfork,vinstates.Body.patterns.items():ifisinstance(v,str):states.Body.patterns[k]=re.compile(v)# register ReStructured Text mimetype / extensionsimportmimetypesmimetypes.add_type('text/rest','.rest')mimetypes.add_type('text/rest','.rst')LOGGER=getLogger('cubicweb.rest')defeid_reference_role(role,rawtext,text,lineno,inliner,options={},content=[]):try:try:eid_num,rest=text.split(u':',1)exceptValueError:eid_num,rest=text,'#'+texteid_num=int(eid_num)ifeid_num<0:raiseValueErrorexceptValueError:msg=inliner.reporter.error('EID number must be a positive number; "%s" is invalid.'%text,line=lineno)prb=inliner.problematic(rawtext,rawtext,msg)return[prb],[msg]# Base URL mainly used by inliner.pep_reference; so this is correct:context=inliner.document.settings.contexttry:refedentity=context._cw.entity_from_eid(eid_num)exceptUnknownEid:ref='#'rest+=u' '+context._cw._('(UNEXISTANT EID)')else:ref=refedentity.absolute_url()set_classes(options)return[nodes.reference(rawtext,utils.unescape(rest),refuri=ref,**options)],[]defrql_role(role,rawtext,text,lineno,inliner,options={},content=[]):""":rql:`<rql-expr>` or :rql:`<rql-expr>:<vid>` Example: :rql:`Any X,Y WHERE X is CWUser, X login Y:table` Replace the directive with the output of applying the view to the resultset returned by the query. "X eid %(userid)s" can be used in the RQL query for this query will be executed with the argument {'userid': _cw.user.eid}. """_cw=inliner.document.settings.context._cwtext=text.strip()if':'intext:rql,vid=text.rsplit(u':',1)rql=rql.strip()else:rql,vid=text,None_cw.ensure_ro_rql(rql)try:rset=_cw.execute(rql,{'userid':_cw.user.eid})ifrset:ifvidisNone:vid=vid_from_rset(_cw,rset,_cw.vreg.schema)else:vid='noresult'view=_cw.vreg['views'].select(vid,_cw,rset=rset)content=view.render()exceptExceptionasexc:content='an error occured while interpreting this rql directive: %r'%excset_classes(options)return[nodes.raw('',content,format='html')],[]defwinclude_directive(name,arguments,options,content,lineno,content_offset,block_text,state,state_machine):"""Include a reST file as part of the content of this reST file. same as standard include directive but using config.locate_doc_resource to get actual file to include. Most part of this implementation is copied from `include` directive defined in `docutils.parsers.rst.directives.misc` """context=state.document.settings.contextcw=context._cwsource=state_machine.input_lines.source(lineno-state_machine.input_offset-1)#source_dir = os.path.dirname(os.path.abspath(source))fid=arguments[0]forlanginchain((cw.lang,cw.vreg.property_value('ui.language')),cw.vreg.config.available_languages()):rid='%s_%s.rst'%(fid,lang)resourcedir=cw.vreg.config.locate_doc_file(rid)ifresourcedir:breakelse:severe=state_machine.reporter.severe('Problems with "%s" directive path:\nno resource matching %s.'%(name,fid),nodes.literal_block(block_text,block_text),line=lineno)return[severe]path=join(resourcedir,rid)encoding=options.get('encoding',state.document.settings.input_encoding)try:state.document.settings.record_dependencies.add(path)include_file=io.FileInput(source_path=path,encoding=encoding,error_handler=state.document.settings.input_encoding_error_handler,handle_io_errors=None)exceptIOErroraserror:severe=state_machine.reporter.severe('Problems with "%s" directive path:\n%s: %s.'%(name,error.__class__.__name__,error),nodes.literal_block(block_text,block_text),line=lineno)return[severe]try:include_text=include_file.read()exceptUnicodeErroraserror:severe=state_machine.reporter.severe('Problem with "%s" directive:\n%s: %s'%(name,error.__class__.__name__,error),nodes.literal_block(block_text,block_text),line=lineno)return[severe]if'literal'inoptions:literal_block=nodes.literal_block(include_text,include_text,source=path)literal_block.line=1returnliteral_blockelse:include_lines=statemachine.string2lines(include_text,convert_whitespace=1)state_machine.insert_input(include_lines,path)return[]winclude_directive.arguments=(1,0,1)winclude_directive.options={'literal':directives.flag,'encoding':directives.encoding}try:frompygmentsimporthighlightfrompygments.lexersimportget_lexer_by_namefrompygments.formatters.htmlimportHtmlFormatterexceptImportError:pygments_directive=Noneelse:_PYGMENTS_FORMATTER=HtmlFormatter()defpygments_directive(name,arguments,options,content,lineno,content_offset,block_text,state,state_machine):try:lexer=get_lexer_by_name(arguments[0])exceptValueError:# no lexer foundlexer=get_lexer_by_name('text')parsed=highlight(u'\n'.join(content),lexer,_PYGMENTS_FORMATTER)# don't fail if no context set on the sourcecode directivetry:context=state.document.settings.contextcontext._cw.add_css('pygments.css')exceptAttributeError:# used outside cubicweb XXX use hasattr insteadpassreturn[nodes.raw('',parsed,format='html')]pygments_directive.arguments=(1,0,1)pygments_directive.content=1classCubicWebReSTParser(Parser):"""The (customized) reStructuredText parser."""def__init__(self):self.initial_state='Body'self.state_classes=states.state_classesself.inliner=states.Inliner()self.statemachine=states.RSTStateMachine(state_classes=self.state_classes,initial_state=self.initial_state,debug=0)defparse(self,inputstring,document):"""Parse `inputstring` and populate `document`, a document tree."""self.setup_parse(inputstring,document)inputlines=statemachine.string2lines(inputstring,convert_whitespace=1)self.statemachine.run(inputlines,document,inliner=self.inliner)self.finish_parse()# XXX docutils keep a ref on context, can't find a correct way to remove itclassCWReSTPublisher(Publisher):def__init__(self,context,settings,**kwargs):Publisher.__init__(self,**kwargs)self.set_components('standalone','restructuredtext','pseudoxml')self.process_programmatic_settings(None,settings,None)self.settings.context=contextdefrest_publish(context,data):"""publish a string formatted as ReStructured Text to HTML :type context: a cubicweb application object :type data: str :param data: some ReST text :rtype: unicode :return: the data formatted as HTML or the original data if an error occurred """req=context._cwifisinstance(data,unicode):encoding='unicode'# remove unprintable characters unauthorized in xmldata=data.translate(ESC_UCAR_TABLE)else:encoding=req.encoding# remove unprintable characters unauthorized in xmldata=data.translate(ESC_CAR_TABLE)settings={'input_encoding':encoding,'output_encoding':'unicode','warning_stream':StringIO(),'traceback':True,# don't sys.exit'stylesheet':None,# don't try to embed stylesheet (may cause# obscure bug due to docutils computing# relative path according to the directory# used *at import time*# dunno what's the max, severe is 4, and we never want a crash# (though try/except may be a better option...). May be the# above traceback option will avoid this?'halt_level':10,}ifcontext:ifhasattr(req,'url'):base_url=req.url()elifhasattr(context,'absolute_url'):base_url=context.absolute_url()else:base_url=req.base_url()else:base_url=Nonetry:pub=CWReSTPublisher(context,settings,parser=CubicWebReSTParser(),writer=Writer(base_url=base_url),source_class=io.StringInput,destination_class=io.StringOutput)pub.set_source(data)pub.set_destination()res=pub.publish(enable_exit_status=None)# necessary for proper garbage collection, else a ref is kept somewhere in docutils...delpub.settings.contextreturnresexceptException:LOGGER.exception('error while publishing ReST text')ifnotisinstance(data,unicode):data=unicode(data,encoding,'replace')returnxml_escape(req._('error while publishing ReST text')+'\n\n'+data)_INITIALIZED=Falsedefcw_rest_init():global_INITIALIZEDif_INITIALIZED:return_INITIALIZED=Trueregister_canonical_role('eid',eid_reference_role)register_canonical_role('rql',rql_role)directives.register_directive('winclude',winclude_directive)ifpygments_directiveisnotNone:directives.register_directive('sourcecode',pygments_directive)