Skip to content

Commit

Permalink
Merge pull request #3 from kyleconroy/template
Browse files Browse the repository at this point in the history
Add RFC6570 Uri Template Support
  • Loading branch information
mwhooker committed Apr 10, 2012
2 parents 06334d0 + b1eb9a2 commit 9dd9704
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 0 deletions.
203 changes: 203 additions & 0 deletions tests/test_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
# encoding: utf-8
from uricore import URI, IRI
from nose.tools import eq_
from uricore.template import uri_template
from collections import OrderedDict

# http://tools.ietf.org/html/rfc6570#section-3.2
params = {
'count': ("one", "two", "three"),
'dom': ("example", "com"),
'dub': "me/too",
'hello': "Hello World!",
'half': "50%",
'var': "value",
'who': "fred",
'base': "http://example.com/home/",
'path': "/foo/bar",
'list': ("red", "green", "blue"),
'year': ("1965", "2000", "2012"),
'semi': ';',
'v': 6,
'x': 1024,
'y': 768,
'empty': "",
'empty_keys': [],
'undef': None,
'list': ["red", "green", "blue"],
'keys': OrderedDict([('semi', ";"), ('dot', "."), ('comma', ",")]),
}

def check_template(template, expansion):
eq_(uri_template(template, **params), expansion)


def test_composite_values():
yield check_template, "find{?year*}", "find?year=1965&year=2000&year=2012"
yield check_template, "www{.dom*}", "www.example.com"


def test_form_continuation_expansion():
yield check_template, "{&who}", "&who=fred"
yield check_template, "{&half}", "&half=50%25"
yield check_template, "?fixed=yes{&x}", "?fixed=yes&x=1024"
yield check_template, "{&x,y,empty}", "&x=1024&y=768&empty="
yield check_template, "{&x,y,undef}", "&x=1024&y=768"
yield check_template, "{&var:3}", "&var=val"
yield check_template, "{&list}", "&list=red,green,blue"
yield check_template, "{&list*}", "&list=red&list=green&list=blue"
yield check_template, "{&keys}", "&keys=semi,%3B,dot,.,comma,%2C"
yield check_template, "{&keys*}", "&semi=%3B&dot=.&comma=%2C"


def test_form_style_expansion():
yield check_template, "{?who}", "?who=fred"
yield check_template, "{?half}", "?half=50%25"
yield check_template, "{?x,y}", "?x=1024&y=768"
yield check_template, "{?x,y,empty}", "?x=1024&y=768&empty="
yield check_template, "{?x,y,undef}", "?x=1024&y=768"
yield check_template, "{?var:3}", "?var=val"
yield check_template, "{?list}", "?list=red,green,blue"
yield check_template, "{?list*}", "?list=red&list=green&list=blue"
yield check_template, "{?keys}", "?keys=semi,%3B,dot,.,comma,%2C"
yield check_template, "{?keys*}", "?semi=%3B&dot=.&comma=%2C"


def test_fragment_expansion():
yield check_template, "{#var}", "#value"
yield check_template, "{#hello}", "#Hello%20World!"
yield check_template, "{#half}", "#50%25"
yield check_template, "foo{#empty}", "foo#"
yield check_template, "foo{#undef}", "foo"
yield check_template, "{#x,hello,y}", "#1024,Hello%20World!,768"
yield check_template, "{#path,x}/here", "#/foo/bar,1024/here"
yield check_template, "{#path:6}/here", "#/foo/b/here"
yield check_template, "{#list}", "#red,green,blue"
yield check_template, "{#list*}", "#red,green,blue"
yield check_template, "{#keys}", "#semi,;,dot,.,comma,,"
yield check_template, "{#keys*}", "#semi=;,dot=.,comma=,"


def test_label_expansion():
yield check_template, "{.who}", ".fred"
yield check_template, "{.who,who}", ".fred.fred"
yield check_template, "{.half,who}", ".50%25.fred"
yield check_template, "www{.dom*}", "www.example.com"
yield check_template, "X{.var}", "X.value"
yield check_template, "X{.empty}", "X."
yield check_template, "X{.undef}", "X"
yield check_template, "X{.var:3}", "X.val"
yield check_template, "X{.list}", "X.red,green,blue"
yield check_template, "X{.list*}", "X.red.green.blue"
yield check_template, "X{.keys}", "X.semi,%3B,dot,.,comma,%2C"
yield check_template, "X{.keys*}", "X.semi=%3B.dot=..comma=%2C"
yield check_template, "X{.empty_keys}", "X"
yield check_template, "X{.empty_keys*}", "X"


