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.html import Fragment, tag
21 from trac.util.presentation import classes
22 from trac.util.translation import _
23
24
26 """Thrown when attempting to insert an existing resource."""
27
28
30 """Thrown when a non-existent resource is requested"""
31
32
34
36 """Return resource realms managed by the component.
37
38 :rtype: `basestring` generator
39 """
40
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
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
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
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
124
130
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
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:
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
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
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
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
236 self._resource_managers_map = None
237
238
239
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
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
262
263
264
265
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
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
350
351
354
355
358
359
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
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
438 """Utility for generating a link `Element` to the given resource.
439
440 Some component manager may directly use an extra `context` parameter
441 in order to directly generate rich content. Otherwise, the textual output
442 is wrapped in a link to the resource.
443 """
444 link = get_resource_description(env, resource, format, context=context)
445 if not isinstance(link, Fragment):
446 missing = resource_exists(env, resource) is False
447 link = tag.a(link, class_=classes(resource.realm, missing=missing),
448 href=get_resource_url(env, resource, context.href),
449 rel='nofollow' if missing else None)
450
451 return link
452
453
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