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    # Coerce any unicode args with the encoding
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        # If the controller name starts with '/', ignore route memory
0036        kargs['controller'] = kargs['controller'][1:]
0037        return kargs
0038    elif controller_name and not kargs.has_key('action'):
0039        # Fill in an action if we don't have one, but have a controller
0040        kargs['action'] = 'index'
0041
0042    memory_kargs = getattr(config, 'mapper_dict', {}).copy()
0043
0044    # Remove keys from memory and kargs if kargs has them as None
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    # Merge the new args on top of the memory args
0051    memory_kargs.update(kargs)
0052
0053    # Setup a sub-domain if applicable
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    # Remove special words from kargs, convert placeholders
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        # No named route found, assume the argument is a relative path
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            # If this route has a filter, apply it
0186            if route.filter:
0187                newargs = route.filter(newargs)
0188
0189            # Handle sub-domains
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            # Ensure we don't use a specific port, as changing the protocol
0202            # means that we most likely need a new port
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