Source

trac-ticketlinks / trac / resource.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# -*- coding: utf-8 -*-
#
# Copyright (C) 2006-2008 Edgewall Software
# Copyright (C) 2006-2007 Alec Thomas <alec@swapoff.org>
# Copyright (C) 2007 Christian Boos <cboos@neuf.fr>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.org/wiki/TracLicense.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/log/.
#
# Author: Christian Boos <cboos@neuf.fr>
#         Alec Thomas <alec@swapoff.org>

from trac.core import *
from trac.util.compat import reversed
from trac.util.translation import _


class ResourceNotFound(TracError):
    """Thrown when a non-existent resource is requested"""


class IResourceManager(Interface):

    def get_resource_realms():
        """Return resource realms managed by the component.

        :rtype: `basestring` generator
        """

    def get_resource_url(resource, href, **kwargs):
        """Return the canonical URL for displaying the given resource.

        :param resource: a `Resource`
        :param href: an `Href` used for creating the URL

        Note that if there's no special rule associated to this realm for
        creating URLs (i.e. the standard convention of using realm/id applies),
        then it's OK to not define this method.
        """

    def get_resource_description(resource, format='default', context=None,
                                 **kwargs):
        """Return a string representation of the resource, according to the
        `format`.

        :param resource: the `Resource` to describe
        :param format: the kind of description wanted. Typical formats are:
                       `'default'`, `'compact'` or `'summary'`.
        :param context: an optional rendering context to allow rendering rich
                        output (like markup containing links)
        :type context: `Context`

        Additional keyword arguments can be given as extra information for
        some formats. 

        For example, the ticket with the id 123 is represented as:
         - `'#123'` in `'compact'` format,
         - `'Ticket #123'` for the `default` format.
         - `'Ticket #123 (closed defect): This is the summary'` for the
           `'summary'` format

        Note that it is also OK to not define this method if there's no
        special way to represent the resource, in which case the standard
        representations 'realm:id' (in compact mode) or 'Realm id' (in
        default mode) will be used.
        """


class Resource(object):
    """Resource identifier.

    This specifies as precisely as possible *which* resource from a Trac
    environment is manipulated.

    A resource is identified by:
    (- a `project` identifier) 0.12?
     - a `realm` (a string like `'wiki'` or `'ticket'`)
     - an `id`, which uniquely identifies a resource within its realm.
       If the `id` information is not set, then the resource represents
       the realm as a whole.
     - an optional `version` information.
       If `version` is `None`, this refers by convention to the latest
       version of the resource.

    Some generic and commonly used rendering methods are associated as well
    to the Resource object. Those properties and methods actually delegate
    the real work to the Resource's manager.
    """

    __slots__ = ('realm', 'id', 'version', 'parent')

    def __repr__(self):
        if self.realm is None:
            return '<Resource>'
        path = []
        r = self
        while r:
            name = r.realm
            if r.id:
                name += ':' + unicode(r.id) # id can be numerical
            if r.version is not None:
                name += '@' + unicode(r.version)
            path.append(name)
            r = r.parent
        return '<Resource %r>' % (', '.join(reversed(path)))

    def __eq__(self, other):
        return self.realm == other.realm and \
               self.id == other.id and \
               self.version == other.version and \
               self.parent == other.parent

    def __hash__(self):
        """Hash this resource descriptor, including its hierarchy."""
        path = ()
        current = self
        while current:
            path += (self.realm, self.id, self.version)
            current = current.parent
        return hash(path)

    # -- methods for creating other Resource identifiers

    def __new__(cls, resource_or_realm=None, id=False, version=False,
                parent=False):
        """Create a new Resource object from a specification.

        :param resource_or_realm: this can be either:
           - a `Resource`, which is then used as a base for making a copy
           - a `basestring`, used to specify a `realm`
        :param id: the resource identifier
        :param version: the version or `None` for indicating the latest version

        >>> main = Resource('wiki', 'WikiStart')
        >>> repr(main)
        "<Resource u'wiki:WikiStart'>"
        
        >>> Resource(main) is main
        True

        >>> main3 = Resource(main, version=3)
        >>> repr(main3)
        "<Resource u'wiki:WikiStart@3'>"

        >>> main0 = main3(version=0)
        >>> repr(main0)
        "<Resource u'wiki:WikiStart@0'>"

        In a copy, if `id` is overriden, then the original `version` value
        will not be reused.

        >>> repr(Resource(main3, id="WikiEnd"))
        "<Resource u'wiki:WikiEnd'>"

        >>> repr(Resource(None))
        '<Resource>'
        """
        realm = resource_or_realm
        if isinstance(resource_or_realm, Resource):
            if id is False and version is False and parent is False:
                return resource_or_realm
            else: # copy and override
                realm = resource_or_realm.realm
            if id is False:
                id = resource_or_realm.id
            if version is False:
                if id == resource_or_realm.id:
                    version = resource_or_realm.version # could be 0...
                else:
                    version = None
            if parent is False:
                parent = resource_or_realm.parent
        else:
            if id is False:
                id = None
            if version is False:
                version = None
            if parent is False:
                parent = None
        resource = super(Resource, cls).__new__(cls)
        resource.realm = realm
        resource.id = id
        resource.version = version
        resource.parent = parent
        return resource


    def __call__(self, realm=False, id=False, version=False, parent=False):
        """Create a new Resource using the current resource as a template.

        Optional keyword arguments can be given to override `id` and
        `version`.
        """
        return Resource(realm is False and self or realm, id, version, parent)

    # -- methods for retrieving children Resource identifiers
    
    def child(self, realm, id=False, version=False):
        """Retrieve a child resource for a secondary `realm`.

        Same as `__call__`, except that this one sets the parent to `self`.
        """
        return self.__call__(realm, id, version, self)
    


