Last active
August 29, 2015 14:25
-
-
Save Ceasar/c545b5a97deecd1d065b to your computer and use it in GitHub Desktop.
Implements a Level 3 expand function for RFC 6570 URI Templates.
This file contains 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
from urllib import quote | |
_OP_LEVEL_2 = {'+', '#'} | |
_OP_LEVEL_3 = {'.', '/', ';', '?', '&'} | |
OPERATORS = _OP_LEVEL_2 | _OP_LEVEL_3 | |
_RESERVED = ':/?#[]@' + '!$&\'()*+,;=' | |
class Token(object): | |
def expand(self, **kwargs): | |
raise NotImplementedError() | |
class Literal(Token): | |
def __init__(self, val): | |
self.val = val | |
def __str__(self): | |
return self.val | |
def expand(self, **kwargs): | |
return self.val | |
_START = { | |
'': '', | |
'+': '', | |
'#': '#', | |
'.': '.', | |
'/': '/', | |
';': ';', | |
'?': '?', | |
'&': '&', | |
} | |
_SEP = { | |
'': ',', | |
'+': ',', | |
'#': ',', | |
'.': '.', | |
'/': '/', | |
';': ';', | |
'?': '&', | |
'&': '&', | |
} | |
class Expression(Token): | |
""" | |
>>> token = Expression(['var']) | |
>>> str(token) | |
'{var}' | |
>>> token.expand(var='value') | |
'value' | |
>>> hello = 'Hello World!' | |
>>> Expression(['hello']).expand(hello=hello) | |
'Hello%20World%21' | |
>>> Expression(['x', 'hello', 'y']).expand(x='1024', y='768', hello=hello) | |
'1024,Hello%20World%21,768' | |
""" | |
def __init__(self, variables, operator=''): | |
self.variables = variables | |
assert operator == '' or operator in OPERATORS, operator | |
self.operator = operator | |
@classmethod | |
def from_string(cls, string): | |
if string.startswith('{') and string.endswith('}'): | |
expression = string[1:-1] | |
if expression[0] in OPERATORS: | |
return cls(expression[1:].split(','), operator=expression[0]) | |
else: | |
return cls(expression.split(',')) | |
else: | |
raise ValueError('invalid expression') | |
def __str__(self): | |
return return '{{{}{}}}'.format(self.operator, ','.join(self.variables)) | |
def expand(self, **kwargs): | |
""" | |
>>> var = 'value' | |
>>> hello = 'Hello World!' | |
>>> empty = '' | |
>>> path = '/foo/bar' | |
>>> x = '1024' | |
>>> y = '768' | |
String expansion with multiple variables: | |
>>> Expression.from_string('{x,y}').expand(x=x, y=y) | |
'1024,768' | |
>>> Expression.from_string('{x,hello,y}').expand(x=x, hello=hello, y=y) | |
'1024,Hello%20World%21,768' | |
Reserved expansion with value modifiers: | |
>>> Expression.from_string('{+x,hello,y}').expand(x=x, hello=hello, y=y) | |
'1024,Hello%20World!,768' | |
>>> Expression.from_string('{+path,x}').expand(path=path, x=x) | |
'/foo/bar,1024' | |
Fragment expansion with multiple variables: | |
>>> Expression.from_string('{#x,hello,y}').expand(x=x, hello=hello, y=y) | |
'#1024,Hello%20World!,768' | |
Label expansion, dot-prefixed: | |
>>> Expression.from_string('{.var}').expand(var=var) | |
'.value' | |
>>> Expression.from_string('{.x,y}').expand(x=x, y=y) | |
'.1024.768' | |
Path segments, slash-prefixed: | |
>>> Expression.from_string('{/var}').expand(var=var) | |
'/value' | |
>>> Expression.from_string('{/var,x}').expand(var=var, x=x) | |
'/value/1024' | |
Path-style parameters, semicolon-prefixed: | |
>>> Expression.from_string('{;x,y}').expand(x=x, y=y) | |
';x=1024;y=768' | |
>>> Expression.from_string('{;x,y,empty}').expand(x=x, y=y, empty=empty) | |
';x=1024;y=768;empty' | |
Form-style query, ampersand-separated: | |
>>> Expression.from_string('{?x,y}').expand(x=x, y=y) | |
'?x=1024&y=768' | |
>>> Expression.from_string('{?x,y,empty}').expand(x=x, y=y, empty=empty) | |
'?x=1024&y=768&empty=' | |
Form-style query continuation: | |
>>> Expression.from_string('{&x,y,empty}').expand(x=x, y=y, empty=empty) | |
'&x=1024&y=768&empty=' | |
""" | |
safe = _RESERVED if self.operator in _OP_LEVEL_2 else '' | |
varlist = ([ | |
'{}={}'.format(quote(var), quote(kwargs[var])) | |
if kwargs[var] else var | |
for var in self.variables if var in kwargs | |
] if self.operator == ';' else [ | |
'{}={}'.format(quote(var), quote(kwargs[var])) | |
for var in self.variables if var in kwargs | |
] if self.operator in {'?', '&'} else [ | |
quote(kwargs[var], safe) | |
for var in self.variables if var in kwargs | |
]) | |
start, sep = _START[self.operator], _SEP[self.operator] | |
return start + sep.join(varlist) if varlist else '' | |
class URITemplate(object): | |
""" | |
Level 3 URI Template. | |
>>> template = URITemplate([ | |
... Literal('http://www.example.com/foo'), | |
... Expression(['query', 'number'], operator='?') | |
... ]) | |
>>> str(template) | |
'http://www.example.com/foo{?query,number}' | |
>>> template.expand(query='mycelium', number='100') | |
'http://www.example.com/foo?query=mycelium&number=100' | |
>>> template.expand(number='100') | |
'http://www.example.com/foo?number=100' | |
>>> template.expand() | |
'http://www.example.com/foo' | |
:param tokens: | |
A list of :class:`Token` objects. | |
""" | |
def __init__(self, tokens): | |
self.tokens = tokens | |
def __str__(self): | |
return ''.join([str(token) for token in self.tokens]) | |
def expand(self, **kwargs): | |
return ''.join([token.expand(**kwargs) for token in self.tokens]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment