Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/routes/route.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
| author | shellac |
|---|---|
| date | Mon, 01 Jun 2020 08:59:25 -0400 |
| parents | 79f47841a781 |
| children |
comparison
equal
deleted
inserted
replaced
| 4:79f47841a781 | 5:9b1c78e6ba9c |
|---|---|
| 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 |
