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 >>> href('ticket', 540) 37 '/trac/ticket/540' 38 >>> href('ticket', 540, 'attachment', 'bugfix.patch') 39 '/trac/ticket/540/attachment/bugfix.patch' 40 >>> href('ticket', '540/attachment/bugfix.patch') 41 '/trac/ticket/540/attachment/bugfix.patch' 42 43 If a positional parameter evaluates to None, it will be skipped: 44 45 >>> href('ticket', 540, 'attachment', None) 46 '/trac/ticket/540/attachment' 47 48 The first path segment can also be specified by calling an attribute 49 of the instance, as follows: 50 51 >>> href.ticket(540) 52 '/trac/ticket/540' 53 >>> href.changeset(42, format='diff') 54 '/trac/changeset/42?format=diff' 55 56 Simply calling the Href object with no arguments will return the base URL: 57 58 >>> href() 59 '/trac' 60 61 Keyword arguments are added to the query string, unless the value is None: 62 63 >>> href = Href('/trac') 64 >>> href('timeline', format='rss') 65 '/trac/timeline?format=rss' 66 >>> href('timeline', format=None) 67 '/trac/timeline' 68 >>> href('search', q='foo bar') 69 '/trac/search?q=foo+bar' 70 71 Multiple values for one parameter are specified using a sequence (a list or 72 tuple) for the parameter: 73 74 >>> href('timeline', show=['ticket', 'wiki', 'changeset']) 75 '/trac/timeline?show=ticket&show=wiki&show=changeset' 76 77 Alternatively, query string parameters can be added by passing a dict or 78 list as last positional argument: 79 80 >>> href('timeline', {'from': '02/24/05', 'daysback': 30}) 81 '/trac/timeline?daysback=30&from=02%2F24%2F05' 82 >>> href('timeline', {}) 83 '/trac/timeline' 84 >>> href('timeline', [('from', '02/24/05')]) 85 '/trac/timeline?from=02%2F24%2F05' 86 >>> href('timeline', ()) == href('timeline', []) == href('timeline', {}) 87 True 88 89 The usual way of quoting arguments that would otherwise be interpreted 90 as Python keywords is supported too: 91 92 >>> href('timeline', from_='02/24/05', daysback=30) 93 '/trac/timeline?from=02%2F24%2F05&daysback=30' 94 95 If the order of query string parameters should be preserved, you may also 96 pass a sequence of (name, value) tuples as last positional argument: 97 98 >>> href('query', (('group', 'component'), ('groupdesc', 1))) 99 '/trac/query?group=component&groupdesc=1' 100 101 >>> params = [] 102 >>> params.append(('group', 'component')) 103 >>> params.append(('groupdesc', 1)) 104 >>> href('query', params) 105 '/trac/query?group=component&groupdesc=1' 106 107 By specifying an absolute base, the function returned will also generate 108 absolute URLs: 109 110 >>> href = Href('https://trac.edgewall.org') 111 >>> href('ticket', 540) 112 'https://trac.edgewall.org/ticket/540' 113 114 >>> href = Href('https://trac.edgewall.org') 115 >>> href('ticket', 540) 116 'https://trac.edgewall.org/ticket/540' 117 118 In common usage, it may improve readability to use the function-calling 119 ability for the first component of the URL as mentioned earlier: 120 121 >>> href = Href('/trac') 122 >>> href.ticket(540) 123 '/trac/ticket/540' 124 >>> href.browser('/trunk/README.txt', format='txt') 125 '/trac/browser/trunk/README.txt?format=txt' 126 127 The ``path_safe`` argument specifies the characters that don't 128 need to be quoted in the path arguments. Likewise, the 129 ``query_safe`` argument specifies the characters that don't need 130 to be quoted in the query string: 131 132 >>> href = Href('') 133 >>> href.milestone('<look,here>', param='<here,too>') 134 '/milestone/%3Clook%2Chere%3E?param=%3Chere%2Ctoo%3E' 135 136 >>> href = Href('', path_safe='/<,', query_safe=',>') 137 >>> href.milestone('<look,here>', param='<here,too>') 138 '/milestone/<look,here%3E?param=%3Chere,too>' 139 """ 140
141 - def __init__(self, base, path_safe="/!~*'()", query_safe="!~*'()"):
142 self.base = base.rstrip('/') 143 self.path_safe = path_safe 144 self.query_safe = query_safe 145 self._derived = {}
146
147 - def __call__(self, *args, **kw):
148 href = self.base 149 params = [] 150 151 def add_param(name, value): 152 if isinstance(value, (list, tuple)): 153 for i in [i for i in value if i is not None]: 154 params.append((name, i)) 155 elif value is not None: 156 params.append((name, value))
157 158 if args: 159 lastp = args[-1] 160 if isinstance(lastp, dict): 161 for k, v in lastp.items(): 162 add_param(k, v) 163 args = args[:-1] 164 elif isinstance(lastp, (list, tuple)): 165 for k, v in lastp: 166 add_param(k, v) 167 args = args[:-1] 168 169 # build the path 170 path = '/'.join(unicode_quote(unicode(arg).strip('/'), self.path_safe) 171 for arg in args if arg is not None) 172 if path: 173 href += '/' + slashes_re.sub('/', path).lstrip('/') 174 elif not href: 175 href = '/' 176 177 # assemble the query string 178 for k, v in kw.items(): 179 add_param(k[:-1] if k.endswith('_') else k, v) 180 if params: 181 href += '?' + unicode_urlencode(params, self.query_safe) 182 183 return href
184
185 - def __getattr__(self, name):
186 if name not in self._derived: 187 self._derived[name] = lambda *args, **kw: self(name, *args, **kw) 188 return self._derived[name]
189 190 _printable_safe = ''.join(map(chr, xrange(0x21, 0x7f))) 191
192 - def __add__(self, rhs):
193 if not rhs: 194 return self.base or '/' 195 if rhs.startswith('?'): 196 return (self.base or '/') + \ 197 unicode_quote(rhs, self._printable_safe) 198 if not rhs.startswith('/'): 199 rhs = '/' + rhs 200 return self.base + unicode_quote(rhs, self._printable_safe)
201 202 203 if __name__ == '__main__': 204 import doctest, sys 205 doctest.testmod(sys.modules[__name__]) 206