Mercurial > piecrust2
comparison piecrust/routing.py @ 723:606f6d57b5df
routing: Cleanup URL routing and improve page matching.
* Add new types of route parameters for integers (int4, int2, int).
* Remove hard-coded hacks around converting year/month/day values.
* Make the blog post routes use the new typed parameters.
* Fix problems with matching routes with integer parameters when they can
get confused with a sub-page number.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Sun, 29 May 2016 20:19:28 -0700 |
parents | ab5c6a8ae90a |
children | 8c3c2b949b82 |
comparison
equal
deleted
inserted
replaced
722:f0a3af3fbea2 | 723:606f6d57b5df |
---|---|
7 | 7 |
8 | 8 |
9 logger = logging.getLogger(__name__) | 9 logger = logging.getLogger(__name__) |
10 | 10 |
11 | 11 |
12 route_re = re.compile(r'%((?P<qual>path):)?(?P<name>\w+)%') | 12 route_re = re.compile(r'%((?P<qual>[\w\d]+):)?(?P<name>\w+)%') |
13 route_esc_re = re.compile(r'\\%((?P<qual>path)\\:)?(?P<name>\w+)\\%') | 13 route_esc_re = re.compile(r'\\%((?P<qual>[\w\d]+)\\:)?(?P<name>\w+)\\%') |
14 template_func_re = re.compile(r'^(?P<name>\w+)\((?P<args>.*)\)\s*$') | 14 template_func_re = re.compile(r'^(?P<name>\w+)\((?P<args>.*)\)\s*$') |
15 template_func_arg_re = re.compile(r'(?P<arg>\+?\w+)') | 15 template_func_arg_re = re.compile(r'(?P<arg>\+?\w+)') |
16 ugly_url_cleaner = re.compile(r'\.html$') | 16 ugly_url_cleaner = re.compile(r'\.html$') |
17 | 17 |
18 | 18 |
25 | 25 |
26 | 26 |
27 def create_route_metadata(page): | 27 def create_route_metadata(page): |
28 route_metadata = copy.deepcopy(page.source_metadata) | 28 route_metadata = copy.deepcopy(page.source_metadata) |
29 route_metadata.update(page.getRouteMetadata()) | 29 route_metadata.update(page.getRouteMetadata()) |
30 | |
31 # TODO: fix this hard-coded shit | |
32 for key in ['year', 'month', 'day']: | |
33 if key in route_metadata and isinstance(route_metadata[key], str): | |
34 route_metadata[key] = int(route_metadata[key]) | |
35 | |
36 return route_metadata | 30 return route_metadata |
37 | 31 |
38 | 32 |
39 class IRouteMetadataProvider(object): | 33 class IRouteMetadataProvider(object): |
40 def getRouteMetadata(self): | 34 def getRouteMetadata(self): |
72 | 66 |
73 # Get the straight-forward regex for matching this URI pattern. | 67 # Get the straight-forward regex for matching this URI pattern. |
74 p = route_esc_re.sub(self._uriPatternRepl, | 68 p = route_esc_re.sub(self._uriPatternRepl, |
75 re.escape(self.uri_pattern)) + '$' | 69 re.escape(self.uri_pattern)) + '$' |
76 self.uri_re = re.compile(p) | 70 self.uri_re = re.compile(p) |
71 | |
72 # Get the types of the route parameters. | |
73 self.param_types = {} | |
74 for m in route_re.finditer(self.uri_pattern): | |
75 qual = m.group('qual') | |
76 if qual: | |
77 self.param_types[str(m.group('name'))] = qual | |
77 | 78 |
78 # If the URI pattern has a 'path'-type component, we'll need to match | 79 # If the URI pattern has a 'path'-type component, we'll need to match |
79 # the versions for which that component is empty. So for instance if | 80 # the versions for which that component is empty. So for instance if |
80 # we have `/foo/%path:bar%`, we may need to match `/foo` (note the | 81 # we have `/foo/%path:bar%`, we may need to match `/foo` (note the |
81 # lack of a trailing slash). We have to build a special pattern (in | 82 # lack of a trailing slash). We have to build a special pattern (in |
172 matched_keys = set(route_metadata.keys()) | 173 matched_keys = set(route_metadata.keys()) |
173 missing_keys = self.required_route_metadata - matched_keys | 174 missing_keys = self.required_route_metadata - matched_keys |
174 for k in missing_keys: | 175 for k in missing_keys: |
175 route_metadata[k] = '' | 176 route_metadata[k] = '' |
176 | 177 |
177 # TODO: fix this hard-coded shit | 178 for k in route_metadata: |
178 for key in ['year', 'month', 'day']: | 179 route_metadata[k] = self._coerceRouteParameter( |
179 if key in route_metadata and isinstance(route_metadata[key], str): | 180 k, route_metadata[k]) |
180 try: | |
181 route_metadata[key] = int(route_metadata[key]) | |
182 except ValueError: | |
183 pass | |
184 | 181 |
185 return route_metadata | 182 return route_metadata |
186 | 183 |
187 def getUri(self, route_metadata, *, sub_num=1): | 184 def getUri(self, route_metadata, *, sub_num=1): |
185 route_metadata = dict(route_metadata) | |
186 for k in route_metadata: | |
187 route_metadata[k] = self._coerceRouteParameter( | |
188 k, route_metadata[k]) | |
189 | |
188 uri = self.uri_format % route_metadata | 190 uri = self.uri_format % route_metadata |
189 suffix = None | 191 suffix = None |
190 if sub_num > 1: | 192 if sub_num > 1: |
191 # Note that we know the pagination suffix starts with a slash. | 193 # Note that we know the pagination suffix starts with a slash. |
192 suffix = self.pagination_suffix_format % {'num': sub_num} | 194 suffix = self.pagination_suffix_format % {'num': sub_num} |
228 uri += '?!debug' | 230 uri += '?!debug' |
229 | 231 |
230 return uri | 232 return uri |
231 | 233 |
232 def _uriFormatRepl(self, m): | 234 def _uriFormatRepl(self, m): |
235 qual = m.group('qual') | |
233 name = m.group('name') | 236 name = m.group('name') |
234 #TODO: fix this hard-coded shit | 237 if qual == 'int4': |
235 if name == 'year': | 238 return '%%(%s)04d' % name |
236 return '%(year)04d' | 239 elif qual == 'int2': |
237 if name == 'month': | 240 return '%%(%s)02d' % name |
238 return '%(month)02d' | 241 return '%%(%s)s' % name |
239 if name == 'day': | |
240 return '%(day)02d' | |
241 return '%(' + name + ')s' | |
242 | 242 |
243 def _uriPatternRepl(self, m): | 243 def _uriPatternRepl(self, m): |
244 name = m.group('name') | 244 name = m.group('name') |
245 qualifier = m.group('qual') | 245 qual = m.group('qual') |
246 if qualifier == 'path': | 246 if qual == 'path': |
247 return r'(?P<%s>[^\?]*)' % name | 247 return r'(?P<%s>[^\?]*)' % name |
248 elif qual == 'int4': | |
249 return r'(?P<%s>\d{4})' % name | |
250 elif qual == 'int2': | |
251 return r'(?P<%s>\d{2})' % name | |
248 return r'(?P<%s>[^/\?]+)' % name | 252 return r'(?P<%s>[^/\?]+)' % name |
249 | 253 |
250 def _uriNoPathRepl(self, m): | 254 def _uriNoPathRepl(self, m): |
251 name = m.group('name') | 255 name = m.group('name') |
252 qualifier = m.group('qual') | 256 qualifier = m.group('qual') |
253 if qualifier == 'path': | 257 if qualifier == 'path': |
254 return '' | 258 return '' |
255 return r'(?P<%s>[^/\?]+)' % name | 259 return r'(?P<%s>[^/\?]+)' % name |
260 | |
261 def _coerceRouteParameter(self, name, val): | |
262 param_type = self.param_types.get(name) | |
263 if param_type is None: | |
264 return val | |
265 if param_type in ['int', 'int2', 'int4']: | |
266 try: | |
267 return int(val) | |
268 except ValueError: | |
269 raise Exception( | |
270 "Expected route parameter '%s' to be of type " | |
271 "'%s', but was: %s" % | |
272 (k, param_type, route_metadata[k])) | |
273 if param_type == 'path': | |
274 return val | |
275 raise Exception("Unknown route parameter type: %s" % param_type) | |
256 | 276 |
257 def _createTemplateFunc(self, func_def): | 277 def _createTemplateFunc(self, func_def): |
258 if func_def is None: | 278 if func_def is None: |
259 return | 279 return |
260 | 280 |
297 non_var_args = list(self.template_func_args) | 317 non_var_args = list(self.template_func_args) |
298 if is_variable: | 318 if is_variable: |
299 del non_var_args[-1] | 319 del non_var_args[-1] |
300 | 320 |
301 for arg_name, arg_val in zip(non_var_args, args): | 321 for arg_name, arg_val in zip(non_var_args, args): |
302 #TODO: fix this hard-coded shit. | 322 metadata[arg_name] = self._coerceRouteParameter( |
303 if arg_name in ['year', 'month', 'day']: | 323 arg_name, arg_val) |
304 arg_val = int(arg_val) | |
305 metadata[arg_name] = arg_val | |
306 | 324 |
307 if is_variable: | 325 if is_variable: |
308 metadata[self.template_func_vararg] = [] | 326 metadata[self.template_func_vararg] = [] |
309 for i in range(len(non_var_args), len(args)): | 327 for i in range(len(non_var_args), len(args)): |
310 metadata[self.template_func_vararg].append(args[i]) | 328 metadata[self.template_func_vararg].append(args[i]) |