def test_path_expansion():
yield check_template, "{/who}", "/fred"
yield check_template, "{/who,who}", "/fred/fred"
yield check_template, "{/half,who}", "/50%25/fred"
yield check_template, "{/who,dub}", "/fred/me%2Ftoo"
yield check_template, "{/var}", "/value"
yield check_template, "{/var,empty}", "/value/"
yield check_template, "{/var,undef}", "/value"
yield check_template, "{/var,x}/here", "/value/1024/here"
yield check_template, "{/var:1,var}", "/v/value"
yield check_template, "{/list}", "/red,green,blue"
yield check_template, "{/list*}", "/red/green/blue"
yield check_template, "{/list*,path:4}", "/red/green/blue/%2Ffoo"
yield check_template, "{/keys}", "/semi,%3B,dot,.,comma,%2C"
yield check_template, "{/keys*}", "/semi=%3B/dot=./comma=%2C"


def test_path_style_expansion():
yield check_template, "{;who}", ";who=fred"
yield check_template, "{;half}", ";half=50%25"
yield check_template, "{;empty}", ";empty"
yield check_template, "{;v,empty,who}", ";v=6;empty;who=fred"
yield check_template, "{;v,bar,who}", ";v=6;who=fred"
yield check_template, "{;x,y}", ";x=1024;y=768"
yield check_template, "{;x,y,empty}", ";x=1024;y=768;empty"
yield check_template, "{;x,y,undef}", ";x=1024;y=768"
yield check_template, "{;hello:5}", ";hello=Hello"
yield check_template, "{;list}", ";list=red,green,blue"
yield check_template, "{;list*}", ";list=red;list=green;list=blue"
yield check_template, "{;keys}", ";keys=semi,%3B,dot,.,comma,%2C"
yield check_template, "{;keys*}", ";semi=%3B;dot=.;comma=%2C"


def test_reserved_expansion():
yield check_template, "{+var}", "value"
yield check_template, "{+hello}", "Hello%20World!"
yield check_template, "{+half}", "50%25"
yield check_template, "{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex"
yield check_template, "{+base}index", "http://example.com/home/index"
yield check_template, "O{+empty}X", "OX"
yield check_template, "O{+undef}X", "OX"
yield check_template, "{+path}/here", "/foo/bar/here"
yield check_template, "here?ref={+path}", "here?ref=/foo/bar"
yield check_template, "up{+path}{var}/here", "up/foo/barvalue/here"
yield check_template, "{+x,hello,y}", "1024,Hello%20World!,768"
yield check_template, "{+path,x}/here", "/foo/bar,1024/here"
yield check_template, "{+path:6}/here", "/foo/b/here"
yield check_template, "{+list}", "red,green,blue"
yield check_template, "{+list*}", "red,green,blue"
yield check_template, "{+keys}", "semi,;,dot,.,comma,,"
yield check_template, "{+keys*}", "semi=;,dot=.,comma=,"


def test_simple_string_expansion():
yield check_template, "{var}", "value"
yield check_template, "{hello}", "Hello%20World%21"
yield check_template, "{half}", "50%25"
yield check_template, "O{empty}X", "OX"
yield check_template, "O{undef}X", "OX"
yield check_template, "{x,y}", "1024,768"
yield check_template, "{x,hello,y}", "1024,Hello%20World%21,768"
yield check_template, "?{x,empty}", "?1024,"
yield check_template, "?{x,undef}", "?1024"
yield check_template, "?{undef,y}", "?768"
yield check_template, "{var:3}", "val"
yield check_template, "{var:30}", "value"
yield check_template, "{list}", "red,green,blue"
yield check_template, "{list*}", "red,green,blue"
yield check_template, "{keys}", "semi,%3B,dot,.,comma,%2C"
yield check_template, "{keys*}", "semi=%3B,dot=.,comma=%2C"


