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