Package trac :: Module resource

Source Code for Module trac.resource

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2006-2023 Edgewall Software 
  4  # Copyright (C) 2006-2007 Alec Thomas <[email protected]> 
  5  # Copyright (C) 2007 Christian Boos <[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: Christian Boos <[email protected]> 
 17  #         Alec Thomas <[email protected]> 
 18   
 19  from trac.core import * 
 20  from trac.util.html import Fragment, tag 
 21  from trac.util.presentation import classes 
 22  from trac.util.translation import _ 
 23   
 24   
25 -class ResourceExistsError(TracError):
26 """Thrown when attempting to insert an existing resource."""
27 28
29 -class ResourceNotFound(TracError):
30 """Thrown when a non-existent resource is requested"""
31 32
33 -class IResourceManager(Interface):
34
36 """Return resource realms managed by the component. 37 38 :rtype: `basestring` generator 39 """
40
41 - def get_resource_url(resource, href, **kwargs):
42 """Return the canonical URL for displaying the given resource. 43 44 :param resource: a `Resource` 45 :param href: an `Href` used for creating the URL 46 47 Note that if there's no special rule associated to this realm for 48 creating URLs (i.e. the standard convention of using realm/id applies), 49 then it's OK to not define this method. 50 """
51
52 - def get_resource_description(resource, format='default', context=None, 53 **kwargs):
54 """Return a string representation of the resource, according to the 55 `format`. 56 57 :param resource: the `Resource` to describe 58 :param format: the kind of description wanted. Typical formats are: 59 `'default'`, `'compact'` or `'summary'`. 60 :param context: an optional rendering context to allow rendering rich 61 output (like markup containing links) 62 :type context: `ResourceContext` 63 64 Additional keyword arguments can be given as extra information for 65 some formats. 66 67 For example, the ticket with the id 123 is represented as: 68 - `'#123'` in `'compact'` format, 69 - `'Ticket #123'` for the `default` format. 70 - `'Ticket #123 (closed defect): This is the summary'` for the 71 `'summary'` format 72 73 Note that it is also OK to not define this method if there's no 74 special way to represent the resource, in which case the standard 75 representations 'realm:id' (in compact mode) or 'Realm id' (in 76 default mode) will be used. 77 """
78
79 - def resource_exists(resource):
80 """Check whether the given `resource` exists physically. 81 82 :rtype: bool 83 84 Attempting to retrieve the model object for a non-existing 85 resource should raise a `ResourceNotFound` exception. 86 (''since 0.11.8'') 87 """
88 89
90 -class Resource(object):
91 """Resource identifier. 92 93 This specifies as precisely as possible *which* resource from a Trac 94 environment is manipulated. 95 96 A resource is identified by: 97 - a `realm` (a string like `'wiki'` or `'ticket'`) 98 - an `id`, which uniquely identifies a resource within its realm. 99 If the `id` information is not set, then the resource represents 100 the realm as a whole. 101 - an optional `version` information. 102 If `version` is `None`, this refers by convention to the latest 103 version of the resource. 104 105 Some generic and commonly used rendering methods are associated as well 106 to the Resource object. Those properties and methods actually delegate 107 the real work to the Resource's manager. 108 """ 109 110 __slots__ = ('realm', 'id', 'version', 'parent') 111
112 - def __repr__(self):
113 path = [] 114 r = self 115 while r: 116 name = r.realm 117 if r.id: 118 name += ':' + unicode(r.id) # id can be numerical 119 if r.version is not None: 120 name += '@' + unicode(r.version) 121 path.append(name or '') 122 r = r.parent 123 return '<Resource %r>' % (', '.join(reversed(path)))
124
125 - def __eq__(self, other):
126 return self.realm == other.realm and \ 127 self.id == other.id and \ 128 self.version == other.version and \ 129 self.parent == other.parent
130
131 - def __hash__(self):
132 """Hash this resource descriptor, including its hierarchy.""" 133 path = () 134 current = self 135 while current: 136 path += (self.realm, self.id, self.version) 137 current = current.parent 138 return hash(path)
139 140 # -- methods for creating other Resource identifiers 141
142 - def __new__(cls, resource_or_realm=None, id=False, version=False, 143 parent=False):
144 """Create a new Resource object from a specification. 145 146 :param resource_or_realm: this can be either: 147 - a `Resource`, which is then used as a base for making a copy 148 - a `basestring`, used to specify a `realm` 149 :param id: the resource identifier 150 :param version: the version or `None` for indicating the latest version 151 152 >>> main = Resource('wiki', 'WikiStart') 153 >>> repr(main) 154 "<Resource u'wiki:WikiStart'>" 155 156 >>> Resource(main) is main 157 True 158 159 >>> main3 = Resource(main, version=3) 160 >>> repr(main3) 161 "<Resource u'wiki:WikiStart@3'>" 162 163 >>> main0 = main3(version=0) 164 >>> repr(main0) 165 "<Resource u'wiki:WikiStart@0'>" 166 167 In a copy, if `id` is overridden, then the original `version` value 168 will not be reused. 169 170 >>> repr(Resource(main3, id="WikiEnd")) 171 "<Resource u'wiki:WikiEnd'>" 172 173 >>> repr(Resource(None)) 174 "<Resource ''>" 175 """ 176 realm = resource_or_realm 177 if isinstance(resource_or_realm, Resource): 178 if id is False and version is False and parent is False: 179 return resource_or_realm 180 else: # copy and override 181 realm = resource_or_realm.realm 182 if id is False: 183 id = resource_or_realm.id 184 if version is False: 185 if id == resource_or_realm.id: 186 version = resource_or_realm.version # could be 0... 187 else: 188 version = None 189 if parent is False: 190 parent = resource_or_realm.parent 191 else: 192 if id is False: 193 id = None 194 if version is False: 195 version = None 196 if parent is False: 197 parent = None 198 resource = super(Resource, cls).__new__(cls) 199 resource.realm = realm 200 resource.id = id 201 resource.version = version 202 resource.parent = parent 203 return resource
204
205 - def __call__(self, realm=False, id=False, version=False, parent=False):
206 """Create a new Resource using the current resource as a template. 207 208 Optional keyword arguments can be given to override `id` and 209 `version`. 210 """ 211 return Resource(self if realm is False else realm, id, version, parent)
212 213 # -- methods for retrieving children Resource identifiers 214
215 - def child(self, realm, id=False, version=False):
216 """Retrieve a child resource for a secondary `realm`. 217 218 Same as `__call__`, except that this one sets the parent to `self`. 219 220 >>> repr(Resource(None).child('attachment', 'file.txt')) 221 "<Resource u', attachment:file.txt'>" 222 """ 223 return Resource(realm, id, version, self)
224 225
226 -class ResourceSystem(Component):
227 """Resource identification and description manager. 228 229 This component makes the link between `Resource` identifiers and their 230 corresponding manager `Component`. 231 """ 232 233 resource_managers = ExtensionPoint(IResourceManager) 234
235 - def __init__(self):
236 self._resource_managers_map = None
237 238 # Public methods 239
240 - def get_resource_manager(self, realm):
241 """Return the component responsible for resources in the given `realm` 242 243 :param realm: the realm name 244 :return: a `Component` implementing `IResourceManager` or `None` 245 """ 246 # build a dict of realm keys to IResourceManager implementations 247 if not self._resource_managers_map: 248 map = {} 249 for manager in self.resource_managers: 250 for manager_realm in manager.get_resource_realms() or []: 251 map[manager_realm] = manager 252 self._resource_managers_map = map 253 return self._resource_managers_map.get(realm)
254
255 - def get_known_realms(self):
256 """Return a list of all the realm names of resource managers.""" 257 realms = [] 258 for manager in self.resource_managers: 259 for realm in manager.get_resource_realms() or []: 260 realms.append(realm) 261 return realms
262 263 264 # -- Utilities for manipulating resources in a generic way 265
266 -def get_resource_url(env, resource, href, **kwargs):
267 """Retrieve the canonical URL for the given resource. 268 269 This function delegates the work to the resource manager for that 270 resource if it implements a `get_resource_url` method, otherwise 271 reverts to simple '/realm/identifier' style URLs. 272 273 :param env: the `Environment` where `IResourceManager` components live 274 :param resource: the `Resource` object specifying the Trac resource 275 :param href: an `Href` object used for building the URL 276 277 Additional keyword arguments are translated as query parameters in 278 the URL. 279 280 >>> from trac.test import EnvironmentStub 281 >>> from trac.web.href import Href 282 >>> env = EnvironmentStub() 283 >>> href = Href('/trac.cgi') 284 >>> main = Resource('generic', 'Main') 285 >>> get_resource_url(env, main, href) 286 '/trac.cgi/generic/Main' 287 288 >>> get_resource_url(env, main(version=3), href) 289 '/trac.cgi/generic/Main?version=3' 290 291 >>> get_resource_url(env, main(version=3), href) 292 '/trac.cgi/generic/Main?version=3' 293 294 >>> get_resource_url(env, main(version=3), href, action='diff') 295 '/trac.cgi/generic/Main?action=diff&version=3' 296 297 >>> get_resource_url(env, main(version=3), href, action='diff', version=5) 298 '/trac.cgi/generic/Main?action=diff&version=5' 299 300 """ 301 manager = ResourceSystem(env).get_resource_manager(resource.realm) 302 if manager and hasattr(manager, 'get_resource_url'): 303 return manager.get_resource_url(resource, href, **kwargs) 304 args = {'version': resource.version} 305 args.update(kwargs) 306 return href(resource.realm, resource.id, **args)
307 308
309 -def get_resource_description(env, resource, format='default', **kwargs):
310 """Retrieve a standardized description for the given resource. 311 312 This function delegates the work to the resource manager for that 313 resource if it implements a `get_resource_description` method, 314 otherwise reverts to simple presentation of the realm and identifier 315 information. 316 317 :param env: the `Environment` where `IResourceManager` components live 318 :param resource: the `Resource` object specifying the Trac resource 319 :param format: which formats to use for the description 320 321 Additional keyword arguments can be provided and will be propagated 322 to resource manager that might make use of them (typically, a `context` 323 parameter for creating context dependent output). 324 325 >>> from trac.test import EnvironmentStub 326 >>> env = EnvironmentStub() 327 >>> main = Resource('generic', 'Main') 328 >>> get_resource_description(env, main) 329 u'generic:Main' 330 331 >>> get_resource_description(env, main(version=3)) 332 u'generic:Main' 333 334 >>> get_resource_description(env, main(version=3), format='summary') 335 u'generic:Main at version 3' 336 337 """ 338 manager = ResourceSystem(env).get_resource_manager(resource.realm) 339 if manager and hasattr(manager, 'get_resource_description'): 340 return manager.get_resource_description(resource, format, **kwargs) 341 name = u'%s:%s' % (resource.realm, resource.id) 342 if format == 'summary': 343 name = _('%(name)s at version %(version)s', 344 name=name, version=resource.version) 345 return name
346 347
348 -def get_resource_name(env, resource):
349 return get_resource_description(env, resource)
350 351
352 -def get_resource_shortname(env, resource):
353 return get_resource_description(env, resource, 'compact')
354 355
356 -def get_resource_summary(env, resource):
357 return get_resource_description(env, resource, 'summary')
358 359
360 -def get_relative_resource(resource, path=''):
361 """Build a Resource relative to a reference resource. 362 363 :param path: path leading to another resource within the same realm. 364 """ 365 if path in (None, '', '.'): 366 return resource 367 else: 368 base = unicode(resource.id if path[0] != '/' else '').split('/') 369 for comp in path.split('/'): 370 if comp == '..': 371 if base: 372 base.pop() 373 elif comp and comp != '.': 374 base.append(comp) 375 return resource(id='/'.join(base) if base else None)
376 377
378 -def get_relative_url(env, resource, href, path='', **kwargs):
379 """Build an URL relative to a resource given as reference. 380 381 :param path: path leading to another resource within the same realm. 382 383 >>> from trac.test import EnvironmentStub 384 >>> env = EnvironmentStub() 385 >>> from trac.web.href import Href 386 >>> href = Href('/trac.cgi') 387 >>> main = Resource('wiki', 'Main', version=3) 388 389 Without parameters, return the canonical URL for the resource, like 390 `get_resource_url` does. 391 392 >>> get_relative_url(env, main, href) 393 '/trac.cgi/wiki/Main?version=3' 394 395 Paths are relative to the given resource: 396 397 >>> get_relative_url(env, main, href, '.') 398 '/trac.cgi/wiki/Main?version=3' 399 400 >>> get_relative_url(env, main, href, './Sub') 401 '/trac.cgi/wiki/Main/Sub' 402 403 >>> get_relative_url(env, main, href, './Sub/Infra') 404 '/trac.cgi/wiki/Main/Sub/Infra' 405 406 >>> get_relative_url(env, main, href, './Sub/') 407 '/trac.cgi/wiki/Main/Sub' 408 409 >>> mainsub = main(id='Main/Sub') 410 >>> get_relative_url(env, mainsub, href, '..') 411 '/trac.cgi/wiki/Main' 412 413 >>> get_relative_url(env, main, href, '../Other') 414 '/trac.cgi/wiki/Other' 415 416 References always stay within the current resource realm: 417 418 >>> get_relative_url(env, mainsub, href, '../..') 419 '/trac.cgi/wiki' 420 421 >>> get_relative_url(env, mainsub, href, '../../..') 422 '/trac.cgi/wiki' 423 424 >>> get_relative_url(env, mainsub, href, '/toplevel') 425 '/trac.cgi/wiki/toplevel' 426 427 Extra keyword arguments are forwarded as query parameters: 428 429 >>> get_relative_url(env, main, href, action='diff') 430 '/trac.cgi/wiki/Main?action=diff&version=3' 431 432 """ 433 return get_resource_url(env, get_relative_resource(resource, path), 434 href, **kwargs)
435 436 452 453
454 -def resource_exists(env, resource):
455 """Checks for resource existence without actually instantiating a model. 456 457 :return: `True` if the resource exists, `False` if it doesn't 458 and `None` in case no conclusion could be made 459 (i.e. when `IResourceManager.resource_exists` is not 460 implemented). 461 462 >>> from trac.test import EnvironmentStub 463 >>> env = EnvironmentStub() 464 465 >>> resource_exists(env, Resource('dummy-realm', 'dummy-id')) is None 466 True 467 >>> resource_exists(env, Resource('dummy-realm')) 468 False 469 470 """ 471 manager = ResourceSystem(env).get_resource_manager(resource.realm) 472 if manager and hasattr(manager, 'resource_exists'): 473 return manager.resource_exists(resource) 474 elif resource.id is None: 475 return False
476