common/rest.py
branchtls-sprint
changeset 704 0c2c8f0a6ded
parent 703 413675c2d46c
child 705 937dd226f72a
equal deleted inserted replaced
703:413675c2d46c 704:0c2c8f0a6ded
     1 """rest publishing functions
       
     2 
       
     3 contains some functions and setup of docutils for cubicweb
       
     4 
       
     5 :organization: Logilab
       
     6 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     8 """
       
     9 __docformat__ = "restructuredtext en"
       
    10 
       
    11 from cStringIO import StringIO
       
    12 from itertools import chain
       
    13 from logging import getLogger
       
    14 from os.path import join
       
    15 
       
    16 from docutils import statemachine, nodes, utils, io
       
    17 from docutils.core import publish_string
       
    18 from docutils.parsers.rst import Parser, states, directives
       
    19 from docutils.parsers.rst.roles import register_canonical_role, set_classes
       
    20 
       
    21 from logilab.mtconverter import html_escape
       
    22 
       
    23 from cubicweb.common.html4zope import Writer
       
    24 
       
    25 # We provide our own parser as an attempt to get rid of
       
    26 # state machine reinstanciation
       
    27 
       
    28 import re
       
    29 # compile states.Body patterns
       
    30 for k, v in states.Body.patterns.items():
       
    31     if isinstance(v, str):
       
    32         states.Body.patterns[k] = re.compile(v)
       
    33 
       
    34 # register ReStructured Text mimetype / extensions
       
    35 import mimetypes
       
    36 mimetypes.add_type('text/rest', '.rest')
       
    37 mimetypes.add_type('text/rest', '.rst')
       
    38 
       
    39 
       
    40 LOGGER = getLogger('cubicweb.rest')
       
    41 
       
    42 def eid_reference_role(role, rawtext, text, lineno, inliner,
       
    43                        options={}, content=[]):
       
    44     try:
       
    45         try:
       
    46             eid_num, rest = text.split(u':', 1)
       
    47         except:
       
    48             eid_num, rest = text, '#'+text
       
    49         eid_num = int(eid_num)
       
    50         if eid_num < 0:
       
    51             raise ValueError
       
    52     except ValueError:
       
    53         msg = inliner.reporter.error(
       
    54             'EID number must be a positive number; "%s" is invalid.'
       
    55             % text, line=lineno)
       
    56         prb = inliner.problematic(rawtext, rawtext, msg)
       
    57         return [prb], [msg]
       
    58     # Base URL mainly used by inliner.pep_reference; so this is correct:
       
    59     context = inliner.document.settings.context
       
    60     refedentity = context.req.eid_rset(eid_num).get_entity(0, 0)
       
    61     ref = refedentity.absolute_url()
       
    62     set_classes(options)
       
    63     return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
       
    64                             **options)], []
       
    65 
       
    66 register_canonical_role('eid', eid_reference_role)
       
    67 
       
    68 
       
    69 def card_reference_role(role, rawtext, text, lineno, inliner,
       
    70                        options={}, content=[]):
       
    71     text = text.strip()
       
    72     try:
       
    73         wikiid, rest = text.split(u':', 1)
       
    74     except:
       
    75         wikiid, rest = text, text
       
    76     context = inliner.document.settings.context
       
    77     cardrset = context.req.execute('Card X WHERE X wikiid %(id)s',
       
    78                                    {'id': wikiid})
       
    79     if cardrset:
       
    80         ref = cardrset.get_entity(0, 0).absolute_url()
       
    81     else:
       
    82         schema = context.schema
       
    83         if schema.eschema('Card').has_perm(context.req, 'add'):
       
    84             ref = context.req.build_url('view', vid='creation', etype='Card', wikiid=wikiid)
       
    85         else:
       
    86             ref = '#'
       
    87     set_classes(options)
       
    88     return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
       
    89                             **options)], []
       
    90 
       
    91 register_canonical_role('card', card_reference_role)
       
    92 
       
    93 
       
    94 def winclude_directive(name, arguments, options, content, lineno,
       
    95                        content_offset, block_text, state, state_machine):
       
    96     """Include a reST file as part of the content of this reST file.
       
    97 
       
    98     same as standard include directive but using config.locate_doc_resource to
       
    99     get actual file to include.
       
   100 
       
   101     Most part of this implementation is copied from `include` directive defined
       
   102     in `docutils.parsers.rst.directives.misc`
       
   103     """
       
   104     context = state.document.settings.context
       
   105     source = state_machine.input_lines.source(
       
   106         lineno - state_machine.input_offset - 1)
       
   107     #source_dir = os.path.dirname(os.path.abspath(source))
       
   108     fid = arguments[0]
       
   109     for lang in chain((context.req.lang, context.vreg.property_value('ui.language')),
       
   110                       context.config.available_languages()):
       
   111         rid = '%s_%s.rst' % (fid, lang)
       
   112         resourcedir = context.config.locate_doc_file(rid)
       
   113         if resourcedir:
       
   114             break
       
   115     else:
       
   116         severe = state_machine.reporter.severe(
       
   117               'Problems with "%s" directive path:\nno resource matching %s.'
       
   118               % (name, fid),
       
   119               nodes.literal_block(block_text, block_text), line=lineno)
       
   120         return [severe]
       
   121     path = join(resourcedir, rid)
       
   122     encoding = options.get('encoding', state.document.settings.input_encoding)
       
   123     try:
       
   124         state.document.settings.record_dependencies.add(path)
       
   125         include_file = io.FileInput(
       
   126             source_path=path, encoding=encoding,
       
   127             error_handler=state.document.settings.input_encoding_error_handler,
       
   128             handle_io_errors=None)
       
   129     except IOError, error:
       
   130         severe = state_machine.reporter.severe(
       
   131               'Problems with "%s" directive path:\n%s: %s.'
       
   132               % (name, error.__class__.__name__, error),
       
   133               nodes.literal_block(block_text, block_text), line=lineno)
       
   134         return [severe]
       
   135     try:
       
   136         include_text = include_file.read()
       
   137     except UnicodeError, error:
       
   138         severe = state_machine.reporter.severe(
       
   139               'Problem with "%s" directive:\n%s: %s'
       
   140               % (name, error.__class__.__name__, error),
       
   141               nodes.literal_block(block_text, block_text), line=lineno)
       
   142         return [severe]
       
   143     if options.has_key('literal'):
       
   144         literal_block = nodes.literal_block(include_text, include_text,
       
   145                                             source=path)
       
   146         literal_block.line = 1
       
   147         return literal_block
       
   148     else:
       
   149         include_lines = statemachine.string2lines(include_text,
       
   150                                                   convert_whitespace=1)
       
   151         state_machine.insert_input(include_lines, path)
       
   152         return []
       
   153 
       
   154 winclude_directive.arguments = (1, 0, 1)
       
   155 winclude_directive.options = {'literal': directives.flag,
       
   156                               'encoding': directives.encoding}
       
   157 directives.register_directive('winclude', winclude_directive)
       
   158 
       
   159 class CubicWebReSTParser(Parser):
       
   160     """The (customized) reStructuredText parser."""
       
   161 
       
   162     def __init__(self):
       
   163         self.initial_state = 'Body'
       
   164         self.state_classes = states.state_classes
       
   165         self.inliner = states.Inliner()
       
   166         self.statemachine = states.RSTStateMachine(
       
   167               state_classes=self.state_classes,
       
   168               initial_state=self.initial_state,
       
   169               debug=0)
       
   170 
       
   171     def parse(self, inputstring, document):
       
   172         """Parse `inputstring` and populate `document`, a document tree."""
       
   173         self.setup_parse(inputstring, document)
       
   174         inputlines = statemachine.string2lines(inputstring,
       
   175                                                convert_whitespace=1)
       
   176         self.statemachine.run(inputlines, document, inliner=self.inliner)
       
   177         self.finish_parse()
       
   178 
       
   179 
       
   180 _REST_PARSER = CubicWebReSTParser()
       
   181 
       
   182 def rest_publish(context, data):
       
   183     """publish a string formatted as ReStructured Text to HTML
       
   184     
       
   185     :type context: a cubicweb application object
       
   186 
       
   187     :type data: str
       
   188     :param data: some ReST text
       
   189 
       
   190     :rtype: unicode
       
   191     :return:
       
   192       the data formatted as HTML or the original data if an error occured
       
   193     """
       
   194     req = context.req
       
   195     if isinstance(data, unicode):
       
   196         encoding = 'unicode'
       
   197     else:
       
   198         encoding = req.encoding
       
   199     settings = {'input_encoding': encoding, 'output_encoding': 'unicode',
       
   200                 'warning_stream': StringIO(), 'context': context,
       
   201                 # dunno what's the max, severe is 4, and we never want a crash
       
   202                 # (though try/except may be a better option...)
       
   203                 'halt_level': 10, 
       
   204                 }
       
   205     if context:
       
   206         if hasattr(req, 'url'):
       
   207             base_url = req.url()
       
   208         elif hasattr(context, 'absolute_url'):
       
   209             base_url = context.absolute_url()
       
   210         else:
       
   211             base_url = req.base_url()
       
   212     else:
       
   213         base_url = None
       
   214     try:
       
   215         return publish_string(writer=Writer(base_url=base_url),
       
   216                               parser=_REST_PARSER, source=data,
       
   217                               settings_overrides=settings)
       
   218     except Exception:
       
   219         LOGGER.exception('error while publishing ReST text')
       
   220         if not isinstance(data, unicode):
       
   221             data = unicode(data, encoding, 'replace')
       
   222         return html_escape(req._('error while publishing ReST text')
       
   223                            + '\n\n' + data)