summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Burrows <mjb@asplake.co.uk>2010-02-10 16:33:59 +0000
committerMike Burrows <mjb@asplake.co.uk>2010-02-10 16:33:59 +0000
commit8a32d35f8b05a5e5845083ade999383f171fa4ca (patch)
treebf36ddb7914f76ba116175d377b7c9f3cbf36870
parent5251795380fe9695906d78bba6226b7b599cf00d (diff)
downloadroutes-8a32d35f8b05a5e5845083ade999383f171fa4ca.tar.gz
{.format} path components
--HG-- branch : trunk
-rw-r--r--routes/mapper.py4
-rw-r--r--routes/route.py63
-rw-r--r--tests/test_functional/test_generation.py9
-rw-r--r--tests/test_functional/test_recognition.py10
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: