[3] | 1 | """Utility functions for use in templates / controllers |
---|
| 2 | |
---|
| 3 | *PLEASE NOTE*: Many of these functions expect an initialized RequestConfig |
---|
| 4 | object. This is expected to have been initialized for EACH REQUEST by the web |
---|
| 5 | framework. |
---|
| 6 | |
---|
| 7 | """ |
---|
| 8 | import os |
---|
| 9 | import re |
---|
| 10 | import urllib |
---|
| 11 | from routes import request_config |
---|
| 12 | |
---|
| 13 | |
---|
| 14 | class RoutesException(Exception): |
---|
| 15 | """Tossed during Route exceptions""" |
---|
| 16 | |
---|
| 17 | |
---|
| 18 | class MatchException(RoutesException): |
---|
| 19 | """Tossed during URL matching exceptions""" |
---|
| 20 | |
---|
| 21 | |
---|
| 22 | class GenerationException(RoutesException): |
---|
| 23 | """Tossed during URL generation exceptions""" |
---|
| 24 | |
---|
| 25 | |
---|
| 26 | def _screenargs(kargs, mapper, environ, force_explicit=False): |
---|
| 27 | """ |
---|
| 28 | Private function that takes a dict, and screens it against the current |
---|
| 29 | request dict to determine what the dict should look like that is used. |
---|
| 30 | This is responsible for the requests "memory" of the current. |
---|
| 31 | """ |
---|
| 32 | # Coerce any unicode args with the encoding |
---|
| 33 | encoding = mapper.encoding |
---|
| 34 | for key, val in kargs.iteritems(): |
---|
| 35 | if isinstance(val, unicode): |
---|
| 36 | kargs[key] = val.encode(encoding) |
---|
| 37 | |
---|
| 38 | if mapper.explicit and mapper.sub_domains and not force_explicit: |
---|
| 39 | return _subdomain_check(kargs, mapper, environ) |
---|
| 40 | elif mapper.explicit and not force_explicit: |
---|
| 41 | return kargs |
---|
| 42 | |
---|
| 43 | controller_name = kargs.get('controller') |
---|
| 44 | |
---|
| 45 | if controller_name and controller_name.startswith('/'): |
---|
| 46 | # If the controller name starts with '/', ignore route memory |
---|
| 47 | kargs['controller'] = kargs['controller'][1:] |
---|
| 48 | return kargs |
---|
| 49 | elif controller_name and not kargs.has_key('action'): |
---|
| 50 | # Fill in an action if we don't have one, but have a controller |
---|
| 51 | kargs['action'] = 'index' |
---|
| 52 | |
---|
| 53 | route_args = environ.get('wsgiorg.routing_args') |
---|
| 54 | if route_args: |
---|
| 55 | memory_kargs = route_args[1].copy() |
---|
| 56 | else: |
---|
| 57 | memory_kargs = {} |
---|
| 58 | |
---|
| 59 | # Remove keys from memory and kargs if kargs has them as None |
---|
| 60 | for key in [key for key in kargs.keys() if kargs[key] is None]: |
---|
| 61 | del kargs[key] |
---|
| 62 | if memory_kargs.has_key(key): |
---|
| 63 | del memory_kargs[key] |
---|
| 64 | |
---|
| 65 | # Merge the new args on top of the memory args |
---|
| 66 | memory_kargs.update(kargs) |
---|
| 67 | |
---|
| 68 | # Setup a sub-domain if applicable |
---|
| 69 | if mapper.sub_domains: |
---|
| 70 | memory_kargs = _subdomain_check(memory_kargs, mapper, environ) |
---|
| 71 | return memory_kargs |
---|
| 72 | |
---|
| 73 | |
---|
| 74 | def _subdomain_check(kargs, mapper, environ): |
---|
| 75 | """Screen the kargs for a subdomain and alter it appropriately depending |
---|
| 76 | on the current subdomain or lack therof.""" |
---|
| 77 | if mapper.sub_domains: |
---|
| 78 | subdomain = kargs.pop('sub_domain', None) |
---|
| 79 | if isinstance(subdomain, unicode): |
---|
| 80 | subdomain = str(subdomain) |
---|
| 81 | |
---|
| 82 | fullhost = environ.get('HTTP_HOST') or environ.get('SERVER_NAME') |
---|
| 83 | |
---|
| 84 | # In case environ defaulted to {} |
---|
| 85 | if not fullhost: |
---|
| 86 | return kargs |
---|
| 87 | |
---|
| 88 | hostmatch = fullhost.split(':') |
---|
| 89 | host = hostmatch[0] |
---|
| 90 | port = '' |
---|
| 91 | if len(hostmatch) > 1: |
---|
| 92 | port += ':' + hostmatch[1] |
---|
| 93 | sub_match = re.compile('^.+?\.(%s)$' % mapper.domain_match) |
---|
| 94 | domain = re.sub(sub_match, r'\1', host) |
---|
| 95 | if subdomain and not host.startswith(subdomain) and \ |
---|
| 96 | subdomain not in mapper.sub_domains_ignore: |
---|
| 97 | kargs['_host'] = subdomain + '.' + domain + port |
---|
| 98 | elif (subdomain in mapper.sub_domains_ignore or \ |
---|
| 99 | subdomain is None) and domain != host: |
---|
| 100 | kargs['_host'] = domain + port |
---|
| 101 | return kargs |
---|
| 102 | else: |
---|
| 103 | return kargs |
---|
| 104 | |
---|
| 105 | |
---|
| 106 | def _url_quote(string, encoding): |
---|
| 107 | """A Unicode handling version of urllib.quote.""" |
---|
| 108 | if encoding: |
---|
| 109 | if isinstance(string, unicode): |
---|
| 110 | s = string.encode(encoding) |
---|
| 111 | elif isinstance(string, str): |
---|
| 112 | # assume the encoding is already correct |
---|
| 113 | s = string |
---|
| 114 | else: |
---|
| 115 | s = unicode(string).encode(encoding) |
---|
| 116 | else: |
---|
| 117 | s = str(string) |
---|
| 118 | return urllib.quote(s, '/') |
---|
| 119 | |
---|
| 120 | |
---|
| 121 | def _str_encode(string, encoding): |
---|
| 122 | if encoding: |
---|
| 123 | if isinstance(string, unicode): |
---|
| 124 | s = string.encode(encoding) |
---|
| 125 | elif isinstance(string, str): |
---|
| 126 | # assume the encoding is already correct |
---|
| 127 | s = string |
---|
| 128 | else: |
---|
| 129 | s = unicode(string).encode(encoding) |
---|
| 130 | return s |
---|
| 131 | |
---|
| 132 | |
---|
| 133 | def url_for(*args, **kargs): |
---|
| 134 | """Generates a URL |
---|
| 135 | |
---|
| 136 | All keys given to url_for are sent to the Routes Mapper instance for |
---|
| 137 | generation except for:: |
---|
| 138 | |
---|
| 139 | anchor specified the anchor name to be appened to the path |
---|
| 140 | host overrides the default (current) host if provided |
---|
| 141 | protocol overrides the default (current) protocol if provided |
---|
| 142 | qualified creates the URL with the host/port information as |
---|
| 143 | needed |
---|
| 144 | |
---|
| 145 | The URL is generated based on the rest of the keys. When generating a new |
---|
| 146 | URL, values will be used from the current request's parameters (if |
---|
| 147 | present). The following rules are used to determine when and how to keep |
---|
| 148 | the current requests parameters: |
---|
| 149 | |
---|
| 150 | * If the controller is present and begins with '/', no defaults are used |
---|
| 151 | * If the controller is changed, action is set to 'index' unless otherwise |
---|
| 152 | specified |
---|
| 153 | |
---|
| 154 | For example, if the current request yielded a dict of |
---|
| 155 | {'controller': 'blog', 'action': 'view', 'id': 2}, with the standard |
---|
| 156 | ':controller/:action/:id' route, you'd get the following results:: |
---|
| 157 | |
---|
| 158 | url_for(id=4) => '/blog/view/4', |
---|
| 159 | url_for(controller='/admin') => '/admin', |
---|
| 160 | url_for(controller='admin') => '/admin/view/2' |
---|
| 161 | url_for(action='edit') => '/blog/edit/2', |
---|
| 162 | url_for(action='list', id=None) => '/blog/list' |
---|
| 163 | |
---|
| 164 | **Static and Named Routes** |
---|
| 165 | |
---|
| 166 | If there is a string present as the first argument, a lookup is done |
---|
| 167 | against the named routes table to see if there's any matching routes. The |
---|
| 168 | keyword defaults used with static routes will be sent in as GET query |
---|
| 169 | arg's if a route matches. |
---|
| 170 | |
---|
| 171 | If no route by that name is found, the string is assumed to be a raw URL. |
---|
| 172 | Should the raw URL begin with ``/`` then appropriate SCRIPT_NAME data will |
---|
| 173 | be added if present, otherwise the string will be used as the url with |
---|
| 174 | keyword args becoming GET query args. |
---|
| 175 | |
---|
| 176 | """ |
---|
| 177 | anchor = kargs.get('anchor') |
---|
| 178 | host = kargs.get('host') |
---|
| 179 | protocol = kargs.get('protocol') |
---|
| 180 | qualified = kargs.pop('qualified', None) |
---|
| 181 | |
---|
| 182 | # Remove special words from kargs, convert placeholders |
---|
| 183 | for key in ['anchor', 'host', 'protocol']: |
---|
| 184 | if kargs.get(key): |
---|
| 185 | del kargs[key] |
---|
| 186 | config = request_config() |
---|
| 187 | route = None |
---|
| 188 | static = False |
---|
| 189 | encoding = config.mapper.encoding |
---|
| 190 | url = '' |
---|
| 191 | if len(args) > 0: |
---|
| 192 | route = config.mapper._routenames.get(args[0]) |
---|
| 193 | |
---|
| 194 | # No named route found, assume the argument is a relative path |
---|
| 195 | if not route: |
---|
| 196 | static = True |
---|
| 197 | url = args[0] |
---|
| 198 | |
---|
| 199 | if url.startswith('/') and hasattr(config, 'environ') \ |
---|
| 200 | and config.environ.get('SCRIPT_NAME'): |
---|
| 201 | url = config.environ.get('SCRIPT_NAME') + url |
---|
| 202 | |
---|
| 203 | if static: |
---|
| 204 | if kargs: |
---|
| 205 | url += '?' |
---|
| 206 | query_args = [] |
---|
| 207 | for key, val in kargs.iteritems(): |
---|
| 208 | if isinstance(val, (list, tuple)): |
---|
| 209 | for value in val: |
---|
| 210 | query_args.append("%s=%s" % ( |
---|
| 211 | urllib.quote(unicode(key).encode(encoding)), |
---|
| 212 | urllib.quote(unicode(value).encode(encoding)))) |
---|
| 213 | else: |
---|
| 214 | query_args.append("%s=%s" % ( |
---|
| 215 | urllib.quote(unicode(key).encode(encoding)), |
---|
| 216 | urllib.quote(unicode(val).encode(encoding)))) |
---|
| 217 | url += '&'.join(query_args) |
---|
| 218 | environ = getattr(config, 'environ', {}) |
---|
| 219 | if 'wsgiorg.routing_args' not in environ: |
---|
| 220 | environ = environ.copy() |
---|
| 221 | mapper_dict = getattr(config, 'mapper_dict', None) |
---|
| 222 | if mapper_dict is not None: |
---|
| 223 | match_dict = mapper_dict.copy() |
---|
| 224 | else: |
---|
| 225 | match_dict = {} |
---|
| 226 | environ['wsgiorg.routing_args'] = ((), match_dict) |
---|
| 227 | |
---|
| 228 | if not static: |
---|
| 229 | route_args = [] |
---|
| 230 | if route: |
---|
| 231 | if config.mapper.hardcode_names: |
---|
| 232 | route_args.append(route) |
---|
| 233 | newargs = route.defaults.copy() |
---|
| 234 | newargs.update(kargs) |
---|
| 235 | |
---|
| 236 | # If this route has a filter, apply it |
---|
| 237 | if route.filter: |
---|
| 238 | newargs = route.filter(newargs) |
---|
| 239 | |
---|
| 240 | if not route.static: |
---|
| 241 | # Handle sub-domains |
---|
| 242 | newargs = _subdomain_check(newargs, config.mapper, environ) |
---|
| 243 | else: |
---|
| 244 | newargs = _screenargs(kargs, config.mapper, environ) |
---|
| 245 | anchor = newargs.pop('_anchor', None) or anchor |
---|
| 246 | host = newargs.pop('_host', None) or host |
---|
| 247 | protocol = newargs.pop('_protocol', None) or protocol |
---|
| 248 | url = config.mapper.generate(*route_args, **newargs) |
---|
| 249 | if anchor is not None: |
---|
| 250 | url += '#' + _url_quote(anchor, encoding) |
---|
| 251 | if host or protocol or qualified: |
---|
| 252 | if not host and not qualified: |
---|
| 253 | # Ensure we don't use a specific port, as changing the protocol |
---|
| 254 | # means that we most likely need a new port |
---|
| 255 | host = config.host.split(':')[0] |
---|
| 256 | elif not host: |
---|
| 257 | host = config.host |
---|
| 258 | if not protocol: |
---|
| 259 | protocol = config.protocol |
---|
| 260 | if url is not None: |
---|
| 261 | url = protocol + '://' + host + url |
---|
| 262 | |
---|
| 263 | if not isinstance(url, str) and url is not None: |
---|
| 264 | raise GenerationException("url_for can only return a string, got " |
---|
| 265 | "unicode instead: %s" % url) |
---|
| 266 | if url is None: |
---|
| 267 | raise GenerationException( |
---|
| 268 | "url_for could not generate URL. Called with args: %s %s" % \ |
---|
| 269 | (args, kargs)) |
---|
| 270 | return url |
---|
| 271 | |
---|
| 272 | |
---|
| 273 | class URLGenerator(object): |
---|
| 274 | """The URL Generator generates URL's |
---|
| 275 | |
---|
| 276 | It is automatically instantiated by the RoutesMiddleware and put |
---|
| 277 | into the ``wsgiorg.routing_args`` tuple accessible as:: |
---|
| 278 | |
---|
| 279 | url = environ['wsgiorg.routing_args'][0][0] |
---|
| 280 | |
---|
| 281 | Or via the ``routes.url`` key:: |
---|
| 282 | |
---|
| 283 | url = environ['routes.url'] |
---|
| 284 | |
---|
| 285 | The url object may be instantiated outside of a web context for use |
---|
| 286 | in testing, however sub_domain support and fully qualified URL's |
---|
| 287 | cannot be generated without supplying a dict that must contain the |
---|
| 288 | key ``HTTP_HOST``. |
---|
| 289 | |
---|
| 290 | """ |
---|
| 291 | def __init__(self, mapper, environ): |
---|
| 292 | """Instantiate the URLGenerator |
---|
| 293 | |
---|
| 294 | ``mapper`` |
---|
| 295 | The mapper object to use when generating routes. |
---|
| 296 | ``environ`` |
---|
| 297 | The environment dict used in WSGI, alternately, any dict |
---|
| 298 | that contains at least an ``HTTP_HOST`` value. |
---|
| 299 | |
---|
| 300 | """ |
---|
| 301 | self.mapper = mapper |
---|
| 302 | if 'SCRIPT_NAME' not in environ: |
---|
| 303 | environ['SCRIPT_NAME'] = '' |
---|
| 304 | self.environ = environ |
---|
| 305 | |
---|
| 306 | def __call__(self, *args, **kargs): |
---|
| 307 | """Generates a URL |
---|
| 308 | |
---|
| 309 | All keys given to url_for are sent to the Routes Mapper instance for |
---|
| 310 | generation except for:: |
---|
| 311 | |
---|
| 312 | anchor specified the anchor name to be appened to the path |
---|
| 313 | host overrides the default (current) host if provided |
---|
| 314 | protocol overrides the default (current) protocol if provided |
---|
| 315 | qualified creates the URL with the host/port information as |
---|
| 316 | needed |
---|
| 317 | |
---|
| 318 | """ |
---|
| 319 | anchor = kargs.get('anchor') |
---|
| 320 | host = kargs.get('host') |
---|
| 321 | protocol = kargs.get('protocol') |
---|
| 322 | qualified = kargs.pop('qualified', None) |
---|
| 323 | |
---|
| 324 | # Remove special words from kargs, convert placeholders |
---|
| 325 | for key in ['anchor', 'host', 'protocol']: |
---|
| 326 | if kargs.get(key): |
---|
| 327 | del kargs[key] |
---|
| 328 | |
---|
| 329 | route = None |
---|
| 330 | use_current = '_use_current' in kargs and kargs.pop('_use_current') |
---|
| 331 | |
---|
| 332 | static = False |
---|
| 333 | encoding = self.mapper.encoding |
---|
| 334 | url = '' |
---|
| 335 | |
---|
| 336 | more_args = len(args) > 0 |
---|
| 337 | if more_args: |
---|
| 338 | route = self.mapper._routenames.get(args[0]) |
---|
| 339 | |
---|
| 340 | if not route and more_args: |
---|
| 341 | static = True |
---|
| 342 | url = args[0] |
---|
| 343 | if url.startswith('/') and self.environ.get('SCRIPT_NAME'): |
---|
| 344 | url = self.environ.get('SCRIPT_NAME') + url |
---|
| 345 | |
---|
| 346 | if static: |
---|
| 347 | if kargs: |
---|
| 348 | url += '?' |
---|
| 349 | query_args = [] |
---|
| 350 | for key, val in kargs.iteritems(): |
---|
| 351 | if isinstance(val, (list, tuple)): |
---|
| 352 | for value in val: |
---|
| 353 | query_args.append("%s=%s" % ( |
---|
| 354 | urllib.quote(unicode(key).encode(encoding)), |
---|
| 355 | urllib.quote(unicode(value).encode(encoding)))) |
---|
| 356 | else: |
---|
| 357 | query_args.append("%s=%s" % ( |
---|
| 358 | urllib.quote(unicode(key).encode(encoding)), |
---|
| 359 | urllib.quote(unicode(val).encode(encoding)))) |
---|
| 360 | url += '&'.join(query_args) |
---|
| 361 | if not static: |
---|
| 362 | route_args = [] |
---|
| 363 | if route: |
---|
| 364 | if self.mapper.hardcode_names: |
---|
| 365 | route_args.append(route) |
---|
| 366 | newargs = route.defaults.copy() |
---|
| 367 | newargs.update(kargs) |
---|
| 368 | |
---|
| 369 | # If this route has a filter, apply it |
---|
| 370 | if route.filter: |
---|
| 371 | newargs = route.filter(newargs) |
---|
| 372 | if not route.static or (route.static and not route.external): |
---|
| 373 | # Handle sub-domains, retain sub_domain if there is one |
---|
| 374 | sub = newargs.get('sub_domain', None) |
---|
| 375 | newargs = _subdomain_check(newargs, self.mapper, |
---|
| 376 | self.environ) |
---|
| 377 | # If the route requires a sub-domain, and we have it, restore |
---|
| 378 | # it |
---|
| 379 | if 'sub_domain' in route.defaults: |
---|
| 380 | newargs['sub_domain'] = sub |
---|
| 381 | |
---|
| 382 | elif use_current: |
---|
| 383 | newargs = _screenargs(kargs, self.mapper, self.environ, force_explicit=True) |
---|
| 384 | elif 'sub_domain' in kargs: |
---|
| 385 | newargs = _subdomain_check(kargs, self.mapper, self.environ) |
---|
| 386 | else: |
---|
| 387 | newargs = kargs |
---|
| 388 | |
---|
| 389 | anchor = anchor or newargs.pop('_anchor', None) |
---|
| 390 | host = host or newargs.pop('_host', None) |
---|
| 391 | protocol = protocol or newargs.pop('_protocol', None) |
---|
| 392 | newargs['_environ'] = self.environ |
---|
| 393 | url = self.mapper.generate(*route_args, **newargs) |
---|
| 394 | if anchor is not None: |
---|
| 395 | url += '#' + _url_quote(anchor, encoding) |
---|
| 396 | if host or protocol or qualified: |
---|
| 397 | if 'routes.cached_hostinfo' not in self.environ: |
---|
| 398 | cache_hostinfo(self.environ) |
---|
| 399 | hostinfo = self.environ['routes.cached_hostinfo'] |
---|
| 400 | |
---|
| 401 | if not host and not qualified: |
---|
| 402 | # Ensure we don't use a specific port, as changing the protocol |
---|
| 403 | # means that we most likely need a new port |
---|
| 404 | host = hostinfo['host'].split(':')[0] |
---|
| 405 | elif not host: |
---|
| 406 | host = hostinfo['host'] |
---|
| 407 | if not protocol: |
---|
| 408 | protocol = hostinfo['protocol'] |
---|
| 409 | if url is not None: |
---|
| 410 | if host[-1] != '/': |
---|
| 411 | host += '/' |
---|
| 412 | url = protocol + '://' + host + url.lstrip('/') |
---|
| 413 | |
---|
| 414 | if not isinstance(url, str) and url is not None: |
---|
| 415 | raise GenerationException("Can only return a string, got " |
---|
| 416 | "unicode instead: %s" % url) |
---|
| 417 | if url is None: |
---|
| 418 | raise GenerationException( |
---|
| 419 | "Could not generate URL. Called with args: %s %s" % \ |
---|
| 420 | (args, kargs)) |
---|
| 421 | return url |
---|
| 422 | |
---|
| 423 | def current(self, *args, **kwargs): |
---|
| 424 | """Generate a route that includes params used on the current |
---|
| 425 | request |
---|
| 426 | |
---|
| 427 | The arguments for this method are identical to ``__call__`` |
---|
| 428 | except that arguments set to None will remove existing route |
---|
| 429 | matches of the same name from the set of arguments used to |
---|
| 430 | construct a URL. |
---|
| 431 | """ |
---|
| 432 | return self(_use_current=True, *args, **kwargs) |
---|
| 433 | |
---|
| 434 | |
---|
| 435 | def redirect_to(*args, **kargs): |
---|
| 436 | """Issues a redirect based on the arguments. |
---|
| 437 | |
---|
| 438 | Redirect's *should* occur as a "302 Moved" header, however the web |
---|
| 439 | framework may utilize a different method. |
---|
| 440 | |
---|
| 441 | All arguments are passed to url_for to retrieve the appropriate URL, then |
---|
| 442 | the resulting URL it sent to the redirect function as the URL. |
---|
| 443 | """ |
---|
| 444 | target = url_for(*args, **kargs) |
---|
| 445 | config = request_config() |
---|
| 446 | return config.redirect(target) |
---|
| 447 | |
---|
| 448 | |
---|
| 449 | def cache_hostinfo(environ): |
---|
| 450 | """Processes the host information and stores a copy |
---|
| 451 | |
---|
| 452 | This work was previously done but wasn't stored in environ, nor is |
---|
| 453 | it guaranteed to be setup in the future (Routes 2 and beyond). |
---|
| 454 | |
---|
| 455 | cache_hostinfo processes environ keys that may be present to |
---|
| 456 | determine the proper host, protocol, and port information to use |
---|
| 457 | when generating routes. |
---|
| 458 | |
---|
| 459 | """ |
---|
| 460 | hostinfo = {} |
---|
| 461 | if environ.get('HTTPS') or environ.get('wsgi.url_scheme') == 'https' \ |
---|
| 462 | or environ.get('HTTP_X_FORWARDED_PROTO') == 'https': |
---|
| 463 | hostinfo['protocol'] = 'https' |
---|
| 464 | else: |
---|
| 465 | hostinfo['protocol'] = 'http' |
---|
| 466 | if environ.get('HTTP_X_FORWARDED_HOST'): |
---|
| 467 | hostinfo['host'] = environ['HTTP_X_FORWARDED_HOST'] |
---|
| 468 | elif environ.get('HTTP_HOST'): |
---|
| 469 | hostinfo['host'] = environ['HTTP_HOST'] |
---|
| 470 | else: |
---|
| 471 | hostinfo['host'] = environ['SERVER_NAME'] |
---|
| 472 | if environ.get('wsgi.url_scheme') == 'https': |
---|
| 473 | if environ['SERVER_PORT'] != '443': |
---|
| 474 | hostinfo['host'] += ':' + environ['SERVER_PORT'] |
---|
| 475 | else: |
---|
| 476 | if environ['SERVER_PORT'] != '80': |
---|
| 477 | hostinfo['host'] += ':' + environ['SERVER_PORT'] |
---|
| 478 | environ['routes.cached_hostinfo'] = hostinfo |
---|
| 479 | return hostinfo |
---|
| 480 | |
---|
| 481 | |
---|
| 482 | def controller_scan(directory=None): |
---|
| 483 | """Scan a directory for python files and use them as controllers""" |
---|
| 484 | if directory is None: |
---|
| 485 | return [] |
---|
| 486 | |
---|
| 487 | def find_controllers(dirname, prefix=''): |
---|
| 488 | """Locate controllers in a directory""" |
---|
| 489 | controllers = [] |
---|
| 490 | for fname in os.listdir(dirname): |
---|
| 491 | filename = os.path.join(dirname, fname) |
---|
| 492 | if os.path.isfile(filename) and \ |
---|
| 493 | re.match('^[^_]{1,1}.*\.py$', fname): |
---|
| 494 | controllers.append(prefix + fname[:-3]) |
---|
| 495 | elif os.path.isdir(filename): |
---|
| 496 | controllers.extend(find_controllers(filename, |
---|
| 497 | prefix=prefix+fname+'/')) |
---|
| 498 | return controllers |
---|
| 499 | def longest_first(fst, lst): |
---|
| 500 | """Compare the length of one string to another, shortest goes first""" |
---|
| 501 | return cmp(len(lst), len(fst)) |
---|
| 502 | controllers = find_controllers(directory) |
---|
| 503 | controllers.sort(longest_first) |
---|
| 504 | return controllers |
---|