diff options
author | Marc Abramowitz <marc@marc-abramowitz.com> | 2016-03-07 18:52:36 -0800 |
---|---|---|
committer | Marc Abramowitz <marc@marc-abramowitz.com> | 2016-03-07 18:52:36 -0800 |
commit | cc83e06efff71b81ca5a3ac6df65775971181295 (patch) | |
tree | d52fa3f1a93730f263c2c5ac8266de8e5fb12abf /paste/url.py | |
download | paste-git-tox_coverage.tar.gz |
tox.ini: Measure test coveragetox_coverage
Diffstat (limited to 'paste/url.py')
-rw-r--r-- | paste/url.py | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/paste/url.py b/paste/url.py new file mode 100644 index 0000000..fb08d6d --- /dev/null +++ b/paste/url.py @@ -0,0 +1,478 @@ +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +""" +This module implements a class for handling URLs. +""" +from six.moves.urllib.parse import parse_qsl, quote, unquote, urlencode +import cgi +from paste import request +import six + +# Imported lazily from FormEncode: +variabledecode = None + +__all__ = ["URL", "Image"] + +def html_quote(v): + if v is None: + return '' + return cgi.escape(str(v), 1) + +def url_quote(v): + if v is None: + return '' + return quote(str(v)) + +def js_repr(v): + if v is None: + return 'null' + elif v is False: + return 'false' + elif v is True: + return 'true' + elif isinstance(v, list): + return '[%s]' % ', '.join(map(js_repr, v)) + elif isinstance(v, dict): + return '{%s}' % ', '.join( + ['%s: %s' % (js_repr(key), js_repr(value)) + for key, value in v]) + elif isinstance(v, str): + return repr(v) + elif isinstance(v, unicode): + # @@: how do you do Unicode literals in Javascript? + return repr(v.encode('UTF-8')) + elif isinstance(v, (float, int)): + return repr(v) + elif isinstance(v, long): + return repr(v).lstrip('L') + elif hasattr(v, '__js_repr__'): + return v.__js_repr__() + else: + raise ValueError( + "I don't know how to turn %r into a Javascript representation" + % v) + +class URLResource(object): + + """ + This is an abstract superclass for different kinds of URLs + """ + + default_params = {} + + def __init__(self, url, vars=None, attrs=None, + params=None): + self.url = url or '/' + self.vars = vars or [] + self.attrs = attrs or {} + self.params = self.default_params.copy() + self.original_params = params or {} + if params: + self.params.update(params) + + #@classmethod + def from_environ(cls, environ, with_query_string=True, + with_path_info=True, script_name=None, + path_info=None, querystring=None): + url = request.construct_url( + environ, with_query_string=False, + with_path_info=with_path_info, script_name=script_name, + path_info=path_info) + if with_query_string: + if querystring is None: + vars = request.parse_querystring(environ) + else: + vars = parse_qsl( + querystring, + keep_blank_values=True, + strict_parsing=False) + else: + vars = None + v = cls(url, vars=vars) + return v + + from_environ = classmethod(from_environ) + + def __call__(self, *args, **kw): + res = self._add_positional(args) + res = res._add_vars(kw) + return res + + def __getitem__(self, item): + if '=' in item: + name, value = item.split('=', 1) + return self._add_vars({unquote(name): unquote(value)}) + return self._add_positional((item,)) + + def attr(self, **kw): + for key in kw.keys(): + if key.endswith('_'): + kw[key[:-1]] = kw[key] + del kw[key] + new_attrs = self.attrs.copy() + new_attrs.update(kw) + return self.__class__(self.url, vars=self.vars, + attrs=new_attrs, + params=self.original_params) + + def param(self, **kw): + new_params = self.original_params.copy() + new_params.update(kw) + return self.__class__(self.url, vars=self.vars, + attrs=self.attrs, + params=new_params) + + def coerce_vars(self, vars): + global variabledecode + need_variable_encode = False + for key, value in vars.items(): + if isinstance(value, dict): + need_variable_encode = True + if key.endswith('_'): + vars[key[:-1]] = vars[key] + del vars[key] + if need_variable_encode: + if variabledecode is None: + from formencode import variabledecode + vars = variabledecode.variable_encode(vars) + return vars + + + def var(self, **kw): + kw = self.coerce_vars(kw) + new_vars = self.vars + list(kw.items()) + return self.__class__(self.url, vars=new_vars, + attrs=self.attrs, + params=self.original_params) + + def setvar(self, **kw): + """ + Like ``.var(...)``, except overwrites keys, where .var simply + extends the keys. Setting a variable to None here will + effectively delete it. + """ + kw = self.coerce_vars(kw) + new_vars = [] + for name, values in self.vars: + if name in kw: + continue + new_vars.append((name, values)) + new_vars.extend(kw.items()) + return self.__class__(self.url, vars=new_vars, + attrs=self.attrs, + params=self.original_params) + + def setvars(self, **kw): + """ + Creates a copy of this URL, but with all the variables set/reset + (like .setvar(), except clears past variables at the same time) + """ + return self.__class__(self.url, vars=kw.items(), + attrs=self.attrs, + params=self.original_params) + + def addpath(self, *paths): + u = self + for path in paths: + path = str(path).lstrip('/') + new_url = u.url + if not new_url.endswith('/'): + new_url += '/' + u = u.__class__(new_url+path, vars=u.vars, + attrs=u.attrs, + params=u.original_params) + return u + + if six.PY3: + __truediv__ = addpath + else: + __div__ = addpath + + def become(self, OtherClass): + return OtherClass(self.url, vars=self.vars, + attrs=self.attrs, + params=self.original_params) + + def href__get(self): + s = self.url + if self.vars: + s += '?' + vars = [] + for name, val in self.vars: + if isinstance(val, (list, tuple)): + val = [v for v in val if v is not None] + elif val is None: + continue + vars.append((name, val)) + s += urlencode(vars, True) + return s + + href = property(href__get) + + def __repr__(self): + base = '<%s %s' % (self.__class__.__name__, + self.href or "''") + if self.attrs: + base += ' attrs(%s)' % ( + ' '.join(['%s="%s"' % (html_quote(n), html_quote(v)) + for n, v in self.attrs.items()])) + if self.original_params: + base += ' params(%s)' % ( + ', '.join(['%s=%r' % (n, v) + for n, v in self.attrs.items()])) + return base + '>' + + def html__get(self): + if not self.params.get('tag'): + raise ValueError( + "You cannot get the HTML of %r until you set the " + "'tag' param'" % self) + content = self._get_content() + tag = '<%s' % self.params.get('tag') + attrs = ' '.join([ + '%s="%s"' % (html_quote(n), html_quote(v)) + for n, v in self._html_attrs()]) + if attrs: + tag += ' ' + attrs + tag += self._html_extra() + if content is None: + return tag + ' />' + else: + return '%s>%s</%s>' % (tag, content, self.params.get('tag')) + + html = property(html__get) + + def _html_attrs(self): + return self.attrs.items() + + def _html_extra(self): + return '' + + def _get_content(self): + """ + Return the content for a tag (for self.html); return None + for an empty tag (like ``<img />``) + """ + raise NotImplementedError + + def _add_vars(self, vars): + raise NotImplementedError + + def _add_positional(self, args): + raise NotImplementedError + +class URL(URLResource): + + r""" + >>> u = URL('http://localhost') + >>> u + <URL http://localhost> + >>> u = u['view'] + >>> str(u) + 'http://localhost/view' + >>> u['//foo'].param(content='view').html + '<a href="http://localhost/view/foo">view</a>' + >>> u.param(confirm='Really?', content='goto').html + '<a href="http://localhost/view" onclick="return confirm(\'Really?\')">goto</a>' + >>> u(title='See "it"', content='goto').html + '<a href="http://localhost/view?title=See+%22it%22">goto</a>' + >>> u('another', var='fuggetaboutit', content='goto').html + '<a href="http://localhost/view/another?var=fuggetaboutit">goto</a>' + >>> u.attr(content='goto').html + Traceback (most recent call last): + .... + ValueError: You must give a content param to <URL http://localhost/view attrs(content="goto")> generate anchor tags + >>> str(u['foo=bar%20stuff']) + 'http://localhost/view?foo=bar+stuff' + """ + + default_params = {'tag': 'a'} + + def __str__(self): + return self.href + + def _get_content(self): + if not self.params.get('content'): + raise ValueError( + "You must give a content param to %r generate anchor tags" + % self) + return self.params['content'] + + def _add_vars(self, vars): + url = self + for name in ('confirm', 'content'): + if name in vars: + url = url.param(**{name: vars.pop(name)}) + if 'target' in vars: + url = url.attr(target=vars.pop('target')) + return url.var(**vars) + + def _add_positional(self, args): + return self.addpath(*args) + + def _html_attrs(self): + attrs = list(self.attrs.items()) + attrs.insert(0, ('href', self.href)) + if self.params.get('confirm'): + attrs.append(('onclick', 'return confirm(%s)' + % js_repr(self.params['confirm']))) + return attrs + + def onclick_goto__get(self): + return 'location.href=%s; return false' % js_repr(self.href) + + onclick_goto = property(onclick_goto__get) + + def button__get(self): + return self.become(Button) + + button = property(button__get) + + def js_popup__get(self): + return self.become(JSPopup) + + js_popup = property(js_popup__get) + +class Image(URLResource): + + r""" + >>> i = Image('/images') + >>> i = i / '/foo.png' + >>> i.html + '<img src="/images/foo.png" />' + >>> str(i['alt=foo']) + '<img src="/images/foo.png" alt="foo" />' + >>> i.href + '/images/foo.png' + """ + + default_params = {'tag': 'img'} + + def __str__(self): + return self.html + + def _get_content(self): + return None + + def _add_vars(self, vars): + return self.attr(**vars) + + def _add_positional(self, args): + return self.addpath(*args) + + def _html_attrs(self): + attrs = list(self.attrs.items()) + attrs.insert(0, ('src', self.href)) + return attrs + +class Button(URLResource): + + r""" + >>> u = URL('/') + >>> u = u / 'delete' + >>> b = u.button['confirm=Sure?'](id=5, content='del') + >>> str(b) + '<button onclick="if (confirm(\'Sure?\')) {location.href=\'/delete?id=5\'}; return false">del</button>' + """ + + default_params = {'tag': 'button'} + + def __str__(self): + return self.html + + def _get_content(self): + if self.params.get('content'): + return self.params['content'] + if self.attrs.get('value'): + return self.attrs['content'] + # @@: Error? + return None + + def _add_vars(self, vars): + button = self + if 'confirm' in vars: + button = button.param(confirm=vars.pop('confirm')) + if 'content' in vars: + button = button.param(content=vars.pop('content')) + return button.var(**vars) + + def _add_positional(self, args): + return self.addpath(*args) + + def _html_attrs(self): + attrs = list(self.attrs.items()) + onclick = 'location.href=%s' % js_repr(self.href) + if self.params.get('confirm'): + onclick = 'if (confirm(%s)) {%s}' % ( + js_repr(self.params['confirm']), onclick) + onclick += '; return false' + attrs.insert(0, ('onclick', onclick)) + return attrs + +class JSPopup(URLResource): + + r""" + >>> u = URL('/') + >>> u = u / 'view' + >>> j = u.js_popup(content='view') + >>> j.html + '<a href="/view" onclick="window.open(\'/view\', \'_blank\'); return false" target="_blank">view</a>' + """ + + default_params = {'tag': 'a', 'target': '_blank'} + + def _add_vars(self, vars): + button = self + for var in ('width', 'height', 'stripped', 'content'): + if var in vars: + button = button.param(**{var: vars.pop(var)}) + return button.var(**vars) + + def _window_args(self): + p = self.params + features = [] + if p.get('stripped'): + p['location'] = p['status'] = p['toolbar'] = '0' + for param in 'channelmode directories fullscreen location menubar resizable scrollbars status titlebar'.split(): + if param not in p: + continue + v = p[param] + if v not in ('yes', 'no', '1', '0'): + if v: + v = '1' + else: + v = '0' + features.append('%s=%s' % (param, v)) + for param in 'height left top width': + if not p.get(param): + continue + features.append('%s=%s' % (param, p[param])) + args = [self.href, p['target']] + if features: + args.append(','.join(features)) + return ', '.join(map(js_repr, args)) + + def _html_attrs(self): + attrs = list(self.attrs.items()) + onclick = ('window.open(%s); return false' + % self._window_args()) + attrs.insert(0, ('target', self.params['target'])) + attrs.insert(0, ('onclick', onclick)) + attrs.insert(0, ('href', self.href)) + return attrs + + def _get_content(self): + if not self.params.get('content'): + raise ValueError( + "You must give a content param to %r generate anchor tags" + % self) + return self.params['content'] + + def _add_positional(self, args): + return self.addpath(*args) + +if __name__ == '__main__': + import doctest + doctest.testmod() + |