class ResourceSystem(Component):
    """Resource identification and description.

    This component makes the link between `Resource` identifiers and their
    corresponding manager `Component`.

    """

    resource_managers = ExtensionPoint(IResourceManager)

    def __init__(self):
        self._resource_managers_map = None

    # Public methods

    def get_resource_manager(self, realm):
        """Return the component responsible for resources in the given `realm`

        :param realm: the realm name
        :return: a `Component` implementing `IResourceManager` or `None`
        """
        # build a dict of realm keys to IResourceManager implementations
        if not self._resource_managers_map:
            map = {}
            for manager in self.resource_managers:
                for manager_realm in manager.get_resource_realms():
                    map[manager_realm] = manager
            self._resource_managers_map = map
        return self._resource_managers_map.get(realm)

    def get_known_realms(self):
        """Return a list of all the realm names of resource managers."""
        realms = []
        for manager in self.resource_managers:
            for realm in manager.get_resource_realms():
                realms.append(realm)
        return realms


# -- Utilities for manipulating resources in a generic way

def get_resource_url(env, resource, href, **kwargs):
    """Retrieve the canonical URL for the given resource.

    This function delegates the work to the resource manager for that
    resource if it implements a `get_resource_url` method, otherwise
    reverts to simple '/realm/identifier' style URLs.
    
    :param env: the `Environment` where `IResourceManager` components live
    :param resource: the `Resource` object specifying the Trac resource
    :param href: an `Href` object used for building the URL

    Additional keyword arguments are translated as query paramaters in the URL.

    >>> from trac.test import EnvironmentStub
    >>> from trac.web.href import Href
    >>> env = EnvironmentStub()
    >>> href = Href('/trac.cgi')
    >>> main = Resource('generic', 'Main')
    >>> get_resource_url(env, main, href)
    '/trac.cgi/generic/Main'
    
    >>> get_resource_url(env, main(version=3), href)
    '/trac.cgi/generic/Main?version=3'
    
    >>> get_resource_url(env, main(version=3), href)
    '/trac.cgi/generic/Main?version=3'
    
    >>> get_resource_url(env, main(version=3), href, action='diff')
    '/trac.cgi/generic/Main?action=diff&version=3'
    
    >>> get_resource_url(env, main(version=3), href, action='diff', version=5)
    '/trac.cgi/generic/Main?action=diff&version=5'
    
    """
    manager = ResourceSystem(env).get_resource_manager(resource.realm)
    if not manager or not hasattr(manager, 'get_resource_url'):
        args = {'version': resource.version}
        args.update(kwargs)
        return href(resource.realm, resource.id, **args)
    else:
        return manager.get_resource_url(resource, href, **kwargs)

