| # (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() |
| |