| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- # (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 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 = cgi.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()
|