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

Side by Side 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, 10 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 unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2016 Google Inc. All Rights Reserved.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """Implements a utility for parsing and formatting path templates."""
16
17 from __future__ import absolute_import
18 from collections import namedtuple
19
20 from ply import lex, yacc
21
22 _BINDING = 1
23 _END_BINDING = 2
24 _TERMINAL = 3
25 _Segment = namedtuple('_Segment', ['kind', 'literal'])
26
27
28 def _format(segments):
29 template = ''
30 slash = True
31 for segment in segments:
32 if segment.kind == _TERMINAL:
33 if slash:
34 template += '/'
35 template += segment.literal
36 slash = True
37 if segment.kind == _BINDING:
38 template += '/{%s=' % segment.literal
39 slash = False
40 if segment.kind == _END_BINDING:
41 template += '%s}' % segment.literal
42 return template[1:] # Remove the leading /
43
44
45 class ValidationException(Exception):
46 """Represents a path template validation error."""
47 pass
48
49
50 class PathTemplate(object):
51 """Represents a path template."""
52
53 segments = None
54 segment_count = 0
55
56 def __init__(self, data):
57 parser = _Parser()
58 self.segments = parser.parse(data)
59 self.verb = parser.verb
60 self.segment_count = parser.segment_count
61
62 def __len__(self):
63 return self.segment_count
64
65 def __repr__(self):
66 return _format(self.segments)
67
68 def render(self, bindings):
69 """Renders a string from a path template using the provided bindings.
70
71 Args:
72 bindings (dict): A dictionary of var names to binding strings.
73
74 Returns:
75 str: The rendered instantiation of this path template.
76
77 Raises:
78 ValidationError: If a key isn't provided or if a sub-template can't
79 be parsed.
80 """
81 out = []
82 binding = False
83 for segment in self.segments:
84 if segment.kind == _BINDING:
85 if segment.literal not in bindings:
86 raise ValidationException(
87 ('rendering error: value for key \'{}\' '
88 'not provided').format(segment.literal))
89 out.extend(PathTemplate(bindings[segment.literal]).segments)
90 binding = True
91 elif segment.kind == _END_BINDING:
92 binding = False
93 else:
94 if binding:
95 continue
96 out.append(segment)
97 path = _format(out)
98 self.match(path)
99 return path
100
101 def match(self, path):
102 """Matches a fully qualified path template string.
103
104 Args:
105 path (str): A fully qualified path template string.
106
107 Returns:
108 dict: Var names to matched binding values.
109
110 Raises:
111 ValidationException: If path can't be matched to the template.
112 """
113 this = self.segments
114 that = path.split('/')
115 current_var = None
116 bindings = {}
117 segment_count = self.segment_count
118 j = 0
119 for i in range(0, len(this)):
120 if j >= len(that):
121 break
122 if this[i].kind == _TERMINAL:
123 if this[i].literal == '*':
124 bindings[current_var] = that[j]
125 j += 1
126 elif this[i].literal == '**':
127 until = j + len(that) - segment_count + 1
128 segment_count += len(that) - segment_count
129 bindings[current_var] = '/'.join(that[j:until])
130 j = until
131 elif this[i].literal != that[j]:
132 raise ValidationException(
133 'mismatched literal: \'%s\' != \'%s\'' % (
134 this[i].literal, that[j]))
135 else:
136 j += 1
137 elif this[i].kind == _BINDING:
138 current_var = this[i].literal
139 if j != len(that) or j != segment_count:
140 raise ValidationException(
141 'match error: could not render from the path template: {}'
142 .format(path))
143 return bindings
144
145
146 # pylint: disable=C0103
147 # pylint: disable=R0201
148 class _Parser(object):
149 tokens = (
150 'FORWARD_SLASH',
151 'LEFT_BRACE',
152 'RIGHT_BRACE',
153 'EQUALS',
154 'WILDCARD',
155 'PATH_WILDCARD',
156 'LITERAL',
157 )
158
159 t_FORWARD_SLASH = r'/'
160 t_LEFT_BRACE = r'\{'
161 t_RIGHT_BRACE = r'\}'
162 t_EQUALS = r'='
163 t_WILDCARD = r'\*'
164 t_PATH_WILDCARD = r'\*\*'
165 t_LITERAL = r'[^*=}{\/]+'
166
167 t_ignore = ' \t'
168
169 def __init__(self):
170 self.lexer = lex.lex(module=self)
171 self.parser = yacc.yacc(module=self, debug=False, write_tables=False)
172 self.verb = ''
173 self.binding_var_count = 0
174 self.segment_count = 0
175
176 def parse(self, data):
177 """Returns a list of path template segments parsed from data.
178
179 Args:
180 data: A path template string.
181 Returns:
182 A list of _Segment.
183 """
184 self.binding_var_count = 0
185 self.segment_count = 0
186
187 segments = self.parser.parse(data)
188 # Validation step: checks that there are no nested bindings.
189 path_wildcard = False
190 for segment in segments:
191 if segment.kind == _TERMINAL and segment.literal == '**':
192 if path_wildcard:
193 raise ValidationException(
194 'validation error: path template cannot contain more '
195 'than one path wildcard')
196 path_wildcard = True
197 if segments and segments[-1].kind == _TERMINAL:
198 final_term = segments[-1].literal
199 last_colon_pos = final_term.rfind(':')
200 if last_colon_pos != -1:
201 self.verb = final_term[last_colon_pos + 1:]
202 segments[-1] = _Segment(_TERMINAL, final_term[:last_colon_pos])
203
204 return segments
205
206 def p_template(self, p):
207 """template : FORWARD_SLASH bound_segments
208 | bound_segments"""
209 # ply fails on a negative index.
210 p[0] = p[len(p) - 1]
211
212 def p_bound_segments(self, p):
213 """bound_segments : bound_segment FORWARD_SLASH bound_segments
214 | bound_segment"""
215 p[0] = p[1]
216 if len(p) > 2:
217 p[0].extend(p[3])
218
219 def p_unbound_segments(self, p):
220 """unbound_segments : unbound_terminal FORWARD_SLASH unbound_segments
221 | unbound_terminal"""
222 p[0] = p[1]
223 if len(p) > 2:
224 p[0].extend(p[3])
225
226 def p_bound_segment(self, p):
227 """bound_segment : bound_terminal
228 | variable"""
229 p[0] = p[1]
230
231 def p_unbound_terminal(self, p):
232 """unbound_terminal : WILDCARD
233 | PATH_WILDCARD
234 | LITERAL"""
235 p[0] = [_Segment(_TERMINAL, p[1])]
236 self.segment_count += 1
237
238 def p_bound_terminal(self, p):
239 """bound_terminal : unbound_terminal"""
240 if p[1][0].literal in ['*', '**']:
241 p[0] = [_Segment(_BINDING, '$%d' % self.binding_var_count),
242 p[1][0],
243 _Segment(_END_BINDING, '')]
244 self.binding_var_count += 1
245 else:
246 p[0] = p[1]
247
248 def p_variable(self, p):
249 """variable : LEFT_BRACE LITERAL EQUALS unbound_segments RIGHT_BRACE
250 | LEFT_BRACE LITERAL RIGHT_BRACE"""
251 p[0] = [_Segment(_BINDING, p[2])]
252 if len(p) > 4:
253 p[0].extend(p[4])
254 else:
255 p[0].append(_Segment(_TERMINAL, '*'))
256 self.segment_count += 1
257 p[0].append(_Segment(_END_BINDING, ''))
258
259 def p_error(self, p):
260 """Raises a parser error."""
261 if p:
262 raise ValidationException(
263 'parser error: unexpected token \'%s\'' % p.type)
264 else:
265 raise ValidationException('parser error: unexpected EOF')
266
267 def t_error(self, t):
268 """Raises a lexer error."""
269 raise ValidationException(
270 'lexer error: illegal character \'%s\'' % t.value[0])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698