| Index: third_party/google-endpoints/google/api/control/path_template.py
|
| diff --git a/third_party/google-endpoints/google/api/control/path_template.py b/third_party/google-endpoints/google/api/control/path_template.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..620b9c8e487cc9fa28baa99105ef19edd11d3878
|
| --- /dev/null
|
| +++ b/third_party/google-endpoints/google/api/control/path_template.py
|
| @@ -0,0 +1,270 @@
|
| +# Copyright 2016 Google Inc. All Rights Reserved.
|
| +#
|
| +# Licensed under the Apache License, Version 2.0 (the "License");
|
| +# you may not use this file except in compliance with the License.
|
| +# You may obtain a copy of the License at
|
| +#
|
| +# http://www.apache.org/licenses/LICENSE-2.0
|
| +#
|
| +# Unless required by applicable law or agreed to in writing, software
|
| +# distributed under the License is distributed on an "AS IS" BASIS,
|
| +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| +# See the License for the specific language governing permissions and
|
| +# limitations under the License.
|
| +
|
| +"""Implements a utility for parsing and formatting path templates."""
|
| +
|
| +from __future__ import absolute_import
|
| +from collections import namedtuple
|
| +
|
| +from ply import lex, yacc
|
| +
|
| +_BINDING = 1
|
| +_END_BINDING = 2
|
| +_TERMINAL = 3
|
| +_Segment = namedtuple('_Segment', ['kind', 'literal'])
|
| +
|
| +
|
| +def _format(segments):
|
| + template = ''
|
| + slash = True
|
| + for segment in segments:
|
| + if segment.kind == _TERMINAL:
|
| + if slash:
|
| + template += '/'
|
| + template += segment.literal
|
| + slash = True
|
| + if segment.kind == _BINDING:
|
| + template += '/{%s=' % segment.literal
|
| + slash = False
|
| + if segment.kind == _END_BINDING:
|
| + template += '%s}' % segment.literal
|
| + return template[1:] # Remove the leading /
|
| +
|
| +
|
| +class ValidationException(Exception):
|
| + """Represents a path template validation error."""
|
| + pass
|
| +
|
| +
|
| +class PathTemplate(object):
|
| + """Represents a path template."""
|
| +
|
| + segments = None
|
| + segment_count = 0
|
| +
|
| + def __init__(self, data):
|
| + parser = _Parser()
|
| + self.segments = parser.parse(data)
|
| + self.verb = parser.verb
|
| + self.segment_count = parser.segment_count
|
| +
|
| + def __len__(self):
|
| + return self.segment_count
|
| +
|
| + def __repr__(self):
|
| + return _format(self.segments)
|
| +
|
| + def render(self, bindings):
|
| + """Renders a string from a path template using the provided bindings.
|
| +
|
| + Args:
|
| + bindings (dict): A dictionary of var names to binding strings.
|
| +
|
| + Returns:
|
| + str: The rendered instantiation of this path template.
|
| +
|
| + Raises:
|
| + ValidationError: If a key isn't provided or if a sub-template can't
|
| + be parsed.
|
| + """
|
| + out = []
|
| + binding = False
|
| + for segment in self.segments:
|
| + if segment.kind == _BINDING:
|
| + if segment.literal not in bindings:
|
| + raise ValidationException(
|
| + ('rendering error: value for key \'{}\' '
|
| + 'not provided').format(segment.literal))
|
| + out.extend(PathTemplate(bindings[segment.literal]).segments)
|
| + binding = True
|
| + elif segment.kind == _END_BINDING:
|
| + binding = False
|
| + else:
|
| + if binding:
|
| + continue
|
| + out.append(segment)
|
| + path = _format(out)
|
| + self.match(path)
|
| + return path
|
| +
|
| + def match(self, path):
|
| + """Matches a fully qualified path template string.
|
| +
|
| + Args:
|
| + path (str): A fully qualified path template string.
|
| +
|
| + Returns:
|
| + dict: Var names to matched binding values.
|
| +
|
| + Raises:
|
| + ValidationException: If path can't be matched to the template.
|
| + """
|
| + this = self.segments
|
| + that = path.split('/')
|
| + current_var = None
|
| + bindings = {}
|
| + segment_count = self.segment_count
|
| + j = 0
|
| + for i in range(0, len(this)):
|
| + if j >= len(that):
|
| + break
|
| + if this[i].kind == _TERMINAL:
|
| + if this[i].literal == '*':
|
| + bindings[current_var] = that[j]
|
| + j += 1
|
| + elif this[i].literal == '**':
|
| + until = j + len(that) - segment_count + 1
|
| + segment_count += len(that) - segment_count
|
| + bindings[current_var] = '/'.join(that[j:until])
|
| + j = until
|
| + elif this[i].literal != that[j]:
|
| + raise ValidationException(
|
| + 'mismatched literal: \'%s\' != \'%s\'' % (
|
| + this[i].literal, that[j]))
|
| + else:
|
| + j += 1
|
| + elif this[i].kind == _BINDING:
|
| + current_var = this[i].literal
|
| + if j != len(that) or j != segment_count:
|
| + raise ValidationException(
|
| + 'match error: could not render from the path template: {}'
|
| + .format(path))
|
| + return bindings
|
| +
|
| +
|
| +# pylint: disable=C0103
|
| +# pylint: disable=R0201
|
| +class _Parser(object):
|
| + tokens = (
|
| + 'FORWARD_SLASH',
|
| + 'LEFT_BRACE',
|
| + 'RIGHT_BRACE',
|
| + 'EQUALS',
|
| + 'WILDCARD',
|
| + 'PATH_WILDCARD',
|
| + 'LITERAL',
|
| + )
|
| +
|
| + t_FORWARD_SLASH = r'/'
|
| + t_LEFT_BRACE = r'\{'
|
| + t_RIGHT_BRACE = r'\}'
|
| + t_EQUALS = r'='
|
| + t_WILDCARD = r'\*'
|
| + t_PATH_WILDCARD = r'\*\*'
|
| + t_LITERAL = r'[^*=}{\/]+'
|
| +
|
| + t_ignore = ' \t'
|
| +
|
| + def __init__(self):
|
| + self.lexer = lex.lex(module=self)
|
| + self.parser = yacc.yacc(module=self, debug=False, write_tables=False)
|
| + self.verb = ''
|
| + self.binding_var_count = 0
|
| + self.segment_count = 0
|
| +
|
| + def parse(self, data):
|
| + """Returns a list of path template segments parsed from data.
|
| +
|
| + Args:
|
| + data: A path template string.
|
| + Returns:
|
| + A list of _Segment.
|
| + """
|
| + self.binding_var_count = 0
|
| + self.segment_count = 0
|
| +
|
| + segments = self.parser.parse(data)
|
| + # Validation step: checks that there are no nested bindings.
|
| + path_wildcard = False
|
| + for segment in segments:
|
| + if segment.kind == _TERMINAL and segment.literal == '**':
|
| + if path_wildcard:
|
| + raise ValidationException(
|
| + 'validation error: path template cannot contain more '
|
| + 'than one path wildcard')
|
| + path_wildcard = True
|
| + if segments and segments[-1].kind == _TERMINAL:
|
| + final_term = segments[-1].literal
|
| + last_colon_pos = final_term.rfind(':')
|
| + if last_colon_pos != -1:
|
| + self.verb = final_term[last_colon_pos + 1:]
|
| + segments[-1] = _Segment(_TERMINAL, final_term[:last_colon_pos])
|
| +
|
| + return segments
|
| +
|
| + def p_template(self, p):
|
| + """template : FORWARD_SLASH bound_segments
|
| + | bound_segments"""
|
| + # ply fails on a negative index.
|
| + p[0] = p[len(p) - 1]
|
| +
|
| + def p_bound_segments(self, p):
|
| + """bound_segments : bound_segment FORWARD_SLASH bound_segments
|
| + | bound_segment"""
|
| + p[0] = p[1]
|
| + if len(p) > 2:
|
| + p[0].extend(p[3])
|
| +
|
| + def p_unbound_segments(self, p):
|
| + """unbound_segments : unbound_terminal FORWARD_SLASH unbound_segments
|
| + | unbound_terminal"""
|
| + p[0] = p[1]
|
| + if len(p) > 2:
|
| + p[0].extend(p[3])
|
| +
|
| + def p_bound_segment(self, p):
|
| + """bound_segment : bound_terminal
|
| + | variable"""
|
| + p[0] = p[1]
|
| +
|
| + def p_unbound_terminal(self, p):
|
| + """unbound_terminal : WILDCARD
|
| + | PATH_WILDCARD
|
| + | LITERAL"""
|
| + p[0] = [_Segment(_TERMINAL, p[1])]
|
| + self.segment_count += 1
|
| +
|
| + def p_bound_terminal(self, p):
|
| + """bound_terminal : unbound_terminal"""
|
| + if p[1][0].literal in ['*', '**']:
|
| + p[0] = [_Segment(_BINDING, '$%d' % self.binding_var_count),
|
| + p[1][0],
|
| + _Segment(_END_BINDING, '')]
|
| + self.binding_var_count += 1
|
| + else:
|
| + p[0] = p[1]
|
| +
|
| + def p_variable(self, p):
|
| + """variable : LEFT_BRACE LITERAL EQUALS unbound_segments RIGHT_BRACE
|
| + | LEFT_BRACE LITERAL RIGHT_BRACE"""
|
| + p[0] = [_Segment(_BINDING, p[2])]
|
| + if len(p) > 4:
|
| + p[0].extend(p[4])
|
| + else:
|
| + p[0].append(_Segment(_TERMINAL, '*'))
|
| + self.segment_count += 1
|
| + p[0].append(_Segment(_END_BINDING, ''))
|
| +
|
| + def p_error(self, p):
|
| + """Raises a parser error."""
|
| + if p:
|
| + raise ValidationException(
|
| + 'parser error: unexpected token \'%s\'' % p.type)
|
| + else:
|
| + raise ValidationException('parser error: unexpected EOF')
|
| +
|
| + def t_error(self, t):
|
| + """Raises a lexer error."""
|
| + raise ValidationException(
|
| + 'lexer error: illegal character \'%s\'' % t.value[0])
|
|
|