Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1464)

Unified Diff: third_party/google-endpoints/google/api/control/path_template.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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])

Powered by Google App Engine
This is Rietveld 408576698