def test_test_prefix_values():
yield check_template, "{var}", "value"
yield check_template, "{var:20}", "value"
yield check_template, "{var:3}", "val"
yield check_template, "{semi}", "%3B"
yield check_template, "{semi:2}", "%3B"


def test_variable_expansion():
yield check_template, "{count}", "one,two,three"
yield check_template, "{count*}", "one,two,three"
yield check_template, "{/count}", "/one,two,three"
yield check_template, "{/count*}", "/one/two/three"
yield check_template, "{;count}", ";count=one,two,three"
yield check_template, "{;count*}", ";count=one;count=two;count=three"
yield check_template, "{?count}", "?count=one,two,three"
yield check_template, "{?count*}", "?count=one&count=two&count=three"
yield check_template, "{&count*}", "&count=one&count=two&count=three"


def test_composite_values():
yield check_template, "find{?year*}", "find?year=1965&year=2000&year=2012"
yield check_template, "www{.dom*}", "www.example.com"


def test_uri_template():
eq_(URI("http://example.com/value"),
URI.from_template("http://example.com/{var}", var="value"))


def test_iri_template():
eq_(IRI(u'http://\u2603/value'),
IRI.from_template(u'http://\N{SNOWMAN}/{var}', var='value'))

6 changes: 6 additions & 0 deletions uricore/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import urllib.parse as urlparse

from collections import defaultdict
from template import uri_template

# TODO: import these from httpcore someday
from . import wkz_urls as urls
Expand Down Expand Up @@ -194,6 +195,11 @@ def join(self, other):

return type(self)(unsplit(**vals), query_cls=self.query_cls)

@classmethod
def from_template(cls, template, **kwargs):
return cls(uri_template(template, **kwargs))



class IRI(ResourceIdentifier):

Expand Down
118 changes: 118 additions & 0 deletions uricore/template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import re
from . import wkz_urls as urls


def _format_mapping(operator, item):
try:
k, v, mapped = item
except ValueError:
k, v = item
mapped = False

if operator in ['#', '+']:
# From http://tools.ietf.org/html/rfc6570#section-1.5
safe = ':/?#[]@!$&\'\"()*/+,;='
else:
safe = ''

if isinstance(v, (list, tuple)):
v = ','.join(urls.url_quote(x, safe=safe) for x in v)
else:
v = urls.url_quote(v, safe=safe)

if operator in [';', '?', '&'] or mapped:
if not v:
mid = '' if operator == ';' else '='
else:
mid = '='

return "{}{}{}".format(k, mid, v)
else:
return "{}".format(v)


def _template_joiner(operator):
if operator in ['#', '+', '']:
return ','
elif operator == '?':
return '&'
elif operator == '.':
return'.'
return operator


def _varspec_expansion(operator, varspec, data):
portion = None
explode = False

if ':' in varspec:
varspec, portion = varspec.split(':', 1)
portion = int(portion)

if varspec.endswith('*'):
varspec = varspec[:-1]
explode = True

value = data.get(varspec)

if value == None:
return []

try:
if len(value) == 0 and value != "":
return []
except TypeError:
pass

try:
if explode:
return [(k, v, True) for k,v in value.items()]
else:
parts = []
for k, v in value.items():
parts += [k, v]
return [(varspec, parts)]
except AttributeError:
pass

if isinstance(value, (list, tuple)):
if explode:
return [(varspec, v) for v in value]
else:
return [(varspec, value)]

value = unicode(value)

if portion is not None:
value = value[:portion]

return [(varspec, value)]


def uri_template(template, **kwargs):

def template_expansion(matchobj):
varlist = matchobj.group(1)
operator = ''

if re.match(r"\+|#|\.|/|;|\?|&", varlist):
operator = varlist[0]
varlist = varlist[1:]

prefix = '' if operator == '+' else operator
joiner = _template_joiner(operator)

params = []
for varspec in varlist.split(','):
params += _varspec_expansion(operator, varspec, kwargs)

uri = [_format_mapping(operator, item) for item in params]

if not uri:
return ""

return prefix + joiner.join(uri)

return re.sub(r"{(.*?)}", template_expansion, template)


0 comments on commit 9dd9704

Please sign in to comment.