Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/routes/route.py @ 0:26e78fe6e8c4 draft
"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author | shellac |
---|---|
date | Sat, 02 May 2020 07:14:21 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:26e78fe6e8c4 |
---|---|
1 import re | |
2 import sys | |
3 if sys.version < '2.4': | |
4 from sets import ImmutableSet as frozenset | |
5 | |
6 import six | |
7 from six.moves.urllib import parse as urlparse | |
8 | |
9 from routes.util import _url_quote as url_quote, _str_encode, as_unicode | |
10 | |
11 | |
12 class Route(object): | |
13 """The Route object holds a route recognition and generation | |
14 routine. | |
15 | |
16 See Route.__init__ docs for usage. | |
17 | |
18 """ | |
19 # reserved keys that don't count | |
20 reserved_keys = ['requirements'] | |
21 | |
22 # special chars to indicate a natural split in the URL | |
23 done_chars = ('/', ',', ';', '.', '#') | |
24 | |
25 def __init__(self, name, routepath, **kargs): | |
26 """Initialize a route, with a given routepath for | |
27 matching/generation | |
28 | |
29 The set of keyword args will be used as defaults. | |
30 | |
31 Usage:: | |
32 | |
33 >>> from routes.base import Route | |
34 >>> newroute = Route(None, ':controller/:action/:id') | |
35 >>> sorted(newroute.defaults.items()) | |
36 [('action', 'index'), ('id', None)] | |
37 >>> newroute = Route(None, 'date/:year/:month/:day', | |
38 ... controller="blog", action="view") | |
39 >>> newroute = Route(None, 'archives/:page', controller="blog", | |
40 ... action="by_page", requirements = { 'page':'\d{1,2}' }) | |
41 >>> newroute.reqs | |
42 {'page': '\\\d{1,2}'} | |
43 | |
44 .. Note:: | |
45 Route is generally not called directly, a Mapper instance | |
46 connect method should be used to add routes. | |
47 | |
48 """ | |
49 self.routepath = routepath | |
50 self.sub_domains = False | |
51 self.prior = None | |
52 self.redirect = False | |
53 self.name = name | |
54 self._kargs = kargs | |
55 self.minimization = kargs.pop('_minimize', False) | |
56 self.encoding = kargs.pop('_encoding', 'utf-8') | |
57 self.reqs = kargs.get('requirements', {}) | |
58 self.decode_errors = 'replace' | |
59 | |
60 # Don't bother forming stuff we don't need if its a static route | |
61 self.static = kargs.pop('_static', False) | |
62 self.filter = kargs.pop('_filter', None) | |
63 self.absolute = kargs.pop('_absolute', False) | |
64 | |
65 # Pull out the member/collection name if present, this applies only to | |
66 # map.resource | |
67 self.member_name = kargs.pop('_member_name', None) | |
68 self.collection_name = kargs.pop('_collection_name', None) | |
69 self.parent_resource = kargs.pop('_parent_resource', None) | |
70 | |
71 # Pull out route conditions | |
72 self.conditions = kargs.pop('conditions', None) | |
73 | |
74 # Determine if explicit behavior should be used | |
75 self.explicit = kargs.pop('_explicit', False) | |
76 | |
77 # Since static need to be generated exactly, treat them as | |
78 # non-minimized | |
79 if self.static: | |
80 self.external = '://' in self.routepath | |
81 self.minimization = False | |
82 | |
83 # Strip preceding '/' if present, and not minimizing | |
84 if routepath.startswith('/') and self.minimization: | |
85 self.routepath = routepath[1:] | |
86 self._setup_route() | |
87 | |
88 def _setup_route(self): | |
89 # Build our routelist, and the keys used in the route | |
90 self.routelist = routelist = self._pathkeys(self.routepath) | |
91 routekeys = frozenset(key['name'] for key in routelist | |
92 if isinstance(key, dict)) | |
93 self.dotkeys = frozenset(key['name'] for key in routelist | |
94 if isinstance(key, dict) and | |
95 key['type'] == '.') | |
96 | |
97 if not self.minimization: | |
98 self.make_full_route() | |
99 | |
100 # Build a req list with all the regexp requirements for our args | |
101 self.req_regs = {} | |
102 for key, val in six.iteritems(self.reqs): | |
103 self.req_regs[key] = re.compile('^' + val + '$') | |
104 # Update our defaults and set new default keys if needed. defaults | |
105 # needs to be saved | |
106 (self.defaults, defaultkeys) = self._defaults(routekeys, | |
107 self.reserved_keys, | |
108 self._kargs.copy()) | |
109 # Save the maximum keys we could utilize | |
110 self.maxkeys = defaultkeys | routekeys | |
111 | |
112 # Populate our minimum keys, and save a copy of our backward keys for | |
113 # quicker generation later | |
114 (self.minkeys, self.routebackwards) = self._minkeys(routelist[:]) | |
115 | |
116 # Populate our hardcoded keys, these are ones that are set and don't | |
117 # exist in the route | |
118 self.hardcoded = frozenset(key for key in self.maxkeys | |
119 if key not in routekeys | |
120 and self.defaults[key] is not None) | |
121 | |
122 # Cache our default keys | |
123 self._default_keys = frozenset(self.defaults.keys()) | |
124 | |
125 def make_full_route(self): | |
126 """Make a full routelist string for use with non-minimized | |
127 generation""" | |
128 regpath = '' | |
129 for part in self.routelist: | |
130 if isinstance(part, dict): | |
131 regpath += '%(' + part['name'] + ')s' | |
132 else: | |
133 regpath += part | |
134 self.regpath = regpath | |
135 | |
136 def make_unicode(self, s): | |
137 """Transform the given argument into a unicode string.""" | |
138 if isinstance(s, six.text_type): | |
139 return s | |
140 elif isinstance(s, bytes): | |
141 return s.decode(self.encoding) | |
142 elif callable(s): | |
143 return s | |
144 else: | |
145 return six.text_type(s) | |
146 | |
147 def _pathkeys(self, routepath): | |
148 """Utility function to walk the route, and pull out the valid | |
149 dynamic/wildcard keys.""" | |
150 collecting = False | |
151 current = '' | |
152 done_on = '' | |
153 var_type = '' | |
154 just_started = False | |
155 routelist = [] | |
156 for char in routepath: | |
157 if char in [':', '*', '{'] and not collecting and not self.static \ | |
158 or char in ['{'] and not collecting: | |
159 just_started = True | |
160 collecting = True | |
161 var_type = char | |
162 if char == '{': | |
163 done_on = '}' | |
164 just_started = False | |
165 if len(current) > 0: | |
166 routelist.append(current) | |
167 current = '' | |
168 elif collecting and just_started: | |
169 just_started = False | |
170 if char == '(': | |
171 done_on = ')' | |
172 else: | |
173 current = char | |
174 done_on = self.done_chars + ('-',) | |
175 elif collecting and char not in done_on: | |
176 current += char | |
177 elif collecting: | |
178 collecting = False | |
179 if var_type == '{': | |
180 if current[0] == '.': | |
181 var_type = '.' | |
182 current = current[1:] | |
183 else: | |
184 var_type = ':' | |
185 opts = current.split(':') | |
186 if len(opts) > 1: | |
187 current = opts[0] | |
188 self.reqs[current] = opts[1] | |
189 routelist.append(dict(type=var_type, name=current)) | |
190 if char in self.done_chars: | |
191 routelist.append(char) | |
192 done_on = var_type = current = '' | |
193 else: | |
194 current += char | |
195 if collecting: | |
196 routelist.append(dict(type=var_type, name=current)) | |
197 elif current: | |
198 routelist.append(current) | |
199 return routelist | |
200 | |
201 def _minkeys(self, routelist): | |
202 """Utility function to walk the route backwards | |
203 | |
204 Will also determine the minimum keys we can handle to generate | |
205 a working route. | |
206 | |
207 routelist is a list of the '/' split route path | |
208 defaults is a dict of all the defaults provided for the route | |
209 | |
210 """ | |
211 minkeys = [] | |
212 backcheck = routelist[:] | |
213 | |
214 # If we don't honor minimization, we need all the keys in the | |
215 # route path | |
216 if not self.minimization: | |
217 for part in backcheck: | |
218 if isinstance(part, dict): | |
219 minkeys.append(part['name']) | |
220 return (frozenset(minkeys), backcheck) | |
221 | |
222 gaps = False | |
223 backcheck.reverse() | |
224 for part in backcheck: | |
225 if not isinstance(part, dict) and part not in self.done_chars: | |
226 gaps = True | |
227 continue | |
228 elif not isinstance(part, dict): | |
229 continue | |
230 key = part['name'] | |
231 if key in self.defaults and not gaps: | |
232 continue | |
233 minkeys.append(key) | |
234 gaps = True | |
235 return (frozenset(minkeys), backcheck) | |
236 | |
237 def _defaults(self, routekeys, reserved_keys, kargs): | |
238 """Creates default set with values stringified | |
239 | |
240 Put together our list of defaults, stringify non-None values | |
241 and add in our action/id default if they use it and didn't | |
242 specify it. | |
243 | |
244 defaultkeys is a list of the currently assumed default keys | |
245 routekeys is a list of the keys found in the route path | |
246 reserved_keys is a list of keys that are not | |
247 | |
248 """ | |
249 defaults = {} | |
250 # Add in a controller/action default if they don't exist | |
251 if 'controller' not in routekeys and 'controller' not in kargs \ | |
252 and not self.explicit: | |
253 kargs['controller'] = 'content' | |
254 if 'action' not in routekeys and 'action' not in kargs \ | |
255 and not self.explicit: | |
256 kargs['action'] = 'index' | |
257 defaultkeys = frozenset(key for key in kargs.keys() | |
258 if key not in reserved_keys) | |
259 for key in defaultkeys: | |
260 if kargs[key] is not None: | |
261 defaults[key] = self.make_unicode(kargs[key]) | |
262 else: | |
263 defaults[key] = None | |
264 if 'action' in routekeys and 'action' not in defaults \ | |
265 and not self.explicit: | |
266 defaults['action'] = 'index' | |
267 if 'id' in routekeys and 'id' not in defaults \ | |
268 and not self.explicit: | |
269 defaults['id'] = None | |
270 newdefaultkeys = frozenset(key for key in defaults.keys() | |
271 if key not in reserved_keys) | |
272 | |
273 return (defaults, newdefaultkeys) | |
274 | |
275 def makeregexp(self, clist, include_names=True): | |
276 """Create a regular expression for matching purposes | |
277 | |
278 Note: This MUST be called before match can function properly. | |
279 | |
280 clist should be a list of valid controller strings that can be | |
281 matched, for this reason makeregexp should be called by the web | |
282 framework after it knows all available controllers that can be | |
283 utilized. | |
284 | |
285 include_names indicates whether this should be a match regexp | |
286 assigned to itself using regexp grouping names, or if names | |
287 should be excluded for use in a single larger regexp to | |
288 determine if any routes match | |
289 | |
290 """ | |
291 if self.minimization: | |
292 reg = self.buildnextreg(self.routelist, clist, include_names)[0] | |
293 if not reg: | |
294 reg = '/' | |
295 reg = reg + '/?' + '$' | |
296 | |
297 if not reg.startswith('/'): | |
298 reg = '/' + reg | |
299 else: | |
300 reg = self.buildfullreg(clist, include_names) | |
301 | |
302 reg = '^' + reg | |
303 | |
304 if not include_names: | |
305 return reg | |
306 | |
307 self.regexp = reg | |
308 self.regmatch = re.compile(reg) | |
309 | |
310 def buildfullreg(self, clist, include_names=True): | |
311 """Build the regexp by iterating through the routelist and | |
312 replacing dicts with the appropriate regexp match""" | |
313 regparts = [] | |
314 for part in self.routelist: | |
315 if isinstance(part, dict): | |
316 var = part['name'] | |
317 if var == 'controller': | |
318 partmatch = '|'.join(map(re.escape, clist)) | |
319 elif part['type'] == ':': | |
320 partmatch = self.reqs.get(var) or '[^/]+?' | |
321 elif part['type'] == '.': | |
322 partmatch = self.reqs.get(var) or '[^/.]+?' | |
323 else: | |
324 partmatch = self.reqs.get(var) or '.+?' | |
325 if include_names: | |
326 regpart = '(?P<%s>%s)' % (var, partmatch) | |
327 else: | |
328 regpart = '(?:%s)' % partmatch | |
329 if part['type'] == '.': | |
330 regparts.append('(?:\.%s)??' % regpart) | |
331 else: | |
332 regparts.append(regpart) | |
333 else: | |
334 regparts.append(re.escape(part)) | |
335 regexp = ''.join(regparts) + '$' | |
336 return regexp | |
337 | |
338 def buildnextreg(self, path, clist, include_names=True): | |
339 """Recursively build our regexp given a path, and a controller | |
340 list. | |
341 | |
342 Returns the regular expression string, and two booleans that | |
343 can be ignored as they're only used internally by buildnextreg. | |
344 | |
345 """ | |
346 if path: | |
347 part = path[0] | |
348 else: | |
349 part = '' | |
350 reg = '' | |
351 | |
352 # noreqs will remember whether the remainder has either a string | |
353 # match, or a non-defaulted regexp match on a key, allblank remembers | |
354 # if the rest could possible be completely empty | |
355 (rest, noreqs, allblank) = ('', True, True) | |
356 if len(path[1:]) > 0: | |
357 self.prior = part | |
358 (rest, noreqs, allblank) = self.buildnextreg(path[1:], clist, | |
359 include_names) | |
360 | |
361 if isinstance(part, dict) and part['type'] in (':', '.'): | |
362 var = part['name'] | |
363 typ = part['type'] | |
364 partreg = '' | |
365 | |
366 # First we plug in the proper part matcher | |
367 if var in self.reqs: | |
368 if include_names: | |
369 partreg = '(?P<%s>%s)' % (var, self.reqs[var]) | |
370 else: | |
371 partreg = '(?:%s)' % self.reqs[var] | |
372 if typ == '.': | |
373 partreg = '(?:\.%s)??' % partreg | |
374 elif var == 'controller': | |
375 if include_names: | |
376 partreg = '(?P<%s>%s)' % (var, '|'.join(map(re.escape, | |
377 clist))) | |
378 else: | |
379 partreg = '(?:%s)' % '|'.join(map(re.escape, clist)) | |
380 elif self.prior in ['/', '#']: | |
381 if include_names: | |
382 partreg = '(?P<' + var + '>[^' + self.prior + ']+?)' | |
383 else: | |
384 partreg = '(?:[^' + self.prior + ']+?)' | |
385 else: | |
386 if not rest: | |
387 if typ == '.': | |
388 exclude_chars = '/.' | |
389 else: | |
390 exclude_chars = '/' | |
391 if include_names: | |
392 partreg = '(?P<%s>[^%s]+?)' % (var, exclude_chars) | |
393 else: | |
394 partreg = '(?:[^%s]+?)' % exclude_chars | |
395 if typ == '.': | |
396 partreg = '(?:\.%s)??' % partreg | |
397 else: | |
398 end = ''.join(self.done_chars) | |
399 rem = rest | |
400 if rem[0] == '\\' and len(rem) > 1: | |
401 rem = rem[1] | |
402 elif rem.startswith('(\\') and len(rem) > 2: | |
403 rem = rem[2] | |
404 else: | |
405 rem = end | |
406 rem = frozenset(rem) | frozenset(['/']) | |
407 if include_names: | |
408 partreg = '(?P<%s>[^%s]+?)' % (var, ''.join(rem)) | |
409 else: | |
410 partreg = '(?:[^%s]+?)' % ''.join(rem) | |
411 | |
412 if var in self.reqs: | |
413 noreqs = False | |
414 if var not in self.defaults: | |
415 allblank = False | |
416 noreqs = False | |
417 | |
418 # Now we determine if its optional, or required. This changes | |
419 # depending on what is in the rest of the match. If noreqs is | |
420 # true, then its possible the entire thing is optional as there's | |
421 # no reqs or string matches. | |
422 if noreqs: | |
423 # The rest is optional, but now we have an optional with a | |
424 # regexp. Wrap to ensure that if we match anything, we match | |
425 # our regexp first. It's still possible we could be completely | |
426 # blank as we have a default | |
427 if var in self.reqs and var in self.defaults: | |
428 reg = '(?:' + partreg + rest + ')?' | |
429 | |
430 # Or we have a regexp match with no default, so now being | |
431 # completely blank form here on out isn't possible | |
432 elif var in self.reqs: | |
433 allblank = False | |
434 reg = partreg + rest | |
435 | |
436 # If the character before this is a special char, it has to be | |
437 # followed by this | |
438 elif var in self.defaults and self.prior in (',', ';', '.'): | |
439 reg = partreg + rest | |
440 | |
441 # Or we have a default with no regexp, don't touch the allblank | |
442 elif var in self.defaults: | |
443 reg = partreg + '?' + rest | |
444 | |
445 # Or we have a key with no default, and no reqs. Not possible | |
446 # to be all blank from here | |
447 else: | |
448 allblank = False | |
449 reg = partreg + rest | |
450 # In this case, we have something dangling that might need to be | |
451 # matched | |
452 else: | |
453 # If they can all be blank, and we have a default here, we know | |
454 # its safe to make everything from here optional. Since | |
455 # something else in the chain does have req's though, we have | |
456 # to make the partreg here required to continue matching | |
457 if allblank and var in self.defaults: | |
458 reg = '(?:' + partreg + rest + ')?' | |
459 | |
460 # Same as before, but they can't all be blank, so we have to | |
461 # require it all to ensure our matches line up right | |
462 else: | |
463 reg = partreg + rest | |
464 elif isinstance(part, dict) and part['type'] == '*': | |
465 var = part['name'] | |
466 if noreqs: | |
467 if include_names: | |
468 reg = '(?P<%s>.*)' % var + rest | |
469 else: | |
470 reg = '(?:.*)' + rest | |
471 if var not in self.defaults: | |
472 allblank = False | |
473 noreqs = False | |
474 else: | |
475 if allblank and var in self.defaults: | |
476 if include_names: | |
477 reg = '(?P<%s>.*)' % var + rest | |
478 else: | |
479 reg = '(?:.*)' + rest | |
480 elif var in self.defaults: | |
481 if include_names: | |
482 reg = '(?P<%s>.*)' % var + rest | |
483 else: | |
484 reg = '(?:.*)' + rest | |
485 else: | |
486 if include_names: | |
487 reg = '(?P<%s>.*)' % var + rest | |
488 else: | |
489 reg = '(?:.*)' + rest | |
490 allblank = False | |
491 noreqs = False | |
492 elif part and part[-1] in self.done_chars: | |
493 if allblank: | |
494 reg = re.escape(part[:-1]) + '(?:' + re.escape(part[-1]) + rest | |
495 reg += ')?' | |
496 else: | |
497 allblank = False | |
498 reg = re.escape(part) + rest | |
499 | |
500 # We have a normal string here, this is a req, and it prevents us from | |
501 # being all blank | |
502 else: | |
503 noreqs = False | |
504 allblank = False | |
505 reg = re.escape(part) + rest | |
506 | |
507 return (reg, noreqs, allblank) | |
508 | |
509 def match(self, url, environ=None, sub_domains=False, | |
510 sub_domains_ignore=None, domain_match=''): | |
511 """Match a url to our regexp. | |
512 | |
513 While the regexp might match, this operation isn't | |
514 guaranteed as there's other factors that can cause a match to | |
515 fail even though the regexp succeeds (Default that was relied | |
516 on wasn't given, requirement regexp doesn't pass, etc.). | |
517 | |
518 Therefore the calling function shouldn't assume this will | |
519 return a valid dict, the other possible return is False if a | |
520 match doesn't work out. | |
521 | |
522 """ | |
523 # Static routes don't match, they generate only | |
524 if self.static: | |
525 return False | |
526 | |
527 match = self.regmatch.match(url) | |
528 | |
529 if not match: | |
530 return False | |
531 | |
532 sub_domain = None | |
533 | |
534 if sub_domains and environ and 'HTTP_HOST' in environ: | |
535 host = environ['HTTP_HOST'].split(':')[0] | |
536 sub_match = re.compile('^(.+?)\.%s$' % domain_match) | |
537 subdomain = re.sub(sub_match, r'\1', host) | |
538 if subdomain not in sub_domains_ignore and host != subdomain: | |
539 sub_domain = subdomain | |
540 | |
541 if self.conditions: | |
542 if 'method' in self.conditions and environ and \ | |
543 environ['REQUEST_METHOD'] not in self.conditions['method']: | |
544 return False | |
545 | |
546 # Check sub-domains? | |
547 use_sd = self.conditions.get('sub_domain') | |
548 if use_sd and not sub_domain: | |
549 return False | |
550 elif not use_sd and 'sub_domain' in self.conditions and sub_domain: | |
551 return False | |
552 if isinstance(use_sd, list) and sub_domain not in use_sd: | |
553 return False | |
554 | |
555 matchdict = match.groupdict() | |
556 result = {} | |
557 extras = self._default_keys - frozenset(matchdict.keys()) | |
558 for key, val in six.iteritems(matchdict): | |
559 if key != 'path_info' and self.encoding: | |
560 # change back into python unicode objects from the URL | |
561 # representation | |
562 try: | |
563 val = as_unicode(val, self.encoding, self.decode_errors) | |
564 except UnicodeDecodeError: | |
565 return False | |
566 | |
567 if not val and key in self.defaults and self.defaults[key]: | |
568 result[key] = self.defaults[key] | |
569 else: | |
570 result[key] = val | |
571 for key in extras: | |
572 result[key] = self.defaults[key] | |
573 | |
574 # Add the sub-domain if there is one | |
575 if sub_domains: | |
576 result['sub_domain'] = sub_domain | |
577 | |
578 # If there's a function, call it with environ and expire if it | |
579 # returns False | |
580 if self.conditions and 'function' in self.conditions and \ | |
581 not self.conditions['function'](environ, result): | |
582 return False | |
583 | |
584 return result | |
585 | |
586 def generate_non_minimized(self, kargs): | |
587 """Generate a non-minimal version of the URL""" | |
588 # Iterate through the keys that are defaults, and NOT in the route | |
589 # path. If its not in kargs, or doesn't match, or is None, this | |
590 # route won't work | |
591 for k in self.maxkeys - self.minkeys: | |
592 if k not in kargs: | |
593 return False | |
594 elif self.make_unicode(kargs[k]) != \ | |
595 self.make_unicode(self.defaults[k]): | |
596 return False | |
597 | |
598 # Ensure that all the args in the route path are present and not None | |
599 for arg in self.minkeys: | |
600 if arg not in kargs or kargs[arg] is None: | |
601 if arg in self.dotkeys: | |
602 kargs[arg] = '' | |
603 else: | |
604 return False | |
605 | |
606 # Encode all the argument that the regpath can use | |
607 for k in kargs: | |
608 if k in self.maxkeys: | |
609 if k in self.dotkeys: | |
610 if kargs[k]: | |
611 kargs[k] = url_quote('.' + as_unicode(kargs[k], | |
612 self.encoding), self.encoding) | |
613 else: | |
614 kargs[k] = url_quote(as_unicode(kargs[k], self.encoding), | |
615 self.encoding) | |
616 | |
617 return self.regpath % kargs | |
618 | |
619 def generate_minimized(self, kargs): | |
620 """Generate a minimized version of the URL""" | |
621 routelist = self.routebackwards | |
622 urllist = [] | |
623 gaps = False | |
624 for part in routelist: | |
625 if isinstance(part, dict) and part['type'] in (':', '.'): | |
626 arg = part['name'] | |
627 | |
628 # For efficiency, check these just once | |
629 has_arg = arg in kargs | |
630 has_default = arg in self.defaults | |
631 | |
632 # Determine if we can leave this part off | |
633 # First check if the default exists and wasn't provided in the | |
634 # call (also no gaps) | |
635 if has_default and not has_arg and not gaps: | |
636 continue | |
637 | |
638 # Now check to see if there's a default and it matches the | |
639 # incoming call arg | |
640 if (has_default and has_arg) and \ | |
641 self.make_unicode(kargs[arg]) == \ | |
642 self.make_unicode(self.defaults[arg]) and not gaps: | |
643 continue | |
644 | |
645 # We need to pull the value to append, if the arg is None and | |
646 # we have a default, use that | |
647 if has_arg and kargs[arg] is None and has_default and not gaps: | |
648 continue | |
649 | |
650 # Otherwise if we do have an arg, use that | |
651 elif has_arg: | |
652 val = kargs[arg] | |
653 | |
654 elif has_default and self.defaults[arg] is not None: | |
655 val = self.defaults[arg] | |
656 # Optional format parameter? | |
657 elif part['type'] == '.': | |
658 continue | |
659 # No arg at all? This won't work | |
660 else: | |
661 return False | |
662 | |
663 val = as_unicode(val, self.encoding) | |
664 urllist.append(url_quote(val, self.encoding)) | |
665 if part['type'] == '.': | |
666 urllist.append('.') | |
667 | |
668 if has_arg: | |
669 del kargs[arg] | |
670 gaps = True | |
671 elif isinstance(part, dict) and part['type'] == '*': | |
672 arg = part['name'] | |
673 kar = kargs.get(arg) | |
674 if kar is not None: | |
675 urllist.append(url_quote(kar, self.encoding)) | |
676 gaps = True | |
677 elif part and part[-1] in self.done_chars: | |
678 if not gaps and part in self.done_chars: | |
679 continue | |
680 elif not gaps: | |
681 urllist.append(part[:-1]) | |
682 gaps = True | |
683 else: | |
684 gaps = True | |
685 urllist.append(part) | |
686 else: | |
687 gaps = True | |
688 urllist.append(part) | |
689 urllist.reverse() | |
690 url = ''.join(urllist) | |
691 return url | |
692 | |
693 def generate(self, _ignore_req_list=False, _append_slash=False, **kargs): | |
694 """Generate a URL from ourself given a set of keyword arguments | |
695 | |
696 Toss an exception if this | |
697 set of keywords would cause a gap in the url. | |
698 | |
699 """ | |
700 # Verify that our args pass any regexp requirements | |
701 if not _ignore_req_list: | |
702 for key in self.reqs.keys(): | |
703 val = kargs.get(key) | |
704 if val and not self.req_regs[key].match(self.make_unicode(val)): | |
705 return False | |
706 | |
707 # Verify that if we have a method arg, its in the method accept list. | |
708 # Also, method will be changed to _method for route generation | |
709 meth = as_unicode(kargs.get('method'), self.encoding) | |
710 if meth: | |
711 if self.conditions and 'method' in self.conditions \ | |
712 and meth.upper() not in self.conditions['method']: | |
713 return False | |
714 kargs.pop('method') | |
715 | |
716 if self.minimization: | |
717 url = self.generate_minimized(kargs) | |
718 else: | |
719 url = self.generate_non_minimized(kargs) | |
720 | |
721 if url is False: | |
722 return url | |
723 | |
724 if not url.startswith('/') and not self.static: | |
725 url = '/' + url | |
726 extras = frozenset(kargs.keys()) - self.maxkeys | |
727 if extras: | |
728 if _append_slash and not url.endswith('/'): | |
729 url += '/' | |
730 fragments = [] | |
731 # don't assume the 'extras' set preserves order: iterate | |
732 # through the ordered kargs instead | |
733 for key in kargs: | |
734 if key not in extras: | |
735 continue | |
736 if key == 'action' or key == 'controller': | |
737 continue | |
738 val = kargs[key] | |
739 if isinstance(val, (tuple, list)): | |
740 for value in val: | |
741 value = as_unicode(value, self.encoding) | |
742 fragments.append((key, _str_encode(value, | |
743 self.encoding))) | |
744 else: | |
745 val = as_unicode(val, self.encoding) | |
746 fragments.append((key, _str_encode(val, self.encoding))) | |
747 if fragments: | |
748 url += '?' | |
749 url += urlparse.urlencode(fragments) | |
750 elif _append_slash and not url.endswith('/'): | |
751 url += '/' | |
752 return url |