1"""
2
3uritemplate.template
4====================
5
6This module contains the essential inner workings of uritemplate.
7
8What treasures await you:
9
10- URITemplate class
11
12You see a treasure chest of knowledge in front of you.
13What do you do?
14>
15
16"""
17
18import re
19from uritemplate.orderedset import OrderedSet
20from uritemplate.variable import URIVariable
21
22template_re = re.compile('{([^}]+)}')
23
24
25def _merge(var_dict, overrides):
26    if var_dict:
27        opts = var_dict.copy()
28        opts.update(overrides)
29        return opts
30    return overrides
31
32
33class URITemplate(object):
34
35    """This parses the template and will be used to expand it.
36
37    This is the most important object as the center of the API.
38
39    Example::
40
41        from uritemplate import URITemplate
42        import requests
43
44
45        t = URITemplate(
46            'https://api.github.com/users/sigmavirus24/gists{/gist_id}'
47        )
48        uri = t.expand(gist_id=123456)
49        resp = requests.get(uri)
50        for gist in resp.json():
51            print(gist['html_url'])
52
53    Please note::
54
55        str(t)
56        # 'https://api.github.com/users/sigmavirus24/gists{/gistid}'
57        repr(t)  # is equivalent to
58        # URITemplate(str(t))
59        # Where str(t) is interpreted as the URI string.
60
61    Also, ``URITemplates`` are hashable so they can be used as keys in
62    dictionaries.
63
64    """
65
66    def __init__(self, uri):
67        #: The original URI to be parsed.
68        self.uri = uri
69        #: A list of the variables in the URI. They are stored as
70        #: :class:`URIVariable`\ s
71        self.variables = [
72            URIVariable(m.groups()[0]) for m in template_re.finditer(self.uri)
73        ]
74        #: A set of variable names in the URI.
75        self.variable_names = OrderedSet()
76        for variable in self.variables:
77            for name in variable.variable_names:
78                self.variable_names.add(name)
79
80    def __repr__(self):
81        return 'URITemplate("%s")' % self
82
83    def __str__(self):
84        return self.uri
85
86    def __eq__(self, other):
87        return self.uri == other.uri
88
89    def __hash__(self):
90        return hash(self.uri)
91
92    def _expand(self, var_dict, replace):
93        if not self.variables:
94            return self.uri
95
96        expansion = var_dict
97        expanded = {}
98        for v in self.variables:
99            expanded.update(v.expand(expansion))
100
101        def replace_all(match):
102            return expanded.get(match.groups()[0], '')
103
104        def replace_partial(match):
105            match = match.groups()[0]
106            var = '{%s}' % match
107            return expanded.get(match) or var
108
109        replace = replace_partial if replace else replace_all
110
111        return template_re.sub(replace, self.uri)
112
113    def expand(self, var_dict=None, **kwargs):
114        """Expand the template with the given parameters.
115
116        :param dict var_dict: Optional dictionary with variables and values
117        :param kwargs: Alternative way to pass arguments
118        :returns: str
119
120        Example::
121
122            t = URITemplate('https://api.github.com{/end}')
123            t.expand({'end': 'users'})
124            t.expand(end='gists')
125
126        .. note:: Passing values by both parts, may override values in
127                  ``var_dict``. For example::
128
129                      expand('https://{var}', {'var': 'val1'}, var='val2')
130
131                  ``val2`` will be used instead of ``val1``.
132
133        """
134        return self._expand(_merge(var_dict, kwargs), False)
135
136    def partial(self, var_dict=None, **kwargs):
137        """Partially expand the template with the given parameters.
138
139        If all of the parameters for the template are not given, return a
140        partially expanded template.
141
142        :param dict var_dict: Optional dictionary with variables and values
143        :param kwargs: Alternative way to pass arguments
144        :returns: :class:`URITemplate`
145
146        Example::
147
148            t = URITemplate('https://api.github.com{/end}')
149            t.partial()  # => URITemplate('https://api.github.com{/end}')
150
151        """
152        return URITemplate(self._expand(_merge(var_dict, kwargs), True))
153