479 lines
14 KiB
Python
479 lines
14 KiB
Python
|
# (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
|
||
|
from paste import request
|
||
|
import six
|
||
|
from paste.util import html
|
||
|
|
||
|
# Imported lazily from FormEncode:
|
||
|
variabledecode = None
|
||
|
|
||
|
__all__ = ["URL", "Image"]
|
||
|
|
||
|
def html_quote(v):
|
||
|
if v is None:
|
||
|
return ''
|
||
|
return html.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()
|
||
|
|