0001"""Utility functions for use in templates / controllers
0002
0003*PLEASE NOTE*: Many of these functions expect an initialized RequestConfig
0004object. This is expected to have been initialized for EACH REQUEST by the web
0005framework.
0006
0007"""
0008import os
0009import re
0010import urllib
0011from routes import request_config
0012
0013def _screenargs(kargs):
0014 """
0015 Private function that takes a dict, and screens it against the current
0016 request dict to determine what the dict should look like that is used.
0017 This is responsible for the requests "memory" of the current.
0018 """
0019 config = request_config()
0020
0021
0022 encoding = config.mapper.encoding
0023 for key, val in kargs.iteritems():
0024 if isinstance(val, unicode):
0025 kargs[key] = val.encode(encoding)
0026
0027 if config.mapper.explicit and config.mapper.sub_domains:
0028 return _subdomain_check(config, kargs)
0029 elif config.mapper.explicit:
0030 return kargs
0031
0032 controller_name = kargs.get('controller')
0033
0034 if controller_name and controller_name.startswith('/'):
0035
0036 kargs['controller'] = kargs['controller'][1:]
0037 return kargs
0038 elif controller_name and not kargs.has_key('action'):
0039
0040 kargs['action'] = 'index'
0041
0042 memory_kargs = getattr(config, 'mapper_dict', {}).copy()
0043
0044
0045 for key in [key for key in kargs.keys() if kargs[key] is None]:
0046 del kargs[key]
0047 if memory_kargs.has_key(key):
0048 del memory_kargs[key]
0049
0050
0051 memory_kargs.update(kargs)
0052
0053
0054 if config.mapper.sub_domains:
0055 memory_kargs = _subdomain_check(config, memory_kargs)
0056
0057 return memory_kargs
0058
0059def _subdomain_check(config, kargs):
0060 """Screen the kargs for a subdomain and alter it appropriately depending
0061 on the current subdomain or lack therof."""
0062 if config.mapper.sub_domains:
0063 subdomain = kargs.pop('sub_domain', None)
0064 if isinstance(subdomain, unicode):
0065 subdomain = str(subdomain)
0066 fullhost = config.environ.get('HTTP_HOST') or config.environ.get('SERVER_NAME')
0068 hostmatch = fullhost.split(':')
0069 host = hostmatch[0]
0070 port = ''
0071 if len(hostmatch) > 1:
0072 port += ':' + hostmatch[1]
0073 sub_match = re.compile('^.+?\.(%s)$' % config.mapper.domain_match)
0074 domain = re.sub(sub_match, r'\1', host)
0075 if subdomain and not host.startswith(subdomain) and subdomain not in config.mapper.sub_domains_ignore:
0077 kargs['_host'] = subdomain + '.' + domain + port
0078 elif (subdomain in config.mapper.sub_domains_ignore or subdomain is None) and domain != host:
0080 kargs['_host'] = domain + port
0081 return kargs
0082 else:
0083 return kargs
0084
0085
0086def _url_quote(string, encoding):
0087 """A Unicode handling version of urllib.quote_plus."""
0088 if encoding:
0089 return urllib.quote_plus(unicode(string).encode(encoding), '/')
0090 else:
0091 return urllib.quote_plus(str(string), '/')
0092
0093def url_for(*args, **kargs):
0094 """Generates a URL
0095
0096 All keys given to url_for are sent to the Routes Mapper instance for
0097 generation except for::
0098
0099 anchor specified the anchor name to be appened to the path
0100 host overrides the default (current) host if provided
0101 protocol overrides the default (current) protocol if provided
0102 qualified creates the URL with the host/port information as
0103 needed
0104
0105 The URL is generated based on the rest of the keys. When generating a new
0106 URL, values will be used from the current request's parameters (if
0107 present). The following rules are used to determine when and how to keep
0108 the current requests parameters:
0109
0110 * If the controller is present and begins with '/', no defaults are used
0111 * If the controller is changed, action is set to 'index' unless otherwise
0112 specified
0113
0114 For example, if the current request yielded a dict of
0115 {'controller': 'blog', 'action': 'view', 'id': 2}, with the standard
0116 ':controller/:action/:id' route, you'd get the following results::
0117
0118 url_for(id=4) => '/blog/view/4',
0119 url_for(controller='/admin') => '/admin',
0120 url_for(controller='admin') => '/admin/view/2'
0121 url_for(action='edit') => '/blog/edit/2',
0122 url_for(action='list', id=None) => '/blog/list'
0123
0124 **Static and Named Routes**
0125
0126 If there is a string present as the first argument, a lookup is done
0127 against the named routes table to see if there's any matching routes. The
0128 keyword defaults used with static routes will be sent in as GET query
0129 arg's if a route matches.
0130
0131 If no route by that name is found, the string is assumed to be a raw URL.
0132 Should the raw URL begin with ``/`` then appropriate SCRIPT_NAME data will
0133 be added if present, otherwise the string will be used as the url with
0134 keyword args becoming GET query args.
0135 """
0136 anchor = kargs.get('anchor')
0137 host = kargs.get('host')
0138 protocol = kargs.get('protocol')
0139 qualified = kargs.pop('qualified', None)
0140
0141
0142 for key in ['anchor', 'host', 'protocol']:
0143 if kargs.get(key):
0144 del kargs[key]
0145 if kargs.has_key(key+'_'):
0146 kargs[key] = kargs.pop(key+'_')
0147 config = request_config()
0148 route = None
0149 static = False
0150 encoding = config.mapper.encoding
0151 url = ''
0152 if len(args) > 0:
0153 route = config.mapper._routenames.get(args[0])
0154
0155 if route and route.defaults.has_key('_static'):
0156 static = True
0157 url = route.routepath
0158
0159
0160 if not route:
0161 static = True
0162 url = args[0]
0163
0164 if url.startswith('/') and hasattr(config, 'environ') and config.environ.get('SCRIPT_NAME'):
0166 url = config.environ.get('SCRIPT_NAME') + url
0167
0168 if static:
0169 if kargs:
0170 url += '?'
0171 query_args = []
0172 for key, val in kargs.iteritems():
0173 query_args.append("%s=%s" % (
0174 urllib.quote_plus(unicode(key).encode(encoding)),
0175 urllib.quote_plus(unicode(val).encode(encoding))))
0176 url += '&'.join(query_args)
0177 if not static:
0178 route_args = []
0179 if route:
0180 if config.mapper.hardcode_names:
0181 route_args.append(route)
0182 newargs = route.defaults.copy()
0183 newargs.update(kargs)
0184
0185
0186 if route.filter:
0187 newargs = route.filter(newargs)
0188
0189
0190 newargs = _subdomain_check(config, newargs)
0191 else:
0192 newargs = _screenargs(kargs)
0193 anchor = newargs.pop('_anchor', None) or anchor
0194 host = newargs.pop('_host', None) or host
0195 protocol = newargs.pop('_protocol', None) or protocol
0196 url = config.mapper.generate(*route_args, **newargs)
0197 if anchor:
0198 url += '#' + _url_quote(anchor, encoding)
0199 if host or protocol or qualified:
0200 if not host and not qualified:
0201
0202
0203 host = config.host.split(':')[0]
0204 elif not host:
0205 host = config.host
0206 if not protocol:
0207 protocol = config.protocol
0208 if url is not None:
0209 url = protocol + '://' + host + url
0210
0211 if not isinstance(url, str) and url is not None:
0212 raise Exception("url_for can only return a string or None, got "
0213 "unicode instead: %s" % url)
0214
0215 return url
0216
0217def redirect_to(*args, **kargs):
0218 """Issues a redirect based on the arguments.
0219
0220 Redirect's *should* occur as a "302 Moved" header, however the web
0221 framework may utilize a different method.
0222
0223 All arguments are passed to url_for to retrieve the appropriate URL, then
0224 the resulting URL it sent to the redirect function as the URL.
0225 """
0226 target = url_for(*args, **kargs)
0227 config = request_config()
0228 return config.redirect(target)
0229
0230def controller_scan(directory=None):
0231 """Scan a directory for python files and use them as controllers"""
0232 if directory is None:
0233 return []
0234
0235 def find_controllers(dirname, prefix=''):
0236 """Locate controllers in a directory"""
0237 controllers = []
0238 for fname in os.listdir(dirname):
0239 filename = os.path.join(dirname, fname)
0240 if os.path.isfile(filename) and re.match('^[^_]{1,1}.*\.py$', fname):
0242 controllers.append(prefix + fname[:-3])
0243 elif os.path.isdir(filename):
0244 controllers.extend(find_controllers(filename,
0245 prefix=prefix+fname+'/'))
0246 return controllers
0247 def longest_first(fst, lst):
0248 """Compare the length of one string to another, shortest goes first"""
0249 return cmp(len(lst), len(fst))
0250 controllers = find_controllers(directory)
0251 controllers.sort(longest_first)
0252 return controllers
0253
0254class RouteException(Exception):
0255 """Tossed during Route exceptions"""
0256 pass