diff env/lib/python3.7/site-packages/routes/mapper.py @ 5:9b1c78e6ba9c draft default tip

"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author shellac
date Mon, 01 Jun 2020 08:59:25 -0400
parents 79f47841a781
children
line wrap: on
line diff
--- a/env/lib/python3.7/site-packages/routes/mapper.py	Thu May 14 16:47:39 2020 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1261 +0,0 @@
-"""Mapper and Sub-Mapper"""
-import re
-import threading
-
-from repoze.lru import LRUCache
-import six
-
-from routes import request_config
-from routes.util import (
-    controller_scan,
-    RoutesException,
-    as_unicode
-)
-from routes.route import Route
-
-
-COLLECTION_ACTIONS = ['index', 'create', 'new']
-MEMBER_ACTIONS = ['show', 'update', 'delete', 'edit']
-
-
-def strip_slashes(name):
-    """Remove slashes from the beginning and end of a part/URL."""
-    if name.startswith('/'):
-        name = name[1:]
-    if name.endswith('/'):
-        name = name[:-1]
-    return name
-
-
-class SubMapperParent(object):
-    """Base class for Mapper and SubMapper, both of which may be the parent
-    of SubMapper objects
-    """
-
-    def submapper(self, **kargs):
-        """Create a partial version of the Mapper with the designated
-        options set
-
-        This results in a :class:`routes.mapper.SubMapper` object.
-
-        If keyword arguments provided to this method also exist in the
-        keyword arguments provided to the submapper, their values will
-        be merged with the saved options going first.
-
-        In addition to :class:`routes.route.Route` arguments, submapper
-        can also take a ``path_prefix`` argument which will be
-        prepended to the path of all routes that are connected.
-
-        Example::
-
-            >>> map = Mapper(controller_scan=None)
-            >>> map.connect('home', '/', controller='home', action='splash')
-            >>> map.matchlist[0].name == 'home'
-            True
-            >>> m = map.submapper(controller='home')
-            >>> m.connect('index', '/index', action='index')
-            >>> map.matchlist[1].name == 'index'
-            True
-            >>> map.matchlist[1].defaults['controller'] == 'home'
-            True
-
-        Optional ``collection_name`` and ``resource_name`` arguments are
-        used in the generation of route names by the ``action`` and
-        ``link`` methods.  These in turn are used by the ``index``,
-        ``new``, ``create``, ``show``, ``edit``, ``update`` and
-        ``delete`` methods which may be invoked indirectly by listing
-        them in the ``actions`` argument.  If the ``formatted`` argument
-        is set to ``True`` (the default), generated paths are given the
-        suffix '{.format}' which matches or generates an optional format
-        extension.
-
-        Example::
-
-            >>> from routes.util import url_for
-            >>> map = Mapper(controller_scan=None)
-            >>> m = map.submapper(path_prefix='/entries', collection_name='entries', resource_name='entry', actions=['index', 'new'])
-            >>> url_for('entries') == '/entries'
-            True
-            >>> url_for('new_entry', format='xml') == '/entries/new.xml'
-            True
-
-        """
-        return SubMapper(self, **kargs)
-
-    def collection(self, collection_name, resource_name, path_prefix=None,
-                   member_prefix='/{id}', controller=None,
-                   collection_actions=COLLECTION_ACTIONS,
-                   member_actions=MEMBER_ACTIONS, member_options=None,
-                   **kwargs):
-        """Create a submapper that represents a collection.
-
-        This results in a :class:`routes.mapper.SubMapper` object, with a
-        ``member`` property of the same type that represents the collection's
-        member resources.
-
-        Its interface is the same as the ``submapper`` together with
-        ``member_prefix``, ``member_actions`` and ``member_options``
-        which are passed to the ``member`` submapper as ``path_prefix``,
-        ``actions`` and keyword arguments respectively.
-
-        Example::
-
-            >>> from routes.util import url_for
-            >>> map = Mapper(controller_scan=None)
-            >>> c = map.collection('entries', 'entry')
-            >>> c.member.link('ping', method='POST')
-            >>> url_for('entries') == '/entries'
-            True
-            >>> url_for('edit_entry', id=1) == '/entries/1/edit'
-            True
-            >>> url_for('ping_entry', id=1) == '/entries/1/ping'
-            True
-
-        """
-        if controller is None:
-            controller = resource_name or collection_name
-
-        if path_prefix is None:
-            if collection_name is None:
-                path_prefix_str = ''
-            else:
-                path_prefix_str = '/{collection_name}'
-        else:
-            if collection_name is None:
-                path_prefix_str = "{pre}"
-            else:
-                path_prefix_str = "{pre}/{collection_name}"
-
-        # generate what will be the path prefix for the collection
-        path_prefix = path_prefix_str.format(pre=path_prefix,
-                                             collection_name=collection_name)
-
-        collection = SubMapper(self, collection_name=collection_name,
-                               resource_name=resource_name,
-                               path_prefix=path_prefix, controller=controller,
-                               actions=collection_actions, **kwargs)
-
-        collection.member = SubMapper(collection, path_prefix=member_prefix,
-                                      actions=member_actions,
-                                      **(member_options or {}))
-
-        return collection
-
-
-class SubMapper(SubMapperParent):
-    """Partial mapper for use with_options"""
-    def __init__(self, obj, resource_name=None, collection_name=None,
-                 actions=None, formatted=None, **kwargs):
-        self.kwargs = kwargs
-        self.obj = obj
-        self.collection_name = collection_name
-        self.member = None
-        self.resource_name = resource_name \
-            or getattr(obj, 'resource_name', None) \
-            or kwargs.get('controller', None) \
-            or getattr(obj, 'controller', None)
-        if formatted is not None:
-            self.formatted = formatted
-        else:
-            self.formatted = getattr(obj, 'formatted', None)
-            if self.formatted is None:
-                self.formatted = True
-        self.add_actions(actions or [], **kwargs)
-
-    def connect(self, routename, path=None, **kwargs):
-        newkargs = {}
-        _routename = routename
-        _path = path
-        for key, value in six.iteritems(self.kwargs):
-            if key == 'path_prefix':
-                if path is not None:
-                    # if there's a name_prefix, add it to the route name
-                    # and if there's a path_prefix
-                    _path = ''.join((self.kwargs[key], path))
-                else:
-                    _path = ''.join((self.kwargs[key], routename))
-            elif key == 'name_prefix':
-                if path is not None:
-                    # if there's a name_prefix, add it to the route name
-                    # and if there's a path_prefix
-                    _routename = ''.join((self.kwargs[key], routename))
-                else:
-                    _routename = None
-            elif key in kwargs:
-                if isinstance(value, dict):
-                    newkargs[key] = dict(value, **kwargs[key])  # merge dicts
-                else:
-                    # Originally used this form:
-                    # newkargs[key] = value + kwargs[key]
-                    # New version avoids the inheritance concatenation issue
-                    # with submappers. Only prefixes concatenate, everything
-                    # else overrides in submappers.
-                    newkargs[key] = kwargs[key]
-            else:
-                newkargs[key] = self.kwargs[key]
-        for key in kwargs:
-            if key not in self.kwargs:
-                newkargs[key] = kwargs[key]
-
-        newargs = (_routename, _path)
-        return self.obj.connect(*newargs, **newkargs)
-
-    def link(self, rel=None, name=None, action=None, method='GET',
-             formatted=None, **kwargs):
-        """Generates a named route for a subresource.
-
-        Example::
-
-            >>> from routes.util import url_for
-            >>> map = Mapper(controller_scan=None)
-            >>> c = map.collection('entries', 'entry')
-            >>> c.link('recent', name='recent_entries')
-            >>> c.member.link('ping', method='POST', formatted=True)
-            >>> url_for('entries') == '/entries'
-            True
-            >>> url_for('recent_entries') == '/entries/recent'
-            True
-            >>> url_for('ping_entry', id=1) == '/entries/1/ping'
-            True
-            >>> url_for('ping_entry', id=1, format='xml') == '/entries/1/ping.xml'
-            True
-
-        """
-        if formatted or (formatted is None and self.formatted):
-            suffix = '{.format}'
-        else:
-            suffix = ''
-
-        return self.connect(name or (rel + '_' + self.resource_name),
-                            '/' + (rel or name) + suffix,
-                            action=action or rel or name,
-                            **_kwargs_with_conditions(kwargs, method))
-
-    def new(self, **kwargs):
-        """Generates the "new" link for a collection submapper."""
-        return self.link(rel='new', **kwargs)
-
-    def edit(self, **kwargs):
-        """Generates the "edit" link for a collection member submapper."""
-        return self.link(rel='edit', **kwargs)
-
-    def action(self, name=None, action=None, method='GET', formatted=None,
-               **kwargs):
-        """Generates a named route at the base path of a submapper.
-
-        Example::
-
-            >>> from routes import url_for
-            >>> map = Mapper(controller_scan=None)
-            >>> c = map.submapper(path_prefix='/entries', controller='entry')
-            >>> c.action(action='index', name='entries', formatted=True)
-            >>> c.action(action='create', method='POST')
-            >>> url_for(controller='entry', action='index', method='GET') == '/entries'
-            True
-            >>> url_for(controller='entry', action='index', method='GET', format='xml') == '/entries.xml'
-            True
-            >>> url_for(controller='entry', action='create', method='POST') == '/entries'
-            True
-
-        """
-        if formatted or (formatted is None and self.formatted):
-            suffix = '{.format}'
-        else:
-            suffix = ''
-        return self.connect(name or (action + '_' + self.resource_name),
-                            suffix,
-                            action=action or name,
-                            **_kwargs_with_conditions(kwargs, method))
-
-    def index(self, name=None, **kwargs):
-        """Generates the "index" action for a collection submapper."""
-        return self.action(name=name or self.collection_name,
-                           action='index', method='GET', **kwargs)
-
-    def show(self, name=None, **kwargs):
-        """Generates the "show" action for a collection member submapper."""
-        return self.action(name=name or self.resource_name,
-                           action='show', method='GET', **kwargs)
-
-    def create(self, **kwargs):
-        """Generates the "create" action for a collection submapper."""
-        return self.action(action='create', method='POST', **kwargs)
-
-    def update(self, **kwargs):
-        """Generates the "update" action for a collection member submapper."""
-        return self.action(action='update', method='PUT', **kwargs)
-
-    def delete(self, **kwargs):
-        """Generates the "delete" action for a collection member submapper."""
-        return self.action(action='delete', method='DELETE', **kwargs)
-
-    def add_actions(self, actions, **kwargs):
-        [getattr(self, action)(**kwargs) for action in actions]
-
-    # Provided for those who prefer using the 'with' syntax in Python 2.5+
-    def __enter__(self):
-        return self
-
-    def __exit__(self, type, value, tb):
-        pass
-
-
-# Create kwargs with a 'conditions' member generated for the given method
-def _kwargs_with_conditions(kwargs, method):
-    if method and 'conditions' not in kwargs:
-        newkwargs = kwargs.copy()
-        newkwargs['conditions'] = {'method': method}
-        return newkwargs
-    else:
-        return kwargs
-
-
-class Mapper(SubMapperParent):
-    """Mapper handles URL generation and URL recognition in a web
-    application.
-
-    Mapper is built handling dictionary's. It is assumed that the web
-    application will handle the dictionary returned by URL recognition
-    to dispatch appropriately.
-
-    URL generation is done by passing keyword parameters into the
-    generate function, a URL is then returned.
-
-    """
-    def __init__(self, controller_scan=controller_scan, directory=None,
-                 always_scan=False, register=True, explicit=True):
-        """Create a new Mapper instance
-
-        All keyword arguments are optional.
-
-        ``controller_scan``
-            Function reference that will be used to return a list of
-            valid controllers used during URL matching. If
-            ``directory`` keyword arg is present, it will be passed
-            into the function during its call. This option defaults to
-            a function that will scan a directory for controllers.
-
-            Alternatively, a list of controllers or None can be passed
-            in which are assumed to be the definitive list of
-            controller names valid when matching 'controller'.
-
-        ``directory``
-            Passed into controller_scan for the directory to scan. It
-            should be an absolute path if using the default
-            ``controller_scan`` function.
-
-        ``always_scan``
-            Whether or not the ``controller_scan`` function should be
-            run during every URL match. This is typically a good idea
-            during development so the server won't need to be restarted
-            anytime a controller is added.
-
-        ``register``
-            Boolean used to determine if the Mapper should use
-            ``request_config`` to register itself as the mapper. Since
-            it's done on a thread-local basis, this is typically best
-            used during testing though it won't hurt in other cases.
-
-        ``explicit``
-            Boolean used to determine if routes should be connected
-            with implicit defaults of::
-
-                {'controller':'content','action':'index','id':None}
-
-            When set to True, these defaults will not be added to route
-            connections and ``url_for`` will not use Route memory.
-
-        Additional attributes that may be set after mapper
-        initialization (ie, map.ATTRIBUTE = 'something'):
-
-        ``encoding``
-            Used to indicate alternative encoding/decoding systems to
-            use with both incoming URL's, and during Route generation
-            when passed a Unicode string. Defaults to 'utf-8'.
-
-        ``decode_errors``
-            How to handle errors in the encoding, generally ignoring
-            any chars that don't convert should be sufficient. Defaults
-            to 'ignore'.
-
-        ``minimization``
-            Boolean used to indicate whether or not Routes should
-            minimize URL's and the generated URL's, or require every
-            part where it appears in the path. Defaults to True.
-
-        ``hardcode_names``
-            Whether or not Named Routes result in the default options
-            for the route being used *or* if they actually force url
-            generation to use the route. Defaults to False.
-
-        """
-        self.matchlist = []
-        self.maxkeys = {}
-        self.minkeys = {}
-        self.urlcache = LRUCache(1600)
-        self._created_regs = False
-        self._created_gens = False
-        self._master_regexp = None
-        self.prefix = None
-        self.req_data = threading.local()
-        self.directory = directory
-        self.always_scan = always_scan
-        self.controller_scan = controller_scan
-        self._regprefix = None
-        self._routenames = {}
-        self.debug = False
-        self.append_slash = False
-        self.sub_domains = False
-        self.sub_domains_ignore = []
-        self.domain_match = '[^\.\/]+?\.[^\.\/]+'
-        self.explicit = explicit
-        self.encoding = 'utf-8'
-        self.decode_errors = 'ignore'
-        self.hardcode_names = True
-        self.minimization = False
-        self.create_regs_lock = threading.Lock()
-        if register:
-            config = request_config()
-            config.mapper = self
-
-    def __str__(self):
-        """Generates a tabular string representation."""
-        def format_methods(r):
-            if r.conditions:
-                method = r.conditions.get('method', '')
-                return type(method) is str and method or ', '.join(method)
-            else:
-                return ''
-
-        table = [('Route name', 'Methods', 'Path', 'Controller', 'action')] + \
-                [(r.name or '', format_methods(r), r.routepath or '',
-                  r.defaults.get('controller', ''), r.defaults.get('action', ''))
-                 for r in self.matchlist]
-
-        widths = [max(len(row[col]) for row in table)
-                  for col in range(len(table[0]))]
-
-        return '\n'.join(
-            ' '.join(row[col].ljust(widths[col])
-                     for col in range(len(widths)))
-            for row in table)
-
-    def _envget(self):
-        try:
-            return self.req_data.environ
-        except AttributeError:
-            return None
-
-    def _envset(self, env):
-        self.req_data.environ = env
-
-    def _envdel(self):
-        del self.req_data.environ
-    environ = property(_envget, _envset, _envdel)
-
-    def extend(self, routes, path_prefix=''):
-        """Extends the mapper routes with a list of Route objects
-
-        If a path_prefix is provided, all the routes will have their
-        path prepended with the path_prefix.
-
-        Example::
-
-            >>> map = Mapper(controller_scan=None)
-            >>> map.connect('home', '/', controller='home', action='splash')
-            >>> map.matchlist[0].name == 'home'
-            True
-            >>> routes = [Route('index', '/index.htm', controller='home',
-            ...                 action='index')]
-            >>> map.extend(routes)
-            >>> len(map.matchlist) == 2
-            True
-            >>> map.extend(routes, path_prefix='/subapp')
-            >>> len(map.matchlist) == 3
-            True
-            >>> map.matchlist[2].routepath == '/subapp/index.htm'
-            True
-
-        .. note::
-
-            This function does not merely extend the mapper with the
-            given list of routes, it actually creates new routes with
-            identical calling arguments.
-
-        """
-        for route in routes:
-            if path_prefix and route.minimization:
-                routepath = '/'.join([path_prefix, route.routepath])
-            elif path_prefix:
-                routepath = path_prefix + route.routepath
-            else:
-                routepath = route.routepath
-            self.connect(route.name, routepath, **route._kargs)
-
-    def make_route(self, *args, **kargs):
-        """Make a new Route object
-
-        A subclass can override this method to use a custom Route class.
-        """
-        return Route(*args, **kargs)
-
-    def connect(self, *args, **kargs):
-        """Create and connect a new Route to the Mapper.
-
-        Usage:
-
-        .. code-block:: python
-
-            m = Mapper()
-            m.connect(':controller/:action/:id')
-            m.connect('date/:year/:month/:day', controller="blog",
-                      action="view")
-            m.connect('archives/:page', controller="blog", action="by_page",
-            requirements = { 'page':'\d{1,2}' })
-            m.connect('category_list', 'archives/category/:section',
-                      controller='blog', action='category',
-                      section='home', type='list')
-            m.connect('home', '', controller='blog', action='view',
-                      section='home')
-
-        """
-        routename = None
-        if len(args) > 1:
-            routename = args[0]
-        else:
-            args = (None,) + args
-        if '_explicit' not in kargs:
-            kargs['_explicit'] = self.explicit
-        if '_minimize' not in kargs:
-            kargs['_minimize'] = self.minimization
-        route = self.make_route(*args, **kargs)
-
-        # Apply encoding and errors if its not the defaults and the route
-        # didn't have one passed in.
-        if (self.encoding != 'utf-8' or self.decode_errors != 'ignore') and \
-           '_encoding' not in kargs:
-            route.encoding = self.encoding
-            route.decode_errors = self.decode_errors
-
-        if not route.static:
-            self.matchlist.append(route)
-
-        if routename:
-            self._routenames[routename] = route
-            route.name = routename
-        if route.static:
-            return
-        exists = False
-        for key in self.maxkeys:
-            if key == route.maxkeys:
-                self.maxkeys[key].append(route)
-                exists = True
-                break
-        if not exists:
-            self.maxkeys[route.maxkeys] = [route]
-        self._created_gens = False
-
-    def _create_gens(self):
-        """Create the generation hashes for route lookups"""
-        # Use keys temporailly to assemble the list to avoid excessive
-        # list iteration testing with "in"
-        controllerlist = {}
-        actionlist = {}
-
-        # Assemble all the hardcoded/defaulted actions/controllers used
-        for route in self.matchlist:
-            if route.static:
-                continue
-            if 'controller' in route.defaults:
-                controllerlist[route.defaults['controller']] = True
-            if 'action' in route.defaults:
-                actionlist[route.defaults['action']] = True
-
-        # Setup the lists of all controllers/actions we'll add each route
-        # to. We include the '*' in the case that a generate contains a
-        # controller/action that has no hardcodes
-        controllerlist = list(controllerlist.keys()) + ['*']
-        actionlist = list(actionlist.keys()) + ['*']
-
-        # Go through our list again, assemble the controllers/actions we'll
-        # add each route to. If its hardcoded, we only add it to that dict key.
-        # Otherwise we add it to every hardcode since it can be changed.
-        gendict = {}  # Our generated two-deep hash
-        for route in self.matchlist:
-            if route.static:
-                continue
-            clist = controllerlist
-            alist = actionlist
-            if 'controller' in route.hardcoded:
-                clist = [route.defaults['controller']]
-            if 'action' in route.hardcoded:
-                alist = [six.text_type(route.defaults['action'])]
-            for controller in clist:
-                for action in alist:
-                    actiondict = gendict.setdefault(controller, {})
-                    actiondict.setdefault(action, ([], {}))[0].append(route)
-        self._gendict = gendict
-        self._created_gens = True
-
-    def create_regs(self, *args, **kwargs):
-        """Atomically creates regular expressions for all connected
-        routes
-        """
-        self.create_regs_lock.acquire()
-        try:
-            self._create_regs(*args, **kwargs)
-        finally:
-            self.create_regs_lock.release()
-
-    def _create_regs(self, clist=None):
-        """Creates regular expressions for all connected routes"""
-        if clist is None:
-            if self.directory:
-                clist = self.controller_scan(self.directory)
-            elif callable(self.controller_scan):
-                clist = self.controller_scan()
-            elif not self.controller_scan:
-                clist = []
-            else:
-                clist = self.controller_scan
-
-        for key, val in six.iteritems(self.maxkeys):
-            for route in val:
-                route.makeregexp(clist)
-
-        regexps = []
-        routematches = []
-        for route in self.matchlist:
-            if not route.static:
-                routematches.append(route)
-                regexps.append(route.makeregexp(clist, include_names=False))
-        self._routematches = routematches
-
-        # Create our regexp to strip the prefix
-        if self.prefix:
-            self._regprefix = re.compile(self.prefix + '(.*)')
-
-        # Save the master regexp
-        regexp = '|'.join(['(?:%s)' % x for x in regexps])
-        self._master_reg = regexp
-        try:
-            self._master_regexp = re.compile(regexp)
-        except OverflowError:
-            self._master_regexp = None
-        self._created_regs = True
-
-    def _match(self, url, environ):
-        """Internal Route matcher
-
-        Matches a URL against a route, and returns a tuple of the match
-        dict and the route object if a match is successfull, otherwise
-        it returns empty.
-
-        For internal use only.
-
-        """
-        if not self._created_regs and self.controller_scan:
-            self.create_regs()
-        elif not self._created_regs:
-            raise RoutesException("You must generate the regular expressions"
-                                  " before matching.")
-
-        if self.always_scan:
-            self.create_regs()
-
-        matchlog = []
-        if self.prefix:
-            if re.match(self._regprefix, url):
-                url = re.sub(self._regprefix, r'\1', url)
-                if not url:
-                    url = '/'
-            else:
-                return (None, None, matchlog)
-
-        environ = environ or self.environ
-        sub_domains = self.sub_domains
-        sub_domains_ignore = self.sub_domains_ignore
-        domain_match = self.domain_match
-        debug = self.debug
-
-        if self._master_regexp is not None:
-            # Check to see if its a valid url against the main regexp
-            # Done for faster invalid URL elimination
-            valid_url = re.match(self._master_regexp, url)
-        else:
-            # Regex is None due to OverflowError caused by too many routes.
-            # This will allow larger projects to work but might increase time
-            # spent invalidating URLs in the loop below.
-            valid_url = True
-        if not valid_url:
-            return (None, None, matchlog)
-
-        for route in self.matchlist:
-            if route.static:
-                if debug:
-                    matchlog.append(dict(route=route, static=True))
-                continue
-            match = route.match(url, environ, sub_domains, sub_domains_ignore,
-                                domain_match)
-            if debug:
-                matchlog.append(dict(route=route, regexp=bool(match)))
-            if isinstance(match, dict) or match:
-                return (match, route, matchlog)
-        return (None, None, matchlog)
-
-    def match(self, url=None, environ=None):
-        """Match a URL against against one of the routes contained.
-
-        Will return None if no valid match is found.
-
-        .. code-block:: python
-
-            resultdict = m.match('/joe/sixpack')
-
-        """
-        if url is None and not environ:
-            raise RoutesException('URL or environ must be provided')
-
-        if url is None:
-            url = environ['PATH_INFO']
-
-        result = self._match(url, environ)
-        if self.debug:
-            return result[0], result[1], result[2]
-        if isinstance(result[0], dict) or result[0]:
-            return result[0]
-        return None
-
-    def routematch(self, url=None, environ=None):
-        """Match a URL against against one of the routes contained.
-
-        Will return None if no valid match is found, otherwise a
-        result dict and a route object is returned.
-
-        .. code-block:: python
-
-            resultdict, route_obj = m.match('/joe/sixpack')
-
-        """
-        if url is None and not environ:
-            raise RoutesException('URL or environ must be provided')
-
-        if url is None:
-            url = environ['PATH_INFO']
-        result = self._match(url, environ)
-        if self.debug:
-            return result[0], result[1], result[2]
-        if isinstance(result[0], dict) or result[0]:
-            return result[0], result[1]
-        return None
-
-    def generate(self, *args, **kargs):
-        """Generate a route from a set of keywords
-
-        Returns the url text, or None if no URL could be generated.
-
-        .. code-block:: python
-
-            m.generate(controller='content',action='view',id=10)
-
-        """
-        # Generate ourself if we haven't already
-        if not self._created_gens:
-            self._create_gens()
-
-        if self.append_slash:
-            kargs['_append_slash'] = True
-
-        if not self.explicit:
-            if 'controller' not in kargs:
-                kargs['controller'] = 'content'
-            if 'action' not in kargs:
-                kargs['action'] = 'index'
-
-        environ = kargs.pop('_environ', self.environ) or {}
-        if 'SCRIPT_NAME' in environ:
-            script_name = environ['SCRIPT_NAME']
-        elif self.environ and 'SCRIPT_NAME' in self.environ:
-            script_name = self.environ['SCRIPT_NAME']
-        else:
-            script_name = ""
-        controller = kargs.get('controller', None)
-        action = kargs.get('action', None)
-
-        # If the URL didn't depend on the SCRIPT_NAME, we'll cache it
-        # keyed by just by kargs; otherwise we need to cache it with
-        # both SCRIPT_NAME and kargs:
-        cache_key = six.text_type(args).encode('utf8') + \
-            six.text_type(kargs).encode('utf8')
-
-        if self.urlcache is not None:
-            if six.PY3:
-                cache_key_script_name = b':'.join((script_name.encode('utf-8'),
-                                                   cache_key))
-            else:
-                cache_key_script_name = '%s:%s' % (script_name, cache_key)
-
-            # Check the url cache to see if it exists, use it if it does
-            val = self.urlcache.get(cache_key_script_name, self)
-            if val != self:
-                return val
-
-        controller = as_unicode(controller, self.encoding)
-        action = as_unicode(action, self.encoding)
-
-        actionlist = self._gendict.get(controller) or self._gendict.get('*', {})
-        if not actionlist and not args:
-            return None
-        (keylist, sortcache) = actionlist.get(action) or \
-            actionlist.get('*', (None, {}))
-        if not keylist and not args:
-            return None
-
-        keys = frozenset(kargs.keys())
-        cacheset = False
-        cachekey = six.text_type(keys)
-        cachelist = sortcache.get(cachekey)
-        if args:
-            keylist = args
-        elif cachelist:
-            keylist = cachelist
-        else:
-            cacheset = True
-            newlist = []
-            for route in keylist:
-                if len(route.minkeys - route.dotkeys - keys) == 0:
-                    newlist.append(route)
-            keylist = newlist
-
-            class KeySorter:
-
-                def __init__(self, obj, *args):
-                    self.obj = obj
-
-                def __lt__(self, other):
-                    return self._keysort(self.obj, other.obj) < 0
-
-                def _keysort(self, a, b):
-                    """Sorts two sets of sets, to order them ideally for
-                    matching."""
-                    a = a.maxkeys
-                    b = b.maxkeys
-
-                    lendiffa = len(keys ^ a)
-                    lendiffb = len(keys ^ b)
-                    # If they both match, don't switch them
-                    if lendiffa == 0 and lendiffb == 0:
-                        return 0
-
-                    # First, if a matches exactly, use it
-                    if lendiffa == 0:
-                        return -1
-
-                    # Or b matches exactly, use it
-                    if lendiffb == 0:
-                        return 1
-
-                    # Neither matches exactly, return the one with the most in
-                    # common
-                    if self._compare(lendiffa, lendiffb) != 0:
-                        return self._compare(lendiffa, lendiffb)
-
-                    # Neither matches exactly, but if they both have just as
-                    # much in common
-                    if len(keys & b) == len(keys & a):
-                        # Then we return the shortest of the two
-                        return self._compare(len(a), len(b))
-
-                    # Otherwise, we return the one that has the most in common
-                    else:
-                        return self._compare(len(keys & b), len(keys & a))
-
-                def _compare(self, obj1, obj2):
-                    if obj1 < obj2:
-                        return -1
-                    elif obj1 < obj2:
-                        return 1
-                    else:
-                        return 0
-
-            keylist.sort(key=KeySorter)
-            if cacheset:
-                sortcache[cachekey] = keylist
-
-        # Iterate through the keylist of sorted routes (or a single route if
-        # it was passed in explicitly for hardcoded named routes)
-        for route in keylist:
-            fail = False
-            for key in route.hardcoded:
-                kval = kargs.get(key)
-                if not kval:
-                    continue
-                kval = as_unicode(kval, self.encoding)
-                if kval != route.defaults[key] and \
-                        not callable(route.defaults[key]):
-                    fail = True
-                    break
-            if fail:
-                continue
-            path = route.generate(**kargs)
-            if path:
-                if self.prefix:
-                    path = self.prefix + path
-                external_static = route.static and route.external
-                if not route.absolute and not external_static:
-                    path = script_name + path
-                    key = cache_key_script_name
-                else:
-                    key = cache_key
-                if self.urlcache is not None:
-                    self.urlcache.put(key, str(path))
-                return str(path)
-            else:
-                continue
-        return None
-
-    def resource(self, member_name, collection_name, **kwargs):
-        """Generate routes for a controller resource
-
-        The member_name name should be the appropriate singular version
-        of the resource given your locale and used with members of the
-        collection. The collection_name name will be used to refer to
-        the resource collection methods and should be a plural version
-        of the member_name argument. By default, the member_name name
-        will also be assumed to map to a controller you create.
-
-        The concept of a web resource maps somewhat directly to 'CRUD'
-        operations. The overlying things to keep in mind is that
-        mapping a resource is about handling creating, viewing, and
-        editing that resource.
-
-        All keyword arguments are optional.
-
-        ``controller``
-            If specified in the keyword args, the controller will be
-            the actual controller used, but the rest of the naming
-            conventions used for the route names and URL paths are
-            unchanged.
-
-        ``collection``
-            Additional action mappings used to manipulate/view the
-            entire set of resources provided by the controller.
-
-            Example::
-
-                map.resource('message', 'messages', collection={'rss':'GET'})
-                # GET /message/rss (maps to the rss action)
-                # also adds named route "rss_message"
-
-        ``member``
-            Additional action mappings used to access an individual
-            'member' of this controllers resources.
-
-            Example::
-
-                map.resource('message', 'messages', member={'mark':'POST'})
-                # POST /message/1/mark (maps to the mark action)
-                # also adds named route "mark_message"
-
-        ``new``
-            Action mappings that involve dealing with a new member in
-            the controller resources.
-
-            Example::
-
-                map.resource('message', 'messages', new={'preview':'POST'})
-                # POST /message/new/preview (maps to the preview action)
-                # also adds a url named "preview_new_message"
-
-        ``path_prefix``
-            Prepends the URL path for the Route with the path_prefix
-            given. This is most useful for cases where you want to mix
-            resources or relations between resources.
-
-        ``name_prefix``
-            Perpends the route names that are generated with the
-            name_prefix given. Combined with the path_prefix option,
-            it's easy to generate route names and paths that represent
-            resources that are in relations.
-
-            Example::
-
-                map.resource('message', 'messages', controller='categories',
-                    path_prefix='/category/:category_id',
-                    name_prefix="category_")
-                # GET /category/7/message/1
-                # has named route "category_message"
-
-        ``requirements``
-
-           A dictionary that restricts the matching of a
-           variable. Can be used when matching variables with path_prefix.
-
-           Example::
-
-                map.resource('message', 'messages',
-                     path_prefix='{project_id}/',
-                     requirements={"project_id": R"\d+"})
-                # POST /01234/message
-                #    success, project_id is set to "01234"
-                # POST /foo/message
-                #    404 not found, won't be matched by this route
-
-
-        ``parent_resource``
-            A ``dict`` containing information about the parent
-            resource, for creating a nested resource. It should contain
-            the ``member_name`` and ``collection_name`` of the parent
-            resource. This ``dict`` will
-            be available via the associated ``Route`` object which can
-            be accessed during a request via
-            ``request.environ['routes.route']``
-
-            If ``parent_resource`` is supplied and ``path_prefix``
-            isn't, ``path_prefix`` will be generated from
-            ``parent_resource`` as
-            "<parent collection name>/:<parent member name>_id".
-
-            If ``parent_resource`` is supplied and ``name_prefix``
-            isn't, ``name_prefix`` will be generated from
-            ``parent_resource`` as  "<parent member name>_".
-
-            Example::
-
-                >>> from routes.util import url_for
-                >>> m = Mapper()
-                >>> m.resource('location', 'locations',
-                ...            parent_resource=dict(member_name='region',
-                ...                                 collection_name='regions'))
-                >>> # path_prefix is "regions/:region_id"
-                >>> # name prefix is "region_"
-                >>> url_for('region_locations', region_id=13)
-                '/regions/13/locations'
-                >>> url_for('region_new_location', region_id=13)
-                '/regions/13/locations/new'
-                >>> url_for('region_location', region_id=13, id=60)
-                '/regions/13/locations/60'
-                >>> url_for('region_edit_location', region_id=13, id=60)
-                '/regions/13/locations/60/edit'
-
-            Overriding generated ``path_prefix``::
-
-                >>> m = Mapper()
-                >>> m.resource('location', 'locations',
-                ...            parent_resource=dict(member_name='region',
-                ...                                 collection_name='regions'),
-                ...            path_prefix='areas/:area_id')
-                >>> # name prefix is "region_"
-                >>> url_for('region_locations', area_id=51)
-                '/areas/51/locations'
-
-            Overriding generated ``name_prefix``::
-
-                >>> m = Mapper()
-                >>> m.resource('location', 'locations',
-                ...            parent_resource=dict(member_name='region',
-                ...                                 collection_name='regions'),
-                ...            name_prefix='')
-                >>> # path_prefix is "regions/:region_id"
-                >>> url_for('locations', region_id=51)
-                '/regions/51/locations'
-
-        """
-        collection = kwargs.pop('collection', {})
-        member = kwargs.pop('member', {})
-        new = kwargs.pop('new', {})
-        path_prefix = kwargs.pop('path_prefix', None)
-        name_prefix = kwargs.pop('name_prefix', None)
-        parent_resource = kwargs.pop('parent_resource', None)
-
-        # Generate ``path_prefix`` if ``path_prefix`` wasn't specified and
-        # ``parent_resource`` was. Likewise for ``name_prefix``. Make sure
-        # that ``path_prefix`` and ``name_prefix`` *always* take precedence if
-        # they are specified--in particular, we need to be careful when they
-        # are explicitly set to "".
-        if parent_resource is not None:
-            if path_prefix is None:
-                path_prefix = '%s/:%s_id' % (parent_resource['collection_name'],
-                                             parent_resource['member_name'])
-            if name_prefix is None:
-                name_prefix = '%s_' % parent_resource['member_name']
-        else:
-            if path_prefix is None:
-                path_prefix = ''
-            if name_prefix is None:
-                name_prefix = ''
-
-        # Ensure the edit and new actions are in and GET
-        member['edit'] = 'GET'
-        new.update({'new': 'GET'})
-
-        # Make new dict's based off the old, except the old values become keys,
-        # and the old keys become items in a list as the value
-        def swap(dct, newdct):
-            """Swap the keys and values in the dict, and uppercase the values
-            from the dict during the swap."""
-            for key, val in six.iteritems(dct):
-                newdct.setdefault(val.upper(), []).append(key)
-            return newdct
-        collection_methods = swap(collection, {})
-        member_methods = swap(member, {})
-        new_methods = swap(new, {})
-
-        # Insert create, update, and destroy methods
-        collection_methods.setdefault('POST', []).insert(0, 'create')
-        member_methods.setdefault('PUT', []).insert(0, 'update')
-        member_methods.setdefault('DELETE', []).insert(0, 'delete')
-
-        # If there's a path prefix option, use it with the controller
-        controller = strip_slashes(collection_name)
-        path_prefix = strip_slashes(path_prefix)
-        path_prefix = '/' + path_prefix
-        if path_prefix and path_prefix != '/':
-            path = path_prefix + '/' + controller
-        else:
-            path = '/' + controller
-        collection_path = path
-        new_path = path + "/new"
-        member_path = path + "/:(id)"
-
-        options = {
-            'controller': kwargs.get('controller', controller),
-            '_member_name': member_name,
-            '_collection_name': collection_name,
-            '_parent_resource': parent_resource,
-            '_filter': kwargs.get('_filter')
-        }
-        if 'requirements' in kwargs:
-            options['requirements'] = kwargs['requirements']
-
-        def requirements_for(meth):
-            """Returns a new dict to be used for all route creation as the
-            route options"""
-            opts = options.copy()
-            if method != 'any':
-                opts['conditions'] = {'method': [meth.upper()]}
-            return opts
-
-        # Add the routes for handling collection methods
-        for method, lst in six.iteritems(collection_methods):
-            primary = (method != 'GET' and lst.pop(0)) or None
-            route_options = requirements_for(method)
-            for action in lst:
-                route_options['action'] = action
-                route_name = "%s%s_%s" % (name_prefix, action, collection_name)
-                self.connect("formatted_" + route_name, "%s/%s.:(format)" %
-                             (collection_path, action), **route_options)
-                self.connect(route_name, "%s/%s" % (collection_path, action),
-                             **route_options)
-            if primary:
-                route_options['action'] = primary
-                self.connect("%s.:(format)" % collection_path, **route_options)
-                self.connect(collection_path, **route_options)
-
-        # Specifically add in the built-in 'index' collection method and its
-        # formatted version
-        self.connect("formatted_" + name_prefix + collection_name,
-                     collection_path + ".:(format)", action='index',
-                     conditions={'method': ['GET']}, **options)
-        self.connect(name_prefix + collection_name, collection_path,
-                     action='index', conditions={'method': ['GET']}, **options)
-
-        # Add the routes that deal with new resource methods
-        for method, lst in six.iteritems(new_methods):
-            route_options = requirements_for(method)
-            for action in lst:
-                name = "new_" + member_name
-                route_options['action'] = action
-                if action == 'new':
-                    path = new_path
-                    formatted_path = new_path + '.:(format)'
-                else:
-                    path = "%s/%s" % (new_path, action)
-                    name = action + "_" + name
-                    formatted_path = "%s/%s.:(format)" % (new_path, action)
-                self.connect("formatted_" + name_prefix + name, formatted_path,
-                             **route_options)
-                self.connect(name_prefix + name, path, **route_options)
-
-        requirements_regexp = '[^\/]+(?<!\\\)'
-
-        # Add the routes that deal with member methods of a resource
-        for method, lst in six.iteritems(member_methods):
-            route_options = requirements_for(method)
-            route_options['requirements'] = {'id': requirements_regexp}
-            if method not in ['POST', 'GET', 'any']:
-                primary = lst.pop(0)
-            else:
-                primary = None
-            for action in lst:
-                route_options['action'] = action
-                self.connect("formatted_%s%s_%s" % (name_prefix, action,
-                                                    member_name),
-                             "%s/%s.:(format)" % (member_path, action),
-                             **route_options)
-                self.connect("%s%s_%s" % (name_prefix, action, member_name),
-                             "%s/%s" % (member_path, action), **route_options)
-            if primary:
-                route_options['action'] = primary
-                self.connect("%s.:(format)" % member_path, **route_options)
-                self.connect(member_path, **route_options)
-
-        # Specifically add the member 'show' method
-        route_options = requirements_for('GET')
-        route_options['action'] = 'show'
-        route_options['requirements'] = {'id': requirements_regexp}
-        self.connect("formatted_" + name_prefix + member_name,
-                     member_path + ".:(format)", **route_options)
-        self.connect(name_prefix + member_name, member_path, **route_options)
-
-    def redirect(self, match_path, destination_path, *args, **kwargs):
-        """Add a redirect route to the mapper
-
-        Redirect routes bypass the wrapped WSGI application and instead
-        result in a redirect being issued by the RoutesMiddleware. As
-        such, this method is only meaningful when using
-        RoutesMiddleware.
-
-        By default, a 302 Found status code is used, this can be
-        changed by providing a ``_redirect_code`` keyword argument
-        which will then be used instead. Note that the entire status
-        code string needs to be present.
-
-        When using keyword arguments, all arguments that apply to
-        matching will be used for the match, while generation specific
-        options will be used during generation. Thus all options
-        normally available to connected Routes may be used with
-        redirect routes as well.
-
-        Example::
-
-            map = Mapper()
-            map.redirect('/legacyapp/archives/{url:.*}', '/archives/{url}')
-            map.redirect('/home/index', '/',
-                         _redirect_code='301 Moved Permanently')
-
-        """
-        both_args = ['_encoding', '_explicit', '_minimize']
-        gen_args = ['_filter']
-
-        status_code = kwargs.pop('_redirect_code', '302 Found')
-        gen_dict, match_dict = {}, {}
-
-        # Create the dict of args for the generation route
-        for key in both_args + gen_args:
-            if key in kwargs:
-                gen_dict[key] = kwargs[key]
-        gen_dict['_static'] = True
-
-        # Create the dict of args for the matching route
-        for key in kwargs:
-            if key not in gen_args:
-                match_dict[key] = kwargs[key]
-
-        self.connect(match_path, **match_dict)
-        match_route = self.matchlist[-1]
-
-        self.connect('_redirect_%s' % id(match_route), destination_path,
-                     **gen_dict)
-        match_route.redirect = True
-        match_route.redirect_status = status_code