Created
April 13, 2013 03:52
-
-
Save shiweifu/5376846 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
#coding: utf-8 | |
import re | |
class MappingFileError(Exception): pass | |
class SimpleParser(object): | |
"""Callable to turn path expressions into regexes with named groups. | |
For instance "/hello/{name}" becomes r"^\/hello\/(?P<name>[^\^.]+)$" | |
For /hello/{name:pattern} | |
you get whatever is in self.patterns['pattern'] instead of "[^\^.]+" | |
Optional portions of path expression can be expressed [like this] | |
/hello/{name}[/] (can have trailing slash or not) | |
Example: | |
/blog/archive/{year:digits}/{month:digits}[/[{article}[/]]] | |
This would catch any of these: | |
/blog/archive/2005/09 | |
/blog/archive/2005/09/ | |
/blog/archive/2005/09/1 | |
/blog/archive/2005/09/1/ | |
(I am not suggesting that this example is a best practice. | |
I would probably have a separate mapping for listing the month | |
and retrieving an individual entry. It depends, though.) | |
""" | |
start, end = '{}' | |
ostart, oend = '[]' | |
_patterns = {'word': r'\w+', | |
'alpha': r'[a-zA-Z]+', | |
'digits': r'\d+', | |
'number': r'\d*.?\d+', | |
'chunk': r'[^/^.]+', | |
'segment': r'[^/]+', | |
'any': r'.+'} | |
default_pattern = 'chunk' | |
def __init__(self, patterns=None): | |
"""Initialize with character class mappings.""" | |
self.patterns = dict(self._patterns) | |
if patterns is not None: | |
self.patterns.update(patterns) | |
def lookup(self, name): | |
"""Return the replacement for the name found.""" | |
if ':' in name: | |
name, pattern = name.split(':') | |
pattern = self.patterns[pattern] | |
else: | |
pattern = self.patterns[self.default_pattern] | |
if name == '': | |
name = '__pos%s' % self._pos | |
self._pos += 1 | |
return '(?P<%s>%s)' % (name, pattern) | |
def lastly(self, regex): | |
"""Process the result of __call__ right before it returns. | |
Adds the ^ and the $ to the beginning and the end, respectively. | |
""" | |
return "^%s$" % regex | |
def openended(self, regex): | |
"""Process the result of __call__ right before it returns. | |
Adds the ^ to the beginning but no $ to the end. | |
Called as a special alternative to lastly. | |
""" | |
return "^%s" % regex | |
def outermost_optionals_split(self, text): | |
"""Split out optional portions by outermost matching delims.""" | |
parts = [] | |
buffer = "" | |
starts = ends = 0 | |
for c in text: | |
if c == self.ostart: | |
if starts == 0: | |
parts.append(buffer) | |
buffer = "" | |
else: | |
buffer += c | |
starts +=1 | |
elif c == self.oend: | |
ends +=1 | |
if starts == ends: | |
parts.append(buffer) | |
buffer = "" | |
starts = ends = 0 | |
else: | |
buffer += c | |
else: | |
buffer += c | |
if not starts == ends == 0: | |
raise PathExpressionParserError( | |
"Mismatch of optional portion delimiters." | |
) | |
parts.append(buffer) | |
return parts | |
def parse(self, text): | |
"""Turn a path expression into regex.""" | |
if self.ostart in text: | |
parts = self.outermost_optionals_split(text) | |
parts = map(self.parse, parts) | |
parts[1::2] = ["(%s)?" % p for p in parts[1::2]] | |
else: | |
parts = [part.split(self.end) | |
for part in text.split(self.start)] | |
parts = [y for x in parts for y in x] | |
parts[::2] = map(re.escape, parts[::2]) | |
parts[1::2] = map(self.lookup, parts[1::2]) | |
return ''.join(parts) | |
def __call__(self, url_pattern): | |
"""Turn a path expression into regex via parse and lastly.""" | |
self._pos = 0 | |
if url_pattern.endswith('|'): | |
return self.openended(self.parse(url_pattern[:-1])) | |
else: | |
return self.lastly(self.parse(url_pattern)) | |
class Application(object): | |
route_list = [] | |
def __init__(self): | |
super(Application, self).__init__() | |
self.parser = SimpleParser() | |
def route(self, path): | |
def wrapper(callback): | |
# print path | |
regex = self.parser(path) | |
print regex | |
regex = re.compile(regex) | |
self.route_list.append((regex, callback)) | |
return wrapper | |
def __call__(self, url): | |
for l in self.route_list: | |
m = l[0].search(url) | |
if m: | |
d = m.groupdict() | |
return l[1](**d) | |
return "404" | |
app = Application() | |
@app.route('/view/{page}[/{revision}]') | |
def index(page, revision=None): | |
print page | |
return "hello world" | |
print app("/view/abc") | |
def main(): | |
parser = SimpleParser() | |
regex = parser("/blog/archive/{year:digits}/{month:digits}[/[{article}[/]]]") | |
compiled_regex = re.compile(regex) | |
m = compiled_regex.match("/blog/archive/abcd/aaa/abcdefg/") | |
# print m.groups() | |
# mappings = [] | |
# mappings.append((compiled_regex, {})) | |
# print mappings | |
if __name__ == '__main__': | |
main() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment