diff env/lib/python3.7/site-packages/routes/route.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.7/site-packages/routes/route.py	Sat May 02 07:14:21 2020 -0400
@@ -0,0 +1,752 @@
+import re
+import sys
+if sys.version < '2.4':
+    from sets import ImmutableSet as frozenset
+
+import six
+from six.moves.urllib import parse as urlparse
+
+from routes.util import _url_quote as url_quote, _str_encode, as_unicode
+
+
+class Route(object):
+    """The Route object holds a route recognition and generation
+    routine.
+
+    See Route.__init__ docs for usage.
+
+    """
+    # reserved keys that don't count
+    reserved_keys = ['requirements']
+
+    # special chars to indicate a natural split in the URL
+    done_chars = ('/', ',', ';', '.', '#')
+
+    def __init__(self, name, routepath, **kargs):
+        """Initialize a route, with a given routepath for
+        matching/generation
+
+        The set of keyword args will be used as defaults.
+
+        Usage::
+
+            >>> from routes.base import Route
+            >>> newroute = Route(None, ':controller/:action/:id')
+            >>> sorted(newroute.defaults.items())
+            [('action', 'index'), ('id', None)]
+            >>> newroute = Route(None, 'date/:year/:month/:day',
+            ...     controller="blog", action="view")
+            >>> newroute = Route(None, 'archives/:page', controller="blog",
+            ...     action="by_page", requirements = { 'page':'\d{1,2}' })
+            >>> newroute.reqs
+            {'page': '\\\d{1,2}'}
+
+        .. Note::
+            Route is generally not called directly, a Mapper instance
+            connect method should be used to add routes.
+
+        """
+        self.routepath = routepath
+        self.sub_domains = False
+        self.prior = None
+        self.redirect = False
+        self.name = name
+        self._kargs = kargs
+        self.minimization = kargs.pop('_minimize', False)
+        self.encoding = kargs.pop('_encoding', 'utf-8')
+        self.reqs = kargs.get('requirements', {})
+        self.decode_errors = 'replace'
+
+        # Don't bother forming stuff we don't need if its a static route
+        self.static = kargs.pop('_static', False)
+        self.filter = kargs.pop('_filter', None)
+        self.absolute = kargs.pop('_absolute', False)
+
+        # Pull out the member/collection name if present, this applies only to
+        # map.resource
+        self.member_name = kargs.pop('_member_name', None)
+        self.collection_name = kargs.pop('_collection_name', None)
+        self.parent_resource = kargs.pop('_parent_resource', None)
+
+        # Pull out route conditions
+        self.conditions = kargs.pop('conditions', None)
+
+        # Determine if explicit behavior should be used
+        self.explicit = kargs.pop('_explicit', False)
+
+        # Since static need to be generated exactly, treat them as
+        # non-minimized
+        if self.static:
+            self.external = '://' in self.routepath
+            self.minimization = False
+
+        # Strip preceding '/' if present, and not minimizing
+        if routepath.startswith('/') and self.minimization:
+            self.routepath = routepath[1:]
+        self._setup_route()
+
+    def _setup_route(self):
+        # Build our routelist, and the keys used in the route
+        self.routelist = routelist = self._pathkeys(self.routepath)
+        routekeys = frozenset(key['name'] for key in routelist
+                              if isinstance(key, dict))
+        self.dotkeys = frozenset(key['name'] for key in routelist
+                                 if isinstance(key, dict) and
+                                 key['type'] == '.')
+
+        if not self.minimization:
+            self.make_full_route()
+
+        # Build a req list with all the regexp requirements for our args
+        self.req_regs = {}
+        for key, val in six.iteritems(self.reqs):
+            self.req_regs[key] = re.compile('^' + val + '$')
+        # Update our defaults and set new default keys if needed. defaults
+        # needs to be saved
+        (self.defaults, defaultkeys) = self._defaults(routekeys,
+                                                      self.reserved_keys,
+                                                      self._kargs.copy())
+        # Save the maximum keys we could utilize
+        self.maxkeys = defaultkeys | routekeys
+
+        # Populate our minimum keys, and save a copy of our backward keys for
+        # quicker generation later
+        (self.minkeys, self.routebackwards) = self._minkeys(routelist[:])
+
+        # Populate our hardcoded keys, these are ones that are set and don't
+        # exist in the route
+        self.hardcoded = frozenset(key for key in self.maxkeys
+                                   if key not in routekeys
+                                      and self.defaults[key] is not None)
+
+        # Cache our default keys
+        self._default_keys = frozenset(self.defaults.keys())
+
+    def make_full_route(self):
+        """Make a full routelist string for use with non-minimized
+        generation"""
+        regpath = ''
+        for part in self.routelist:
+            if isinstance(part, dict):
+                regpath += '%(' + part['name'] + ')s'
+            else:
+                regpath += part
+        self.regpath = regpath
+
+    def make_unicode(self, s):
+        """Transform the given argument into a unicode string."""
+        if isinstance(s, six.text_type):
+            return s
+        elif isinstance(s, bytes):
+            return s.decode(self.encoding)
+        elif callable(s):
+            return s
+        else:
+            return six.text_type(s)
+
+    def _pathkeys(self, routepath):
+        """Utility function to walk the route, and pull out the valid
+        dynamic/wildcard keys."""
+        collecting = False
+        current = ''
+        done_on = ''
+        var_type = ''
+        just_started = False
+        routelist = []
+        for char in routepath:
+            if char in [':', '*', '{'] and not collecting and not self.static \
+               or char in ['{'] and not collecting:
+                just_started = True
+                collecting = True
+                var_type = char
+                if char == '{':
+                    done_on = '}'
+                    just_started = False
+                if len(current) > 0:
+                    routelist.append(current)
+                    current = ''
+            elif collecting and just_started:
+                just_started = False
+                if char == '(':
+                    done_on = ')'
+                else:
+                    current = char
+                    done_on = self.done_chars + ('-',)
+            elif collecting and char not in done_on:
+                current += char
+            elif collecting:
+                collecting = False
+                if var_type == '{':
+                    if current[0] == '.':
+                        var_type = '.'
+                        current = current[1:]
+                    else:
+                        var_type = ':'
+                    opts = current.split(':')
+                    if len(opts) > 1:
+                        current = opts[0]
+                        self.reqs[current] = opts[1]
+                routelist.append(dict(type=var_type, name=current))
+                if char in self.done_chars:
+                    routelist.append(char)
+                done_on = var_type = current = ''
+            else:
+                current += char
+        if collecting:
+            routelist.append(dict(type=var_type, name=current))
+        elif current:
+            routelist.append(current)
+        return routelist
+
+    def _minkeys(self, routelist):
+        """Utility function to walk the route backwards
+
+        Will also determine the minimum keys we can handle to generate
+        a working route.
+
+        routelist is a list of the '/' split route path
+        defaults is a dict of all the defaults provided for the route
+
+        """
+        minkeys = []
+        backcheck = routelist[:]
+
+        # If we don't honor minimization, we need all the keys in the
+        # route path
+        if not self.minimization:
+            for part in backcheck:
+                if isinstance(part, dict):
+                    minkeys.append(part['name'])
+            return (frozenset(minkeys), backcheck)
+
+        gaps = False
+        backcheck.reverse()
+        for part in backcheck:
+            if not isinstance(part, dict) and part not in self.done_chars:
+                gaps = True
+                continue
+            elif not isinstance(part, dict):
+                continue
+            key = part['name']
+            if key in self.defaults and not gaps:
+                continue
+            minkeys.append(key)
+            gaps = True
+        return (frozenset(minkeys), backcheck)
+
+    def _defaults(self, routekeys, reserved_keys, kargs):
+        """Creates default set with values stringified
+
+        Put together our list of defaults, stringify non-None values
+        and add in our action/id default if they use it and didn't
+        specify it.
+
+        defaultkeys is a list of the currently assumed default keys
+        routekeys is a list of the keys found in the route path
+        reserved_keys is a list of keys that are not
+
+        """
+        defaults = {}
+        # Add in a controller/action default if they don't exist
+        if 'controller' not in routekeys and 'controller' not in kargs \
+           and not self.explicit:
+            kargs['controller'] = 'content'
+        if 'action' not in routekeys and 'action' not in kargs \
+           and not self.explicit:
+            kargs['action'] = 'index'
+        defaultkeys = frozenset(key for key in kargs.keys()
+                                if key not in reserved_keys)
+        for key in defaultkeys:
+            if kargs[key] is not None:
+                defaults[key] = self.make_unicode(kargs[key])
+            else:
+                defaults[key] = None
+        if 'action' in routekeys and 'action' not in defaults \
+           and not self.explicit:
+            defaults['action'] = 'index'
+        if 'id' in routekeys and 'id' not in defaults \
+           and not self.explicit:
+            defaults['id'] = None
+        newdefaultkeys = frozenset(key for key in defaults.keys()
+                                   if key not in reserved_keys)
+
+        return (defaults, newdefaultkeys)
+
+    def makeregexp(self, clist, include_names=True):
+        """Create a regular expression for matching purposes
+
+        Note: This MUST be called before match can function properly.
+
+        clist should be a list of valid controller strings that can be
+        matched, for this reason makeregexp should be called by the web
+        framework after it knows all available controllers that can be
+        utilized.
+
+        include_names indicates whether this should be a match regexp
+        assigned to itself using regexp grouping names, or if names
+        should be excluded for use in a single larger regexp to
+        determine if any routes match
+
+        """
+        if self.minimization:
+            reg = self.buildnextreg(self.routelist, clist, include_names)[0]
+            if not reg:
+                reg = '/'
+            reg = reg + '/?' + '$'
+
+            if not reg.startswith('/'):
+                reg = '/' + reg
+        else:
+            reg = self.buildfullreg(clist, include_names)
+
+        reg = '^' + reg
+
+        if not include_names:
+            return reg
+
+        self.regexp = reg
+        self.regmatch = re.compile(reg)
+
+    def buildfullreg(self, clist, include_names=True):
+        """Build the regexp by iterating through the routelist and
+        replacing dicts with the appropriate regexp match"""
+        regparts = []
+        for part in self.routelist:
+            if isinstance(part, dict):
+                var = part['name']
+                if var == 'controller':
+                    partmatch = '|'.join(map(re.escape, clist))
+                elif part['type'] == ':':
+                    partmatch = self.reqs.get(var) or '[^/]+?'
+                elif part['type'] == '.':
+                    partmatch = self.reqs.get(var) or '[^/.]+?'
+                else:
+                    partmatch = self.reqs.get(var) or '.+?'
+                if include_names:
+                    regpart = '(?P<%s>%s)' % (var, partmatch)
+                else:
+                    regpart = '(?:%s)' % partmatch
+                if part['type'] == '.':
+                    regparts.append('(?:\.%s)??' % regpart)
+                else:
+                    regparts.append(regpart)
+            else:
+                regparts.append(re.escape(part))
+        regexp = ''.join(regparts) + '$'
+        return regexp
+
+    def buildnextreg(self, path, clist, include_names=True):
+        """Recursively build our regexp given a path, and a controller
+        list.
+
+        Returns the regular expression string, and two booleans that
+        can be ignored as they're only used internally by buildnextreg.
+
+        """
+        if path:
+            part = path[0]
+        else:
+            part = ''
+        reg = ''
+
+        # noreqs will remember whether the remainder has either a string
+        # match, or a non-defaulted regexp match on a key, allblank remembers
+        # if the rest could possible be completely empty
+        (rest, noreqs, allblank) = ('', True, True)
+        if len(path[1:]) > 0:
+            self.prior = part
+            (rest, noreqs, allblank) = self.buildnextreg(path[1:], clist,
+                                                         include_names)
+
+        if isinstance(part, dict) and part['type'] in (':', '.'):
+            var = part['name']
+            typ = part['type']
+            partreg = ''
+
+            # First we plug in the proper part matcher
+            if var in self.reqs:
+                if include_names:
+                    partreg = '(?P<%s>%s)' % (var, self.reqs[var])
+                else:
+                    partreg = '(?:%s)' % self.reqs[var]
+                if typ == '.':
+                    partreg = '(?:\.%s)??' % partreg
+            elif var == 'controller':
+                if include_names:
+                    partreg = '(?P<%s>%s)' % (var, '|'.join(map(re.escape,
+                                                                clist)))
+                else:
+                    partreg = '(?:%s)' % '|'.join(map(re.escape, clist))
+            elif self.prior in ['/', '#']:
+                if include_names:
+                    partreg = '(?P<' + var + '>[^' + self.prior + ']+?)'
+                else:
+                    partreg = '(?:[^' + self.prior + ']+?)'
+            else:
+                if not rest:
+                    if typ == '.':
+                        exclude_chars = '/.'
+                    else:
+                        exclude_chars = '/'
+                    if include_names:
+                        partreg = '(?P<%s>[^%s]+?)' % (var, exclude_chars)
+                    else:
+                        partreg = '(?:[^%s]+?)' % exclude_chars
+                    if typ == '.':
+                        partreg = '(?:\.%s)??' % partreg
+                else:
+                    end = ''.join(self.done_chars)
+                    rem = rest
+                    if rem[0] == '\\' and len(rem) > 1:
+                        rem = rem[1]
+                    elif rem.startswith('(\\') and len(rem) > 2:
+                        rem = rem[2]
+                    else:
+                        rem = end
+                    rem = frozenset(rem) | frozenset(['/'])
+                    if include_names:
+                        partreg = '(?P<%s>[^%s]+?)' % (var, ''.join(rem))
+                    else:
+                        partreg = '(?:[^%s]+?)' % ''.join(rem)
+
+            if var in self.reqs:
+                noreqs = False
+            if var not in self.defaults:
+                allblank = False
+                noreqs = False
+
+            # Now we determine if its optional, or required. This changes
+            # depending on what is in the rest of the match. If noreqs is
+            # true, then its possible the entire thing is optional as there's
+            # no reqs or string matches.
+            if noreqs:
+                # The rest is optional, but now we have an optional with a
+                # regexp. Wrap to ensure that if we match anything, we match
+                # our regexp first. It's still possible we could be completely
+                # blank as we have a default
+                if var in self.reqs and var in self.defaults:
+                    reg = '(?:' + partreg + rest + ')?'
+
+                # Or we have a regexp match with no default, so now being
+                # completely blank form here on out isn't possible
+                elif var in self.reqs:
+                    allblank = False
+                    reg = partreg + rest
+
+                # If the character before this is a special char, it has to be
+                # followed by this
+                elif var in self.defaults and self.prior in (',', ';', '.'):
+                    reg = partreg + rest
+
+                # Or we have a default with no regexp, don't touch the allblank
+                elif var in self.defaults:
+                    reg = partreg + '?' + rest
+
+                # Or we have a key with no default, and no reqs. Not possible
+                # to be all blank from here
+                else:
+                    allblank = False
+                    reg = partreg + rest
+            # In this case, we have something dangling that might need to be
+            # matched
+            else:
+                # If they can all be blank, and we have a default here, we know
+                # its safe to make everything from here optional. Since
+                # something else in the chain does have req's though, we have
+                # to make the partreg here required to continue matching
+                if allblank and var in self.defaults:
+                    reg = '(?:' + partreg + rest + ')?'
+
+                # Same as before, but they can't all be blank, so we have to
+                # require it all to ensure our matches line up right
+                else:
+                    reg = partreg + rest
+        elif isinstance(part, dict) and part['type'] == '*':
+            var = part['name']
+            if noreqs:
+                if include_names:
+                    reg = '(?P<%s>.*)' % var + rest
+                else:
+                    reg = '(?:.*)' + rest
+                if var not in self.defaults:
+                    allblank = False
+                    noreqs = False
+            else:
+                if allblank and var in self.defaults:
+                    if include_names:
+                        reg = '(?P<%s>.*)' % var + rest
+                    else:
+                        reg = '(?:.*)' + rest
+                elif var in self.defaults:
+                    if include_names:
+                        reg = '(?P<%s>.*)' % var + rest
+                    else:
+                        reg = '(?:.*)' + rest
+                else:
+                    if include_names:
+                        reg = '(?P<%s>.*)' % var + rest
+                    else:
+                        reg = '(?:.*)' + rest
+                    allblank = False
+                    noreqs = False
+        elif part and part[-1] in self.done_chars:
+            if allblank:
+                reg = re.escape(part[:-1]) + '(?:' + re.escape(part[-1]) + rest
+                reg += ')?'
+            else:
+                allblank = False
+                reg = re.escape(part) + rest
+
+        # We have a normal string here, this is a req, and it prevents us from
+        # being all blank
+        else:
+            noreqs = False
+            allblank = False
+            reg = re.escape(part) + rest
+
+        return (reg, noreqs, allblank)
+
+    def match(self, url, environ=None, sub_domains=False,
+              sub_domains_ignore=None, domain_match=''):
+        """Match a url to our regexp.
+
+        While the regexp might match, this operation isn't
+        guaranteed as there's other factors that can cause a match to
+        fail even though the regexp succeeds (Default that was relied
+        on wasn't given, requirement regexp doesn't pass, etc.).
+
+        Therefore the calling function shouldn't assume this will
+        return a valid dict, the other possible return is False if a
+        match doesn't work out.
+
+        """
+        # Static routes don't match, they generate only
+        if self.static:
+            return False
+
+        match = self.regmatch.match(url)
+
+        if not match:
+            return False
+
+        sub_domain = None
+
+        if sub_domains and environ and 'HTTP_HOST' in environ:
+            host = environ['HTTP_HOST'].split(':')[0]
+            sub_match = re.compile('^(.+?)\.%s$' % domain_match)
+            subdomain = re.sub(sub_match, r'\1', host)
+            if subdomain not in sub_domains_ignore and host != subdomain:
+                sub_domain = subdomain
+
+        if self.conditions:
+            if 'method' in self.conditions and environ and \
+                    environ['REQUEST_METHOD'] not in self.conditions['method']:
+                return False
+
+            # Check sub-domains?
+            use_sd = self.conditions.get('sub_domain')
+            if use_sd and not sub_domain:
+                return False
+            elif not use_sd and 'sub_domain' in self.conditions and sub_domain:
+                return False
+            if isinstance(use_sd, list) and sub_domain not in use_sd:
+                return False
+
+        matchdict = match.groupdict()
+        result = {}
+        extras = self._default_keys - frozenset(matchdict.keys())
+        for key, val in six.iteritems(matchdict):
+            if key != 'path_info' and self.encoding:
+                # change back into python unicode objects from the URL
+                # representation
+                try:
+                    val = as_unicode(val, self.encoding, self.decode_errors)
+                except UnicodeDecodeError:
+                    return False
+
+            if not val and key in self.defaults and self.defaults[key]:
+                result[key] = self.defaults[key]
+            else:
+                result[key] = val
+        for key in extras:
+            result[key] = self.defaults[key]
+
+        # Add the sub-domain if there is one
+        if sub_domains:
+            result['sub_domain'] = sub_domain
+
+        # If there's a function, call it with environ and expire if it
+        # returns False
+        if self.conditions and 'function' in self.conditions and \
+                not self.conditions['function'](environ, result):
+            return False
+
+        return result
+
+    def generate_non_minimized(self, kargs):
+        """Generate a non-minimal version of the URL"""
+        # Iterate through the keys that are defaults, and NOT in the route
+        # path. If its not in kargs, or doesn't match, or is None, this
+        # route won't work
+        for k in self.maxkeys - self.minkeys:
+            if k not in kargs:
+                return False
+            elif self.make_unicode(kargs[k]) != \
+                    self.make_unicode(self.defaults[k]):
+                return False
+
+        # Ensure that all the args in the route path are present and not None
+        for arg in self.minkeys:
+            if arg not in kargs or kargs[arg] is None:
+                if arg in self.dotkeys:
+                    kargs[arg] = ''
+                else:
+                    return False
+
+        # Encode all the argument that the regpath can use
+        for k in kargs:
+            if k in self.maxkeys:
+                if k in self.dotkeys:
+                    if kargs[k]:
+                        kargs[k] = url_quote('.' + as_unicode(kargs[k],
+                                             self.encoding), self.encoding)
+                else:
+                    kargs[k] = url_quote(as_unicode(kargs[k], self.encoding),
+                                         self.encoding)
+
+        return self.regpath % kargs
+
+    def generate_minimized(self, kargs):
+        """Generate a minimized version of the URL"""
+        routelist = self.routebackwards
+        urllist = []
+        gaps = False
+        for part in routelist:
+            if isinstance(part, dict) and part['type'] in (':', '.'):
+                arg = part['name']
+
+                # For efficiency, check these just once
+                has_arg = arg in kargs
+                has_default = arg in self.defaults
+
+                # Determine if we can leave this part off
+                # First check if the default exists and wasn't provided in the
+                # call (also no gaps)
+                if has_default and not has_arg and not gaps:
+                    continue
+
+                # Now check to see if there's a default and it matches the
+                # incoming call arg
+                if (has_default and has_arg) and \
+                    self.make_unicode(kargs[arg]) == \
+                        self.make_unicode(self.defaults[arg]) and not gaps:
+                    continue
+
+                # We need to pull the value to append, if the arg is None and
+                # we have a default, use that
+                if has_arg and kargs[arg] is None and has_default and not gaps:
+                    continue
+
+                # Otherwise if we do have an arg, use that
+                elif has_arg:
+                    val = kargs[arg]
+
+                elif has_default and self.defaults[arg] is not None:
+                    val = self.defaults[arg]
+                # Optional format parameter?
+                elif part['type'] == '.':
+                    continue
+                # No arg at all? This won't work
+                else:
+                    return False
+
+                val = as_unicode(val, self.encoding)
+                urllist.append(url_quote(val, self.encoding))
+                if part['type'] == '.':
+                    urllist.append('.')
+
+                if has_arg:
+                    del kargs[arg]
+                gaps = True
+            elif isinstance(part, dict) and part['type'] == '*':
+                arg = part['name']
+                kar = kargs.get(arg)
+                if kar is not None:
+                    urllist.append(url_quote(kar, self.encoding))
+                    gaps = True
+            elif part and part[-1] in self.done_chars:
+                if not gaps and part in self.done_chars:
+                    continue
+                elif not gaps:
+                    urllist.append(part[:-1])
+                    gaps = True
+                else:
+                    gaps = True
+                    urllist.append(part)
+            else:
+                gaps = True
+                urllist.append(part)
+        urllist.reverse()
+        url = ''.join(urllist)
+        return url
+
+    def generate(self, _ignore_req_list=False, _append_slash=False, **kargs):
+        """Generate a URL from ourself given a set of keyword arguments
+
+        Toss an exception if this
+        set of keywords would cause a gap in the url.
+
+        """
+        # Verify that our args pass any regexp requirements
+        if not _ignore_req_list:
+            for key in self.reqs.keys():
+                val = kargs.get(key)
+                if val and not self.req_regs[key].match(self.make_unicode(val)):
+                    return False
+
+        # Verify that if we have a method arg, its in the method accept list.
+        # Also, method will be changed to _method for route generation
+        meth = as_unicode(kargs.get('method'), self.encoding)
+        if meth:
+            if self.conditions and 'method' in self.conditions \
+                    and meth.upper() not in self.conditions['method']:
+                return False
+            kargs.pop('method')
+
+        if self.minimization:
+            url = self.generate_minimized(kargs)
+        else:
+            url = self.generate_non_minimized(kargs)
+
+        if url is False:
+            return url
+
+        if not url.startswith('/') and not self.static:
+            url = '/' + url
+        extras = frozenset(kargs.keys()) - self.maxkeys
+        if extras:
+            if _append_slash and not url.endswith('/'):
+                url += '/'
+            fragments = []
+            # don't assume the 'extras' set preserves order: iterate
+            # through the ordered kargs instead
+            for key in kargs:
+                if key not in extras:
+                    continue
+                if key == 'action' or key == 'controller':
+                    continue
+                val = kargs[key]
+                if isinstance(val, (tuple, list)):
+                    for value in val:
+                        value = as_unicode(value, self.encoding)
+                        fragments.append((key, _str_encode(value,
+                                                           self.encoding)))
+                else:
+                    val = as_unicode(val, self.encoding)
+                    fragments.append((key, _str_encode(val, self.encoding)))
+            if fragments:
+                url += '?'
+                url += urlparse.urlencode(fragments)
+        elif _append_slash and not url.endswith('/'):
+            url += '/'
+        return url