• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3"""
4URI Template (RFC6570) Processor
5"""
6
7__copyright__ = """\
8Copyright 2011-2013 Joe Gregorio
9
10Licensed under the Apache License, Version 2.0 (the "License");
11you may not use this file except in compliance with the License.
12You may obtain a copy of the License at
13
14   http://www.apache.org/licenses/LICENSE-2.0
15
16Unless required by applicable law or agreed to in writing, software
17distributed under the License is distributed on an "AS IS" BASIS,
18WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19See the License for the specific language governing permissions and
20limitations under the License.
21"""
22
23import re
24try:
25   from urllib.parse import quote
26except ImportError:
27   from urllib import quote
28
29
30
31__version__ = "0.6"
32
33RESERVED = ":/?#[]@!$&'()*+,;="
34OPERATOR = "+#./;?&|!@"
35MODIFIER = ":^"
36TEMPLATE = re.compile(r"{([^\}]+)}")
37
38
39def variables(template):
40    '''Returns the set of keywords in a uri template'''
41    vars = set()
42    for varlist in TEMPLATE.findall(template):
43        if varlist[0] in OPERATOR:
44            varlist = varlist[1:]
45        varspecs = varlist.split(',')
46        for var in varspecs:
47            # handle prefix values
48            var = var.split(':')[0]
49            # handle composite values
50            if var.endswith('*'):
51                var = var[:-1]
52            vars.add(var)
53    return vars
54
55
56def _quote(value, safe, prefix=None):
57    if prefix is not None:
58        return quote(str(value)[:prefix], safe)
59    return quote(str(value), safe)
60
61
62def _tostring(varname, value, explode, prefix, operator, safe=""):
63    if isinstance(value, list):
64        return ",".join([_quote(x, safe) for x in value])
65    if isinstance(value, dict):
66        keys = sorted(value.keys())
67        if explode:
68            return ",".join([_quote(key, safe) + "=" + \
69                             _quote(value[key], safe) for key in keys])
70        else:
71            return ",".join([_quote(key, safe) + "," + \
72                             _quote(value[key], safe) for key in keys])
73    elif value is None:
74        return
75    else:
76        return _quote(value, safe, prefix)
77
78
79def _tostring_path(varname, value, explode, prefix, operator, safe=""):
80    joiner = operator
81    if isinstance(value, list):
82        if explode:
83            out = [_quote(x, safe) for x in value if value is not None]
84        else:
85            joiner = ","
86            out = [_quote(x, safe) for x in value if value is not None]
87        if out:
88            return joiner.join(out)
89        else:
90            return
91    elif isinstance(value, dict):
92        keys = sorted(value.keys())
93        if explode:
94            out = [_quote(key, safe) + "=" + \
95                   _quote(value[key], safe) for key in keys \
96                   if value[key] is not None]
97        else:
98            joiner = ","
99            out = [_quote(key, safe) + "," + \
100                   _quote(value[key], safe) \
101                   for key in keys if value[key] is not None]
102        if out:
103            return joiner.join(out)
104        else:
105            return
106    elif value is None:
107        return
108    else:
109        return _quote(value, safe, prefix)
110
111
112def _tostring_semi(varname, value, explode, prefix, operator, safe=""):
113    joiner = operator
114    if operator == "?":
115        joiner = "&"
116    if isinstance(value, list):
117        if explode:
118            out = [varname + "=" + _quote(x, safe) \
119                   for x in value if x is not None]
120            if out:
121                return joiner.join(out)
122            else:
123                return
124        else:
125            return varname + "=" + ",".join([_quote(x, safe) \
126                                             for x in value])
127    elif isinstance(value, dict):
128        keys = sorted(value.keys())
129        if explode:
130            return joiner.join([_quote(key, safe) + "=" + \
131                                _quote(value[key], safe) \
132                                for key in keys if key is not None])
133        else:
134            return varname + "=" + ",".join([_quote(key, safe) + "," + \
135                             _quote(value[key], safe) for key in keys \
136                             if key is not None])
137    else:
138        if value is None:
139            return
140        elif value:
141            return (varname + "=" + _quote(value, safe, prefix))
142        else:
143            return varname
144
145
146def _tostring_query(varname, value, explode, prefix, operator, safe=""):
147    joiner = operator
148    if operator in ["?", "&"]:
149        joiner = "&"
150    if isinstance(value, list):
151        if 0 == len(value):
152            return None
153        if explode:
154            return joiner.join([varname + "=" + _quote(x, safe) \
155                                for x in value])
156        else:
157            return (varname + "=" + ",".join([_quote(x, safe) \
158                                             for x in value]))
159    elif isinstance(value, dict):
160        if 0 == len(value):
161            return None
162        keys = sorted(value.keys())
163        if explode:
164            return joiner.join([_quote(key, safe) + "=" + \
165                                _quote(value[key], safe) \
166                                for key in keys])
167        else:
168            return varname + "=" + \
169                   ",".join([_quote(key, safe) + "," + \
170                             _quote(value[key], safe) for key in keys])
171    else:
172        if value is None:
173            return
174        elif value:
175            return (varname + "=" + _quote(value, safe, prefix))
176        else:
177            return (varname  + "=")
178
179
180TOSTRING = {
181    "" : _tostring,
182    "+": _tostring,
183    "#": _tostring,
184    ";": _tostring_semi,
185    "?": _tostring_query,
186    "&": _tostring_query,
187    "/": _tostring_path,
188    ".": _tostring_path,
189    }
190
191
192def expand(template, variables):
193    """
194    Expand template as a URI Template using variables.
195    """
196    def _sub(match):
197        expression = match.group(1)
198        operator = ""
199        if expression[0] in OPERATOR:
200            operator = expression[0]
201            varlist = expression[1:]
202        else:
203            varlist = expression
204
205        safe = ""
206        if operator in ["+", "#"]:
207            safe = RESERVED
208        varspecs = varlist.split(",")
209        varnames = []
210        defaults = {}
211        for varspec in varspecs:
212            default = None
213            explode = False
214            prefix = None
215            if "=" in varspec:
216                varname, default = tuple(varspec.split("=", 1))
217            else:
218                varname = varspec
219            if varname[-1] == "*":
220                explode = True
221                varname = varname[:-1]
222            elif ":" in varname:
223                try:
224                    prefix = int(varname[varname.index(":")+1:])
225                except ValueError:
226                    raise ValueError("non-integer prefix '{0}'".format(
227                       varname[varname.index(":")+1:]))
228                varname = varname[:varname.index(":")]
229            if default:
230                defaults[varname] = default
231            varnames.append((varname, explode, prefix))
232
233        retval = []
234        joiner = operator
235        start = operator
236        if operator == "+":
237            start = ""
238            joiner = ","
239        if operator == "#":
240            joiner = ","
241        if operator == "?":
242            joiner = "&"
243        if operator == "&":
244            start = "&"
245        if operator == "":
246            joiner = ","
247        for varname, explode, prefix in varnames:
248            if varname in variables:
249                value = variables[varname]
250                if not value and value != "" and varname in defaults:
251                    value = defaults[varname]
252            elif varname in defaults:
253                value = defaults[varname]
254            else:
255                continue
256            expanded = TOSTRING[operator](
257              varname, value, explode, prefix, operator, safe=safe)
258            if expanded is not None:
259                retval.append(expanded)
260        if len(retval) > 0:
261            return start + joiner.join(retval)
262        else:
263            return ""
264
265    return TEMPLATE.sub(_sub, template)
266