summaryrefslogtreecommitdiff
path: root/paste/url.py
diff options
context:
space:
mode:
Diffstat (limited to 'paste/url.py')
-rw-r--r--paste/url.py478
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()
+