diff options
author | Mike Burrows <mjb@asplake.co.uk> | 2010-02-10 16:33:59 +0000 |
---|---|---|
committer | Mike Burrows <mjb@asplake.co.uk> | 2010-02-10 16:33:59 +0000 |
commit | 8a32d35f8b05a5e5845083ade999383f171fa4ca (patch) | |
tree | bf36ddb7914f76ba116175d377b7c9f3cbf36870 | |
parent | 5251795380fe9695906d78bba6226b7b599cf00d (diff) | |
download | routes-8a32d35f8b05a5e5845083ade999383f171fa4ca.tar.gz |
{.format} path components
--HG--
branch : trunk
-rw-r--r-- | routes/mapper.py | 4 | ||||
-rw-r--r-- | routes/route.py | 63 | ||||
-rw-r--r-- | tests/test_functional/test_generation.py | 9 | ||||
-rw-r--r-- | tests/test_functional/test_recognition.py | 10 |
4 files changed, 69 insertions, 17 deletions
diff --git a/routes/mapper.py b/routes/mapper.py index 7aca987..451276f 100644 --- a/routes/mapper.py +++ b/routes/mapper.py @@ -750,7 +750,7 @@ class Mapper(SubMapperParent): cacheset = True newlist = [] for route in keylist: - if len(route.minkeys-keys) == 0: + if len(route.minkeys - route.dotkeys - keys) == 0: newlist.append(route) keylist = newlist @@ -793,7 +793,7 @@ class Mapper(SubMapperParent): keylist.sort(keysort) 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: diff --git a/routes/route.py b/routes/route.py index 85fd601..552f6b8 100644 --- a/routes/route.py +++ b/routes/route.py @@ -87,9 +87,12 @@ class Route(object): 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 \ + 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() @@ -175,7 +178,11 @@ class Route(object): if len(opts) > 1: current = opts[0] self.reqs[current] = opts[1] - var_type = ':' + if current[0] == '.': + var_type = '.' + current = current[1:] + else: + var_type = ':' routelist.append(dict(type=var_type, name=current)) if char in self.done_chars: routelist.append(char) @@ -308,12 +315,18 @@ class Route(object): 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: - regparts.append('(?P<%s>%s)' % (var, partmatch)) + regpart = '(?P<%s>%s)' % (var, partmatch) else: - regparts.append('(?:%s)' % partmatch) + regpart = '(?:%s)' % partmatch + if part['type'] == '.': + regparts.append('(?:\.%s)??' % regpart) + else: + regparts.append(regpart) else: regparts.append(re.escape(part)) regexp = ''.join(regparts) + '$' @@ -341,8 +354,9 @@ class Route(object): self.prior = part (rest, noreqs, allblank) = self.buildnextreg(path[1:], clist, include_names) - if isinstance(part, dict) and part['type'] == ':': + if isinstance(part, dict) and part['type'] in (':', '.'): var = part['name'] + typ = part['type'] partreg = '' # First we plug in the proper part matcher @@ -363,10 +377,16 @@ class Route(object): partreg = '(?:[^' + self.prior + ']+?)' else: if not rest: + if typ == '.': + exclude_chars = '/.' + else: + exclude_chars = '/' if include_names: - partreg = '(?P<%s>[^%s]+?)' % (var, '/') + partreg = '(?P<%s>[^%s]+?)' % (var, exclude_chars) else: - partreg = '(?:[^%s]+?)' % '/' + partreg = '(?:[^%s]+?)' % exclude_chars + if typ == '.': + partreg = '(?:\.%s)??' % partreg else: end = ''.join(self.done_chars) rem = rest @@ -568,16 +588,24 @@ class Route(object): 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: - return False - + 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: - kargs[k] = url_quote(kargs[k], self.encoding) + if k in self.dotkeys: + if kargs[k]: + kargs[k] = url_quote('.' + kargs[k], self.encoding) + else: + kargs[k] = url_quote(kargs[k], self.encoding) + return self.regpath % kargs def generate_minimized(self, kargs): @@ -586,7 +614,7 @@ class Route(object): urllist = [] gaps = False for part in routelist: - if isinstance(part, dict) and part['type'] == ':': + if isinstance(part, dict) and part['type'] in (':', '.'): arg = part['name'] # For efficiency, check these just once @@ -616,12 +644,17 @@ class Route(object): 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 - + urllist.append(url_quote(val, self.encoding)) + if part['type'] == '.': + urllist.append('.') + if has_arg: del kargs[arg] gaps = True diff --git a/tests/test_functional/test_generation.py b/tests/test_functional/test_generation.py index 7c2d2ba..9bff54a 100644 --- a/tests/test_functional/test_generation.py +++ b/tests/test_functional/test_generation.py @@ -626,6 +626,15 @@ class TestGeneration(unittest.TestCase): eq_('/2007/test.xml,ja', m.generate(year=2007, slug='test', format='xml', locale='ja')) eq_(None, m.generate(year=2007, format='html')) + def test_dot_format_args(self): + for minimization in [False, True]: + m = Mapper(explicit=True) + m.minimization=minimization + m.connect('/songs/{title}{.format}') + + eq_('/songs/my-way', m.generate(title='my-way')) + eq_('/songs/my-way.mp3', m.generate(title='my-way', format='mp3')) + if __name__ == '__main__': unittest.main() else: diff --git a/tests/test_functional/test_recognition.py b/tests/test_functional/test_recognition.py index c606beb..9dcc861 100644 --- a/tests/test_functional/test_recognition.py +++ b/tests/test_functional/test_recognition.py @@ -929,6 +929,16 @@ class TestRecognition(unittest.TestCase): m.match('') assert_raises(RoutesException, call_func) + def test_dot_format_args(self): + for minimization in [False, True]: + m = Mapper(explicit=True) + m.minimization=minimization + m.connect('/songs/{title}{.format}') + + eq_({'title': 'my-way', 'format': None}, m.match('/songs/my-way')) + eq_({'title': 'my-way', 'format': 'mp3'}, m.match('/songs/my-way.mp3')) + + if __name__ == '__main__': unittest.main() else: |