Example #1
0
 def _start(self, namespace=None, opener=None, mode=None):
     """INTERNAL: start parsing but do not yet allocate a frame."""
     if namespace is None:
         namespace = {}
     if mode is None:
         mode = self.PARSE
     self.m_globals = namespace.copy()
     self.m_context = DracoContext()
     self.m_opener = opener
     self.m_mode = mode
     self.m_frames = []
Example #2
0
class DracoParser(Parser):
    """The draco parser."""

    def parse(self, input, namespace=None, opener=None, mode=None):
        """Parse a document."""
        self._start(namespace, opener, mode)
        result = self.include(input)
        return result

    def _start(self, namespace=None, opener=None, mode=None):
        """INTERNAL: start parsing but do not yet allocate a frame."""
        if namespace is None:
            namespace = {}
        if mode is None:
            mode = self.PARSE
        self.m_globals = namespace.copy()
        self.m_context = DracoContext()
        self.m_opener = opener
        self.m_mode = mode
        self.m_frames = []

    def start(self, namespace=None, opener=None, mode=None):
        """Start parsing."""
        self._start(namespace, opener, mode)
        self.m_frames.append(Frame('<feed>'))

    def close(self):
        """Close parsing."""
        self.feed('', eof=True)
        return self._result()

    def _emit(self, data):
        """INTERNAL: emit output data."""
        self.m_frames[-1].result.append(data)

    def _result(self):
        """INTERNAL: return result."""
        return ''.join(self.m_frames[-1].result)

    def include(self, input, **kwargs):
        """Include a document.

        This function can be called by embedded code while parsing.
        """
        if len(self.m_frames) > 20:
            raise ParseError, 'Maximum recursion depth exceeded.'
        if hasattr(input, 'read'):
            fin = input
            fname = '<file object>'
            close = False
        else:
            try:
                if self.m_opener:
                    fin = self.m_opener.open(input)
                    fname = fin.name
                else:
                    fin = file(input, 'rbU')
                    fname = input
            except IOError:
                raise ParseError, 'Could not open input: %s' % input
            close = True
        self.m_frames.append(Frame(fname))
        self.m_frames[-1].locals.update(kwargs)
        while True:
            buf = fin.read(4096)
            if not buf:
                break
            self.feed(buf)
        self.feed(buf, eof=True)
        if close:
            fin.close()
        result = self._result()
        self.m_frames.pop()
        return result

    def _parse_error(self, message):
        """Raise a parse error."""
        error = ParseError(message)
        error.filename = self.m_frames[-1].filename
        error.lineno = self.m_frames[-1].lineno
        error.backtrace = get_backtrace()
        raise error

    # Any character that is valid for XML is valid for us.
    re_valid = re.compile('^[\t\r\n\x20-\xff]*$')
    re_valid_unicode = re.compile(u'^[\t\r\n\x20-\ud7ff\ue000-\ufffd'
                                   '\U00010000-\U0010FFFF]*$')

    def feed(self, data, eof=False):
        """Feed `data' to the parser."""
        if isinstance(data, str):
            validator = self.re_valid
        elif isinstance(data, unicode):
            validator = self.re_valid_unicode
        else:
            m = 'Expecting string or unicode object (got %s).'
            raise TypeError, m % type(data)
        if not validator.match(data):
            raise ParseError, 'Illegal string/unicode input.'
        buffer = self.m_frames[-1].buffer
        buffer += data
        p0 = 0
        while True:
            p1 = buffer.find('<%', p0)
            if p1 == -1:
                if buffer.endswith('<'):
                    p1 = len(buffer) - 1
                else:
                    p1 = len(buffer)
                if self.m_mode in (self.PARSE, self.COLLECT_TEXT):
                    self._emit(buffer[p0:p1])
                p0 = p1
                break
            if self.m_mode in (self.PARSE, self.COLLECT_TEXT):
                self._emit(buffer[p0:p1])
            self.m_frames[-1].lineno += buffer[p0:p1].count('\n')
            p0 = p1
            p1 = buffer.find('%>', p0+2)
            if p1 < 0:
                if not eof:
                    break
                self._parse_error('Premature EOF (unmatched <% tag)')
            if buffer[p0+2] == '@':
                self._parse_directive(buffer[p0+3:p1])
            elif buffer[p0+2] in '=+':
                if self.m_mode == self.PARSE:
                    res = self._parse_expression(buffer[p0+3:p1])
                    if type(res) not in (str, unicode):
                        res = str(res)
                    if buffer[p0+2] == '+':
                        res = res.encode('html')
                    self._emit(res)
                elif self.m_mode == self.COLLECT_CODE:
                    self._emit(dedent(buffer[p0+3:p1]) + '\n')
            else:
                if self.m_mode == self.PARSE:
                    res = self._parse_code(buffer[p0+2:p1])
                    self._emit(res)
                elif self.m_mode == self.COLLECT_CODE:
                    self._emit(dedent(buffer[p0+2:p1]) + '\n')
            self.m_frames[-1].lineno += buffer[p0:p1+2].count('\n')
            p0 = p1+2
        self.m_frames[-1].buffer = buffer[p0:]

    re_directive = re.compile("""
        \s*(?P<name>[a-z_][a-z_0-9:]+)\s*
        (?P<attributes>([a-z_][a-z_0-9:]*\s*=\s*("[^"&<]*"|'[^'&<]*')\s*)*)
        """, re.VERBOSE | re.IGNORECASE)

    re_attribute = re.compile("""
        \s*(?P<name>[a-z_][a-z_0-9:]*)\s*=\s*
        (?P<value>("[^"&<]*"|'[^'&<]*'))
        """, re.VERBOSE | re.IGNORECASE)

    def _parse_directive(self, buffer):
        """Parse an embedded directive: <%@ name [argn="valuen"]... %>."""
        mobj = self.re_directive.match(buffer)
        if not mobj:
            self._parse_error('Parse error in directive.')
        name = mobj.group('name')
        attributes = mobj.group('attributes')
        attrs = {}
        for mobj in self.re_attribute.finditer(attributes):
            # Convert attribute names to plain strings (no unicode) as
            # these are passed as keyword arguments to .include().
            attrs[str(mobj.group('name'))] = mobj.group('value')[1:-1]
        if name == 'include':
            self._directive_include(attrs)
        else:
            self._parse_error('Unknown directive: %s.' % name)

    def _directive_include(self, attrs):
        """Handle an include directive."""
        if attrs.has_key('file'):
            fname = attrs['file']
            del attrs['file']
        elif attrs.has_key('expr'):
            if self.m_mode == self.PARSE:
                fname = self._parse_expression(attrs['expr'])
            elif self.m_mode == self.COLLECT_CODE:
                self._emit(dedent(attrs['expr']) + '\n')
                fname = None
            else:
                fname = None
            del attrs['expr']
        else:
            self._parse_error('Include directive does not specify source.')
        kwargs = {}
        for key,value in attrs.items():
            if self.m_mode == self.PARSE:
                kwargs[key] = self._parse_expression(value)
            elif self.m_mode == self.COLLECT_CODE:
                self._emit(dedent(value) + '\n')
        if fname is not None:
            result = self.include(fname, **kwargs)
            self._emit(result)

    def _parse_expression(self, buffer):
        """Evaluate an expression: <%= expr %>."""
        try:
            result = self.m_context.eval(buffer, self.m_globals,
                                         self.m_frames[-1].locals,
                                         self.m_frames[-1].filename,
                                         self.m_frames[-1].lineno)
        except ParseError:
            raise  # pass through nested exceptions
        except SyntaxError:
            self._parse_error('Syntax error in expression.')
        except StandardError:
            self._parse_error('Uncaught exception in expression.')
        return result

    def _parse_code(self, buffer):
        """Run a clode block: <% code %>."""
        try:
            stdout, stderr = self.m_context.run(buffer, self.m_globals,
                                                self.m_frames[-1].locals,
                                                self.m_frames[-1].filename,
                                                self.m_frames[-1].lineno)
        except ParseError:
            raise
        except SyntaxError:
            self._parse_error('Syntax error in code block.')
        except StandardError:
            self._parse_error('Uncaught exception in code block.')
        return stdout