Package trac :: Package web :: Module href

Source Code for Module trac.web.href

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2003-2020 Edgewall Software 
  4  # Copyright (C) 2003-2004 Jonas Borgström <[email protected]> 
  5  # Copyright (C) 2005 Christopher Lenz <[email protected]> 
  6  # All rights reserved. 
  7  # 
  8  # This software is licensed as described in the file COPYING, which 
  9  # you should have received as part of this distribution. The terms 
 10  # are also available at https://trac.edgewall.org/wiki/TracLicense. 
 11  # 
 12  # This software consists of voluntary contributions made by many 
 13  # individuals. For the exact contribution history, see the revision 
 14  # history and logs, available at https://trac.edgewall.org/log/. 
 15  # 
 16  # Author: Jonas Borgström <[email protected]> 
 17  #         Christopher Lenz <[email protected]> 
 18   
 19  import re 
 20   
 21  from trac.util.text import unicode_quote, unicode_urlencode 
 22   
 23   
 24  slashes_re = re.compile(r'/{2,}') 
 25   
 26   
27 -class Href(object):
28 """Implements a callable that constructs URLs with the given base. The 29 function can be called with any number of positional and keyword 30 arguments which then are used to assemble the URL. 31 32 Positional arguments are appended as individual segments to 33 the path of the URL: 34 35 >>> href = Href('/trac') 36 >>> repr(href) 37 "<Href '/trac'>" 38 >>> href('ticket', 540) 39 '/trac/ticket/540' 40 >>> href('ticket', 540, 'attachment', 'bugfix.patch') 41 '/trac/ticket/540/attachment/bugfix.patch' 42 >>> href('ticket', '540/attachment/bugfix.patch') 43 '/trac/ticket/540/attachment/bugfix.patch' 44 45 If a positional parameter evaluates to None, it will be skipped: 46 47 >>> href('ticket', 540, 'attachment', None) 48 '/trac/ticket/540/attachment' 49 50 The first path segment can also be specified by calling an attribute 51 of the instance, as follows: 52 53 >>> href.ticket(540) 54 '/trac/ticket/540' 55 >>> href.changeset(42, format='diff') 56 '/trac/changeset/42?format=diff' 57 58 Simply calling the Href object with no arguments will return the base URL: 59 60 >>> href() 61 '/trac' 62 63 Keyword arguments are added to the query string, unless the value is None: 64 65 >>> href = Href('/trac') 66 >>> href('timeline', format='rss') 67 '/trac/timeline?format=rss' 68 >>> href('timeline', format=None) 69 '/trac/timeline' 70 >>> href('search', q='foo bar') 71 '/trac/search?q=foo+bar' 72 73 Multiple values for one parameter are specified using a sequence (a list or 74 tuple) for the parameter: 75 76 >>> href('timeline', show=['ticket', 'wiki', 'changeset']) 77 '/trac/timeline?show=ticket&show=wiki&show=changeset' 78 79 Alternatively, query string parameters can be added by passing a dict or 80 list as last positional argument: 81 82 >>> href('timeline', {'from': '02/24/05', 'daysback': 30}) 83 '/trac/timeline?daysback=30&from=02%2F24%2F05' 84 >>> href('timeline', {}) 85 '/trac/timeline' 86 >>> href('timeline', [('from', '02/24/05')]) 87 '/trac/timeline?from=02%2F24%2F05' 88 >>> href('timeline', ()) == href('timeline', []) == href('timeline', {}) 89 True 90 91 The usual way of quoting arguments that would otherwise be interpreted 92 as Python keywords is supported too: 93 94 >>> href('timeline', from_='02/24/05', daysback=30) 95 '/trac/timeline?from=02%2F24%2F05&daysback=30' 96 97 If the order of query string parameters should be preserved, you may also 98 pass a sequence of (name, value) tuples as last positional argument: 99 100 >>> href('query', (('group', 'component'), ('groupdesc', 1))) 101 '/trac/query?group=component&groupdesc=1' 102 103 >>> params = [] 104 >>> params.append(('group', 'component')) 105 >>> params.append(('groupdesc', 1)) 106 >>> href('query', params) 107 '/trac/query?group=component&groupdesc=1' 108 109 By specifying an absolute base, the function returned will also generate 110 absolute URLs: 111 112 >>> href = Href('https://trac.edgewall.org') 113 >>> href('ticket', 540) 114 'https://trac.edgewall.org/ticket/540' 115 116 >>> href = Href('https://trac.edgewall.org') 117 >>> href('ticket', 540) 118 'https://trac.edgewall.org/ticket/540' 119 120 In common usage, it may improve readability to use the function-calling 121 ability for the first component of the URL as mentioned earlier: 122 123 >>> href = Href('/trac') 124 >>> href.ticket(540) 125 '/trac/ticket/540' 126 >>> href.browser('/trunk/README.txt', format='txt') 127 '/trac/browser/trunk/README.txt?format=txt' 128 129 The ``path_safe`` argument specifies the characters that don't 130 need to be quoted in the path arguments. Likewise, the 131 ``query_safe`` argument specifies the characters that don't need 132 to be quoted in the query string: 133 134 >>> href = Href('') 135 >>> href.milestone('<look,here>', param='<here,too>') 136 '/milestone/%3Clook%2Chere%3E?param=%3Chere%2Ctoo%3E' 137 138 >>> href = Href('', path_safe='/<,', query_safe=',>') 139 >>> href.milestone('<look,here>', param='<here,too>') 140 '/milestone/<look,here%3E?param=%3Chere,too>' 141 """ 142
143 - def __init__(self, base, path_safe="/!~*'()", query_safe="!~*'()"):
144 self.base = base.rstrip('/') 145 self.path_safe = path_safe 146 self.query_safe = query_safe 147 self._derived = {}
148
149 - def __repr__(self):
150 return '<%s %r>' % (self.__class__.__name__, self.base)
151
152 - def __call__(self, *args, **kw):
153 href = self.base 154 params = [] 155 156 def add_param(name, value): 157 if isinstance(value, (list, tuple)): 158 for i in [i for i in value if i is not None]: 159 params.append((name, i)) 160 elif value is not None: 161 params.append((name, value))
162 163 if args: 164 lastp = args[-1] 165 if isinstance(lastp, dict): 166 for k, v in lastp.items(): 167 add_param(k, v) 168 args = args[:-1] 169 elif isinstance(lastp, (list, tuple)): 170 for k, v in lastp: 171 add_param(k, v) 172 args = args[:-1] 173 174 # build the path 175 path = '/'.join(unicode_quote(unicode(arg).strip('/'), self.path_safe) 176 for arg in args if arg is not None) 177 if path: 178 href += '/' + slashes_re.sub('/', path).lstrip('/') 179 elif not href: 180 href = '/' 181 182 # assemble the query string 183 for k, v in kw.items(): 184 add_param(k[:-1] if k.endswith('_') else k, v) 185 if params: 186 href += '?' + unicode_urlencode(params, self.query_safe) 187 188 return href
189
190 - def __getattr__(self, name):
191 if name not in self._derived: 192 self._derived[name] = lambda *args, **kw: self(name, *args, **kw) 193 return self._derived[name]
194 195 _printable_safe = ''.join(map(chr, xrange(0x21, 0x7f))) 196
197 - def __add__(self, rhs):
198 if not rhs: 199 return self.base or '/' 200 if rhs.startswith('?'): 201 return (self.base or '/') + \ 202 unicode_quote(rhs, self._printable_safe) 203 if not rhs.startswith('/'): 204 rhs = '/' + rhs 205 return self.base + unicode_quote(rhs, self._printable_safe)
206 207 208 if __name__ == '__main__': 209 import doctest, sys 210 doctest.testmod(sys.modules[__name__]) 211