def get_resource_description(env, resource, format='default', **kwargs):
    """Retrieve a standardized description for the given resource.

    This function delegates the work to the resource manager for that
    resource if it implements a `get_resource_description` method,
    otherwise reverts to simple presentation of the realm and identifier
    information.
    
    :param env: the `Environment` where `IResourceManager` components live
    :param resource: the `Resource` object specifying the Trac resource
    :param format: which formats to use for the description

    Additional keyword arguments can be provided and will be propagated
    to resource manager that might make use of them (typically, a `context`
    parameter for creating context dependent output).

    >>> from trac.test import EnvironmentStub
    >>> env = EnvironmentStub()
    >>> main = Resource('generic', 'Main')
    >>> get_resource_description(env, main)
    'generic:Main'
    
    >>> get_resource_description(env, main(version=3))
    'generic:Main'

    >>> get_resource_description(env, main(version=3), format='summary')
    'generic:Main at version 3'
    
    """
    manager = ResourceSystem(env).get_resource_manager(resource.realm)
    if not manager or not hasattr(manager, 'get_resource_description'):
        name = '%s:%s' % (resource.realm, resource.id)
        if format == 'summary':
            name += _(' at version %(version)s', version=resource.version)
        return name
    else:
        return manager.get_resource_description(resource, format, **kwargs)

def get_resource_name(env, resource):
    return get_resource_description(env, resource)

def get_resource_shortname(env, resource):
    return get_resource_description(env, resource, 'compact')

def get_resource_summary(env, resource):
    return get_resource_description(env, resource, 'summary')

def get_relative_url(env, resource, href, path='', **kwargs):
    """Build an URL relative to a resource given as reference.

    :param path: path leading to another resource within the same realm.

    >>> from trac.test import EnvironmentStub
    >>> env = EnvironmentStub()
    >>> from trac.web.href import Href
    >>> href = Href('/trac.cgi')
    >>> main = Resource('wiki', 'Main', version=3)

    Without parameters, return the canonical URL for the resource, like
    `get_resource_url` does.

    >>> get_relative_url(env, main, href)
    '/trac.cgi/wiki/Main?version=3'

    Paths are relative to the given resource:

    >>> get_relative_url(env, main, href, '.')
    '/trac.cgi/wiki/Main?version=3'

    >>> get_relative_url(env, main, href, './Sub')
    '/trac.cgi/wiki/Main/Sub'

    >>> get_relative_url(env, main, href, './Sub/Infra')
    '/trac.cgi/wiki/Main/Sub/Infra'

    >>> get_relative_url(env, main, href, './Sub/')
    '/trac.cgi/wiki/Main/Sub'

    >>> mainsub = main(id='Main/Sub')
    >>> get_relative_url(env, mainsub, href, '..')
    '/trac.cgi/wiki/Main'

    >>> get_relative_url(env, main, href, '../Other')
    '/trac.cgi/wiki/Other'

    References always stay within the current resource realm:

    >>> get_relative_url(env, mainsub, href, '../..')
    '/trac.cgi/wiki'

    >>> get_relative_url(env, mainsub, href, '../../..')
    '/trac.cgi/wiki'

    >>> get_relative_url(env, mainsub, href, '/toplevel')
    '/trac.cgi/wiki/toplevel'

    Extra keyword arguments are forwarded as query parameters:

    >>> get_relative_url(env, main, href, action='diff')
    '/trac.cgi/wiki/Main?action=diff&version=3'

    """
    if path in (None, '', '.'):
        return get_resource_url(env, resource, href, **kwargs)
    else:
        base = unicode(path[0] != '/' and resource.id or '').split('/')
        for comp in path.split('/'):
            if comp in ('.', ''):
                continue
            elif comp == '..':
                if base:
                    base.pop()
            elif comp:
                base.append(comp)
        return get_resource_url(env, resource(id=base and '/'.join(base) or
                                              None), href, **kwargs)

def render_resource_link(env, context, resource, format='default'):
    """Utility for generating a link `Element` to the given resource.

    Some component manager may directly use an extra `context` parameter
    in order to directly generate rich content. Otherwise, the textual output
    is wrapped in a link to the resource.
    """
    from genshi.builder import Element, tag
    link = get_resource_description(env, resource, format, context=context)
    if not isinstance(link, Element):
        link = tag.a(link, href=get_resource_url(env, resource, context.href))
    return link
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.