~brian-sidebotham/wxwidgets-cmake/wxpython-2.9.4

« back to all changes in this revision

Viewing changes to wxPython/wx/tools/Editra/src/extern/pkg_resources.py

  • Committer: Brian Sidebotham
  • Date: 2013-08-03 14:30:08 UTC
  • Revision ID: brian.sidebotham@gmail.com-20130803143008-c7806tkych1tp6fc
Initial import into Bazaar

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""Package resource API
 
2
--------------------
 
3
 
 
4
A resource is a logical file contained within a package, or a logical
 
5
subdirectory thereof.  The package resource API expects resource names
 
6
to have their path parts separated with ``/``, *not* whatever the local
 
7
path separator is.  Do not use os.path operations to manipulate resource
 
8
names being passed into the API.
 
9
 
 
10
The package resource API is designed to work with normal filesystem packages,
 
11
.egg files, and unpacked .egg files.  It can also work in a limited way with
 
12
.zip files and with custom PEP 302 loaders that support the ``get_data()``
 
13
method.
 
14
"""
 
15
 
 
16
import sys, os, zipimport, time, re, imp, new
 
17
 
 
18
# Fix for Python 2.6 deprecation warning
 
19
try:
 
20
    ImmutableSet = frozenset
 
21
except NameError:
 
22
    from sets import ImmutableSet
 
23
 
 
24
from os import utime, rename, unlink    # capture these to bypass sandboxing
 
25
from os import open as os_open
 
26
 
 
27
def get_supported_platform():
 
28
    """Return this platform's maximum compatible version.
 
29
 
 
30
    distutils.util.get_platform() normally reports the minimum version
 
31
    of Mac OS X that would be required to *use* extensions produced by
 
32
    distutils.  But what we want when checking compatibility is to know the
 
33
    version of Mac OS X that we are *running*.  To allow usage of packages that
 
34
    explicitly require a newer version of Mac OS X, we must also know the
 
35
    current version of the OS.
 
36
 
 
37
    If this condition occurs for any other platform with a version in its
 
38
    platform strings, this function should be extended accordingly.
 
39
    """
 
40
    plat = get_build_platform(); m = macosVersionString.match(plat)
 
41
    if m is not None and sys.platform == "darwin":
 
42
        try:
 
43
            plat = 'macosx-%s-%s' % ('.'.join(_macosx_vers()[:2]), m.group(3))
 
44
        except ValueError:
 
45
            pass    # not Mac OS X
 
46
    return plat
 
47
 
 
48
__all__ = [
 
49
    # Basic resource access and distribution/entry point discovery
 
50
    'require', 'run_script', 'get_provider',  'get_distribution',
 
51
    'load_entry_point', 'get_entry_map', 'get_entry_info', 'iter_entry_points',
 
52
    'resource_string', 'resource_stream', 'resource_filename',
 
53
    'resource_listdir', 'resource_exists', 'resource_isdir',
 
54
 
 
55
    # Environmental control
 
56
    'declare_namespace', 'working_set', 'add_activation_listener',
 
57
    'find_distributions', 'set_extraction_path', 'cleanup_resources',
 
58
    'get_default_cache',
 
59
 
 
60
    # Primary implementation classes
 
61
    'Environment', 'WorkingSet', 'ResourceManager',
 
62
    'Distribution', 'Requirement', 'EntryPoint',
 
63
 
 
64
    # Exceptions
 
65
    'ResolutionError','VersionConflict','DistributionNotFound','UnknownExtra',
 
66
    'ExtractionError',
 
67
 
 
68
    # Parsing functions and string utilities
 
69
    'parse_requirements', 'parse_version', 'safe_name', 'safe_version',
 
70
    'get_platform', 'compatible_platforms', 'yield_lines', 'split_sections',
 
71
    'safe_extra', 'to_filename',
 
72
 
 
73
    # filesystem utilities
 
74
    'ensure_directory', 'normalize_path',
 
75
 
 
76
    # Distribution "precedence" constants
 
77
    'EGG_DIST', 'BINARY_DIST', 'SOURCE_DIST', 'CHECKOUT_DIST', 'DEVELOP_DIST',
 
78
 
 
79
    # "Provider" interfaces, implementations, and registration/lookup APIs
 
80
    'IMetadataProvider', 'IResourceProvider', 'FileMetadata',
 
81
    'PathMetadata', 'EggMetadata', 'EmptyProvider', 'empty_provider',
 
82
    'NullProvider', 'EggProvider', 'DefaultProvider', 'ZipProvider',
 
83
    'register_finder', 'register_namespace_handler', 'register_loader_type',
 
84
    'fixup_namespace_packages', 'get_importer',
 
85
 
 
86
    # Deprecated/backward compatibility only
 
87
    'run_main', 'AvailableDistributions',
 
88
]
 
89
class ResolutionError(Exception):
 
90
    """Abstract base for dependency resolution errors"""
 
91
    def __repr__(self): return self.__class__.__name__+repr(self.args)
 
92
 
 
93
class VersionConflict(ResolutionError):
 
94
    """An already-installed version conflicts with the requested version"""
 
95
 
 
96
class DistributionNotFound(ResolutionError):
 
97
    """A requested distribution was not found"""
 
98
 
 
99
class UnknownExtra(ResolutionError):
 
100
    """Distribution doesn't have an "extra feature" of the given name"""
 
101
_provider_factories = {}
 
102
PY_MAJOR = sys.version[:3]
 
103
EGG_DIST    = 3
 
104
BINARY_DIST = 2
 
105
SOURCE_DIST = 1
 
106
CHECKOUT_DIST = 0
 
107
DEVELOP_DIST = -1
 
108
 
 
109
def register_loader_type(loader_type, provider_factory):
 
110
    """Register `provider_factory` to make providers for `loader_type`
 
111
 
 
112
    `loader_type` is the type or class of a PEP 302 ``module.__loader__``,
 
113
    and `provider_factory` is a function that, passed a *module* object,
 
114
    returns an ``IResourceProvider`` for that module.
 
115
    """
 
116
    _provider_factories[loader_type] = provider_factory
 
117
 
 
118
def get_provider(moduleOrReq):
 
119
    """Return an IResourceProvider for the named module or requirement"""
 
120
    if isinstance(moduleOrReq,Requirement):
 
121
        return working_set.find(moduleOrReq) or require(str(moduleOrReq))[0]
 
122
    try:
 
123
        module = sys.modules[moduleOrReq]
 
124
    except KeyError:
 
125
        __import__(moduleOrReq)
 
126
        module = sys.modules[moduleOrReq]
 
127
    loader = getattr(module, '__loader__', None)
 
128
    return _find_adapter(_provider_factories, loader)(module)
 
129
 
 
130
def _macosx_vers(_cache=[]):
 
131
    if not _cache:
 
132
        info = os.popen('/usr/bin/sw_vers').read().splitlines()
 
133
        for line in info:
 
134
            key, value = line.split(None, 1)
 
135
            if key == 'ProductVersion:':
 
136
                _cache.append(value.strip().split("."))
 
137
                break
 
138
        else:
 
139
            raise ValueError, "What?!"
 
140
    return _cache[0]
 
141
 
 
142
def _macosx_arch(machine):
 
143
    return {'PowerPC':'ppc', 'Power_Macintosh':'ppc'}.get(machine,machine)
 
144
 
 
145
def get_build_platform():
 
146
    """Return this platform's string for platform-specific distributions
 
147
 
 
148
    XXX Currently this is the same as ``distutils.util.get_platform()``, but it
 
149
    needs some hacks for Linux and Mac OS X.
 
150
    """
 
151
    from distutils.util import get_platform
 
152
    plat = get_platform()
 
153
    if sys.platform == "darwin" and not plat.startswith('macosx-'):
 
154
        try:
 
155
            version = _macosx_vers()
 
156
            machine = os.uname()[4].replace(" ", "_")
 
157
            return "macosx-%d.%d-%s" % (int(version[0]), int(version[1]),
 
158
                _macosx_arch(machine))
 
159
        except ValueError:
 
160
            # if someone is running a non-Mac darwin system, this will fall
 
161
            # through to the default implementation
 
162
            pass
 
163
    return plat
 
164
 
 
165
macosVersionString = re.compile(r"macosx-(\d+)\.(\d+)-(.*)")
 
166
darwinVersionString = re.compile(r"darwin-(\d+)\.(\d+)\.(\d+)-(.*)")
 
167
get_platform = get_build_platform   # XXX backward compat
 
168
 
 
169
 
 
170
 
 
171
def compatible_platforms(provided,required):
 
172
    """Can code for the `provided` platform run on the `required` platform?
 
173
 
 
174
    Returns true if either platform is ``None``, or the platforms are equal.
 
175
 
 
176
    XXX Needs compatibility checks for Linux and other unixy OSes.
 
177
    """
 
178
    if provided is None or required is None or provided==required:
 
179
        return True     # easy case
 
180
 
 
181
    # Mac OS X special cases
 
182
    reqMac = macosVersionString.match(required)
 
183
    if reqMac:
 
184
        provMac = macosVersionString.match(provided)
 
185
 
 
186
        # is this a Mac package?
 
187
        if not provMac:
 
188
            # this is backwards compatibility for packages built before
 
189
            # setuptools 0.6. All packages built after this point will
 
190
            # use the new macosx designation.
 
191
            provDarwin = darwinVersionString.match(provided)
 
192
            if provDarwin:
 
193
                dversion = int(provDarwin.group(1))
 
194
                macosversion = "%s.%s" % (reqMac.group(1), reqMac.group(2))
 
195
                if dversion == 7 and macosversion >= "10.3" or \
 
196
                    dversion == 8 and macosversion >= "10.4":
 
197
 
 
198
                    #import warnings
 
199
                    #warnings.warn("Mac eggs should be rebuilt to "
 
200
                    #    "use the macosx designation instead of darwin.",
 
201
                    #    category=DeprecationWarning)
 
202
                    return True
 
203
            return False    # egg isn't macosx or legacy darwin
 
204
 
 
205
        # are they the same major version and machine type?
 
206
        if provMac.group(1) != reqMac.group(1) or \
 
207
            provMac.group(3) != reqMac.group(3):
 
208
            return False
 
209
 
 
210
 
 
211
 
 
212
        # is the required OS major update >= the provided one?
 
213
        if int(provMac.group(2)) > int(reqMac.group(2)):
 
214
            return False
 
215
 
 
216
        return True
 
217
 
 
218
    # XXX Linux and other platforms' special cases should go here
 
219
    return False
 
220
 
 
221
 
 
222
def run_script(dist_spec, script_name):
 
223
    """Locate distribution `dist_spec` and run its `script_name` script"""
 
224
    ns = sys._getframe(1).f_globals
 
225
    name = ns['__name__']
 
226
    ns.clear()
 
227
    ns['__name__'] = name
 
228
    require(dist_spec)[0].run_script(script_name, ns)
 
229
 
 
230
run_main = run_script   # backward compatibility
 
231
 
 
232
def get_distribution(dist):
 
233
    """Return a current distribution object for a Requirement or string"""
 
234
    if isinstance(dist,basestring): dist = Requirement.parse(dist)
 
235
    if isinstance(dist,Requirement): dist = get_provider(dist)
 
236
    if not isinstance(dist,Distribution):
 
237
        raise TypeError("Expected string, Requirement, or Distribution", dist)
 
238
    return dist
 
239
 
 
240
def load_entry_point(dist, group, name):
 
241
    """Return `name` entry point of `group` for `dist` or raise ImportError"""
 
242
    return get_distribution(dist).load_entry_point(group, name)
 
243
 
 
244
def get_entry_map(dist, group=None):
 
245
    """Return the entry point map for `group`, or the full entry map"""
 
246
    return get_distribution(dist).get_entry_map(group)
 
247
 
 
248
def get_entry_info(dist, group, name):
 
249
    """Return the EntryPoint object for `group`+`name`, or ``None``"""
 
250
    return get_distribution(dist).get_entry_info(group, name)
 
251
 
 
252
 
 
253
class IMetadataProvider:
 
254
 
 
255
    def has_metadata(name):
 
256
        """Does the package's distribution contain the named metadata?"""
 
257
 
 
258
    def get_metadata(name):
 
259
        """The named metadata resource as a string"""
 
260
 
 
261
    def get_metadata_lines(name):
 
262
        """Yield named metadata resource as list of non-blank non-comment lines
 
263
 
 
264
       Leading and trailing whitespace is stripped from each line, and lines
 
265
       with ``#`` as the first non-blank character are omitted."""
 
266
 
 
267
    def metadata_isdir(name):
 
268
        """Is the named metadata a directory?  (like ``os.path.isdir()``)"""
 
269
 
 
270
    def metadata_listdir(name):
 
271
        """List of metadata names in the directory (like ``os.listdir()``)"""
 
272
 
 
273
    def run_script(script_name, namespace):
 
274
        """Execute the named script in the supplied namespace dictionary"""
 
275
 
 
276
 
 
277
 
 
278
 
 
279
 
 
280
 
 
281
 
 
282
 
 
283
 
 
284
 
 
285
 
 
286
 
 
287
 
 
288
 
 
289
 
 
290
 
 
291
 
 
292
 
 
293
 
 
294
class IResourceProvider(IMetadataProvider):
 
295
    """An object that provides access to package resources"""
 
296
 
 
297
    def get_resource_filename(manager, resource_name):
 
298
        """Return a true filesystem path for `resource_name`
 
299
 
 
300
        `manager` must be an ``IResourceManager``"""
 
301
 
 
302
    def get_resource_stream(manager, resource_name):
 
303
        """Return a readable file-like object for `resource_name`
 
304
 
 
305
        `manager` must be an ``IResourceManager``"""
 
306
 
 
307
    def get_resource_string(manager, resource_name):
 
308
        """Return a string containing the contents of `resource_name`
 
309
 
 
310
        `manager` must be an ``IResourceManager``"""
 
311
 
 
312
    def has_resource(resource_name):
 
313
        """Does the package contain the named resource?"""
 
314
 
 
315
    def resource_isdir(resource_name):
 
316
        """Is the named resource a directory?  (like ``os.path.isdir()``)"""
 
317
 
 
318
    def resource_listdir(resource_name):
 
319
        """List of resource names in the directory (like ``os.listdir()``)"""
 
320
 
 
321
 
 
322
 
 
323
 
 
324
 
 
325
 
 
326
 
 
327
 
 
328
 
 
329
 
 
330
 
 
331
 
 
332
 
 
333
 
 
334
 
 
335
class WorkingSet(object):
 
336
    """A collection of active distributions on sys.path (or a similar list)"""
 
337
 
 
338
    def __init__(self, entries=None):
 
339
        """Create working set from list of path entries (default=sys.path)"""
 
340
        self.entries = []
 
341
        self.entry_keys = {}
 
342
        self.by_key = {}
 
343
        self.callbacks = []
 
344
 
 
345
        if entries is None:
 
346
            entries = sys.path
 
347
 
 
348
        for entry in entries:
 
349
            self.add_entry(entry)
 
350
 
 
351
 
 
352
    def add_entry(self, entry):
 
353
        """Add a path item to ``.entries``, finding any distributions on it
 
354
 
 
355
        ``find_distributions(entry,False)`` is used to find distributions
 
356
        corresponding to the path entry, and they are added.  `entry` is
 
357
        always appended to ``.entries``, even if it is already present.
 
358
        (This is because ``sys.path`` can contain the same value more than
 
359
        once, and the ``.entries`` of the ``sys.path`` WorkingSet should always
 
360
        equal ``sys.path``.)
 
361
        """
 
362
        self.entry_keys.setdefault(entry, [])
 
363
        self.entries.append(entry)
 
364
        for dist in find_distributions(entry, True):
 
365
            self.add(dist, entry, False)
 
366
 
 
367
 
 
368
    def __contains__(self,dist):
 
369
        """True if `dist` is the active distribution for its project"""
 
370
        return self.by_key.get(dist.key) == dist
 
371
 
 
372
 
 
373
 
 
374
 
 
375
 
 
376
    def find(self, req):
 
377
        """Find a distribution matching requirement `req`
 
378
 
 
379
        If there is an active distribution for the requested project, this
 
380
        returns it as long as it meets the version requirement specified by
 
381
        `req`.  But, if there is an active distribution for the project and it
 
382
        does *not* meet the `req` requirement, ``VersionConflict`` is raised.
 
383
        If there is no active distribution for the requested project, ``None``
 
384
        is returned.
 
385
        """
 
386
        dist = self.by_key.get(req.key)
 
387
        if dist is not None and dist not in req:
 
388
            raise VersionConflict(dist,req)     # XXX add more info
 
389
        else:
 
390
            return dist
 
391
 
 
392
    def iter_entry_points(self, group, name=None):
 
393
        """Yield entry point objects from `group` matching `name`
 
394
 
 
395
        If `name` is None, yields all entry points in `group` from all
 
396
        distributions in the working set, otherwise only ones matching
 
397
        both `group` and `name` are yielded (in distribution order).
 
398
        """
 
399
        for dist in self:
 
400
            entries = dist.get_entry_map(group)
 
401
            if name is None:
 
402
                for ep in entries.values():
 
403
                    yield ep
 
404
            elif name in entries:
 
405
                yield entries[name]
 
406
 
 
407
    def run_script(self, requires, script_name):
 
408
        """Locate distribution for `requires` and run `script_name` script"""
 
409
        ns = sys._getframe(1).f_globals
 
410
        name = ns['__name__']
 
411
        ns.clear()
 
412
        ns['__name__'] = name
 
413
        self.require(requires)[0].run_script(script_name, ns)
 
414
 
 
415
 
 
416
 
 
417
    def __iter__(self):
 
418
        """Yield distributions for non-duplicate projects in the working set
 
419
 
 
420
        The yield order is the order in which the items' path entries were
 
421
        added to the working set.
 
422
        """
 
423
        seen = {}
 
424
        for item in self.entries:
 
425
            for key in self.entry_keys[item]:
 
426
                if key not in seen:
 
427
                    seen[key]=1
 
428
                    yield self.by_key[key]
 
429
 
 
430
    def add(self, dist, entry=None, insert=True):
 
431
        """Add `dist` to working set, associated with `entry`
 
432
 
 
433
        If `entry` is unspecified, it defaults to the ``.location`` of `dist`.
 
434
        On exit from this routine, `entry` is added to the end of the working
 
435
        set's ``.entries`` (if it wasn't already present).
 
436
 
 
437
        `dist` is only added to the working set if it's for a project that
 
438
        doesn't already have a distribution in the set.  If it's added, any
 
439
        callbacks registered with the ``subscribe()`` method will be called.
 
440
        """
 
441
        if insert:
 
442
            dist.insert_on(self.entries, entry)
 
443
 
 
444
        if entry is None:
 
445
            entry = dist.location
 
446
        keys = self.entry_keys.setdefault(entry,[])
 
447
        keys2 = self.entry_keys.setdefault(dist.location,[])
 
448
        if dist.key in self.by_key:
 
449
            return      # ignore hidden distros
 
450
 
 
451
        self.by_key[dist.key] = dist
 
452
        if dist.key not in keys:
 
453
            keys.append(dist.key)
 
454
        if dist.key not in keys2:
 
455
            keys2.append(dist.key)
 
456
        self._added_new(dist)
 
457
 
 
458
    def resolve(self, requirements, env=None, installer=None):
 
459
        """List all distributions needed to (recursively) meet `requirements`
 
460
 
 
461
        `requirements` must be a sequence of ``Requirement`` objects.  `env`,
 
462
        if supplied, should be an ``Environment`` instance.  If
 
463
        not supplied, it defaults to all distributions available within any
 
464
        entry or distribution in the working set.  `installer`, if supplied,
 
465
        will be invoked with each requirement that cannot be met by an
 
466
        already-installed distribution; it should return a ``Distribution`` or
 
467
        ``None``.
 
468
        """
 
469
 
 
470
        requirements = list(requirements)[::-1]  # set up the stack
 
471
        processed = {}  # set of processed requirements
 
472
        best = {}  # key -> dist
 
473
        to_activate = []
 
474
 
 
475
        while requirements:
 
476
            req = requirements.pop(0)   # process dependencies breadth-first
 
477
            if req in processed:
 
478
                # Ignore cyclic or redundant dependencies
 
479
                continue
 
480
            dist = best.get(req.key)
 
481
            if dist is None:
 
482
                # Find the best distribution and add it to the map
 
483
                dist = self.by_key.get(req.key)
 
484
                if dist is None:
 
485
                    if env is None:
 
486
                        env = Environment(self.entries)
 
487
                    dist = best[req.key] = env.best_match(req, self, installer)
 
488
                    if dist is None:
 
489
                        raise DistributionNotFound(req)  # XXX put more info here
 
490
                to_activate.append(dist)
 
491
            if dist not in req:
 
492
                # Oops, the "best" so far conflicts with a dependency
 
493
                raise VersionConflict(dist,req) # XXX put more info here
 
494
            requirements.extend(dist.requires(req.extras)[::-1])
 
495
            processed[req] = True
 
496
 
 
497
        return to_activate    # return list of distros to activate
 
498
 
 
499
    def find_plugins(self,
 
500
        plugin_env, full_env=None, installer=None, fallback=True
 
501
    ):
 
502
        """Find all activatable distributions in `plugin_env`
 
503
 
 
504
        Example usage::
 
505
 
 
506
            distributions, errors = working_set.find_plugins(
 
507
                Environment(plugin_dirlist)
 
508
            )
 
509
            map(working_set.add, distributions)  # add plugins+libs to sys.path
 
510
            print "Couldn't load", errors        # display errors
 
511
 
 
512
        The `plugin_env` should be an ``Environment`` instance that contains
 
513
        only distributions that are in the project's "plugin directory" or
 
514
        directories. The `full_env`, if supplied, should be an ``Environment``
 
515
        contains all currently-available distributions.  If `full_env` is not
 
516
        supplied, one is created automatically from the ``WorkingSet`` this
 
517
        method is called on, which will typically mean that every directory on
 
518
        ``sys.path`` will be scanned for distributions.
 
519
 
 
520
        `installer` is a standard installer callback as used by the
 
521
        ``resolve()`` method. The `fallback` flag indicates whether we should
 
522
        attempt to resolve older versions of a plugin if the newest version
 
523
        cannot be resolved.
 
524
 
 
525
        This method returns a 2-tuple: (`distributions`, `error_info`), where
 
526
        `distributions` is a list of the distributions found in `plugin_env`
 
527
        that were loadable, along with any other distributions that are needed
 
528
        to resolve their dependencies.  `error_info` is a dictionary mapping
 
529
        unloadable plugin distributions to an exception instance describing the
 
530
        error that occurred. Usually this will be a ``DistributionNotFound`` or
 
531
        ``VersionConflict`` instance.
 
532
        """
 
533
 
 
534
        plugin_projects = list(plugin_env)
 
535
        plugin_projects.sort()  # scan project names in alphabetic order
 
536
 
 
537
        error_info = {}
 
538
        distributions = {}
 
539
 
 
540
        if full_env is None:
 
541
            env = Environment(self.entries)
 
542
            env += plugin_env
 
543
        else:
 
544
            env = full_env + plugin_env
 
545
 
 
546
        shadow_set = self.__class__([])
 
547
        map(shadow_set.add, self)   # put all our entries in shadow_set
 
548
 
 
549
        for project_name in plugin_projects:
 
550
 
 
551
            for dist in plugin_env[project_name]:
 
552
 
 
553
                req = [dist.as_requirement()]
 
554
 
 
555
                try:
 
556
                    resolvees = shadow_set.resolve(req, env, installer)
 
557
 
 
558
                except ResolutionError,v:
 
559
                    error_info[dist] = v    # save error info
 
560
                    if fallback:
 
561
                        continue    # try the next older version of project
 
562
                    else:
 
563
                        break       # give up on this project, keep going
 
564
 
 
565
                else:
 
566
                    map(shadow_set.add, resolvees)
 
567
                    distributions.update(dict.fromkeys(resolvees))
 
568
 
 
569
                    # success, no need to try any more versions of this project
 
570
                    break
 
571
 
 
572
        distributions = list(distributions)
 
573
        distributions.sort()
 
574
 
 
575
        return distributions, error_info
 
576
 
 
577
 
 
578
 
 
579
 
 
580
 
 
581
    def require(self, *requirements):
 
582
        """Ensure that distributions matching `requirements` are activated
 
583
 
 
584
        `requirements` must be a string or a (possibly-nested) sequence
 
585
        thereof, specifying the distributions and versions required.  The
 
586
        return value is a sequence of the distributions that needed to be
 
587
        activated to fulfill the requirements; all relevant distributions are
 
588
        included, even if they were already activated in this working set.
 
589
        """
 
590
 
 
591
        needed = self.resolve(parse_requirements(requirements))
 
592
 
 
593
        for dist in needed:
 
594
            self.add(dist)
 
595
 
 
596
        return needed
 
597
 
 
598
 
 
599
    def subscribe(self, callback):
 
600
        """Invoke `callback` for all distributions (including existing ones)"""
 
601
        if callback in self.callbacks:
 
602
            return
 
603
        self.callbacks.append(callback)
 
604
        for dist in self:
 
605
            callback(dist)
 
606
 
 
607
 
 
608
    def _added_new(self, dist):
 
609
        for callback in self.callbacks:
 
610
            callback(dist)
 
611
 
 
612
 
 
613
 
 
614
 
 
615
 
 
616
 
 
617
 
 
618
 
 
619
 
 
620
 
 
621
 
 
622
class Environment(object):
 
623
    """Searchable snapshot of distributions on a search path"""
 
624
 
 
625
    def __init__(self, search_path=None, platform=get_supported_platform(), python=PY_MAJOR):
 
626
        """Snapshot distributions available on a search path
 
627
 
 
628
        Any distributions found on `search_path` are added to the environment.
 
629
        `search_path` should be a sequence of ``sys.path`` items.  If not
 
630
        supplied, ``sys.path`` is used.
 
631
 
 
632
        `platform` is an optional string specifying the name of the platform
 
633
        that platform-specific distributions must be compatible with.  If
 
634
        unspecified, it defaults to the current platform.  `python` is an
 
635
        optional string naming the desired version of Python (e.g. ``'2.4'``);
 
636
        it defaults to the current version.
 
637
 
 
638
        You may explicitly set `platform` (and/or `python`) to ``None`` if you
 
639
        wish to map *all* distributions, not just those compatible with the
 
640
        running platform or Python version.
 
641
        """
 
642
        self._distmap = {}
 
643
        self._cache = {}
 
644
        self.platform = platform
 
645
        self.python = python
 
646
        self.scan(search_path)
 
647
 
 
648
    def can_add(self, dist):
 
649
        """Is distribution `dist` acceptable for this environment?
 
650
 
 
651
        The distribution must match the platform and python version
 
652
        requirements specified when this environment was created, or False
 
653
        is returned.
 
654
        """
 
655
        return (self.python is None or dist.py_version is None
 
656
            or dist.py_version==self.python) \
 
657
           and compatible_platforms(dist.platform,self.platform)
 
658
 
 
659
    def remove(self, dist):
 
660
        """Remove `dist` from the environment"""
 
661
        self._distmap[dist.key].remove(dist)
 
662
 
 
663
    def scan(self, search_path=None):
 
664
        """Scan `search_path` for distributions usable in this environment
 
665
 
 
666
        Any distributions found are added to the environment.
 
667
        `search_path` should be a sequence of ``sys.path`` items.  If not
 
668
        supplied, ``sys.path`` is used.  Only distributions conforming to
 
669
        the platform/python version defined at initialization are added.
 
670
        """
 
671
        if search_path is None:
 
672
            search_path = sys.path
 
673
 
 
674
        for item in search_path:
 
675
            for dist in find_distributions(item):
 
676
                self.add(dist)
 
677
 
 
678
    def __getitem__(self,project_name):
 
679
        """Return a newest-to-oldest list of distributions for `project_name`
 
680
        """
 
681
        try:
 
682
            return self._cache[project_name]
 
683
        except KeyError:
 
684
            project_name = project_name.lower()
 
685
            if project_name not in self._distmap:
 
686
                return []
 
687
 
 
688
        if project_name not in self._cache:
 
689
            dists = self._cache[project_name] = self._distmap[project_name]
 
690
            _sort_dists(dists)
 
691
 
 
692
        return self._cache[project_name]
 
693
 
 
694
    def add(self,dist):
 
695
        """Add `dist` if we ``can_add()`` it and it isn't already added"""
 
696
        if self.can_add(dist) and dist.has_version():
 
697
            dists = self._distmap.setdefault(dist.key,[])
 
698
            if dist not in dists:
 
699
                dists.append(dist)
 
700
                if dist.key in self._cache:
 
701
                    _sort_dists(self._cache[dist.key])
 
702
 
 
703
 
 
704
    def best_match(self, req, working_set, installer=None):
 
705
        """Find distribution best matching `req` and usable on `working_set`
 
706
 
 
707
        This calls the ``find(req)`` method of the `working_set` to see if a
 
708
        suitable distribution is already active.  (This may raise
 
709
        ``VersionConflict`` if an unsuitable version of the project is already
 
710
        active in the specified `working_set`.)  If a suitable distribution
 
711
        isn't active, this method returns the newest distribution in the
 
712
        environment that meets the ``Requirement`` in `req`.  If no suitable
 
713
        distribution is found, and `installer` is supplied, then the result of
 
714
        calling the environment's ``obtain(req, installer)`` method will be
 
715
        returned.
 
716
        """
 
717
        dist = working_set.find(req)
 
718
        if dist is not None:
 
719
            return dist
 
720
        for dist in self[req.key]:
 
721
            if dist in req:
 
722
                return dist
 
723
        return self.obtain(req, installer) # try and download/install
 
724
 
 
725
    def obtain(self, requirement, installer=None):
 
726
        """Obtain a distribution matching `requirement` (e.g. via download)
 
727
 
 
728
        Obtain a distro that matches requirement (e.g. via download).  In the
 
729
        base ``Environment`` class, this routine just returns
 
730
        ``installer(requirement)``, unless `installer` is None, in which case
 
731
        None is returned instead.  This method is a hook that allows subclasses
 
732
        to attempt other ways of obtaining a distribution before falling back
 
733
        to the `installer` argument."""
 
734
        if installer is not None:
 
735
            return installer(requirement)
 
736
 
 
737
    def __iter__(self):
 
738
        """Yield the unique project names of the available distributions"""
 
739
        for key in self._distmap.keys():
 
740
            if self[key]: yield key
 
741
 
 
742
 
 
743
 
 
744
 
 
745
    def __iadd__(self, other):
 
746
        """In-place addition of a distribution or environment"""
 
747
        if isinstance(other,Distribution):
 
748
            self.add(other)
 
749
        elif isinstance(other,Environment):
 
750
            for project in other:
 
751
                for dist in other[project]:
 
752
                    self.add(dist)
 
753
        else:
 
754
            raise TypeError("Can't add %r to environment" % (other,))
 
755
        return self
 
756
 
 
757
    def __add__(self, other):
 
758
        """Add an environment or distribution to an environment"""
 
759
        new = self.__class__([], platform=None, python=None)
 
760
        for env in self, other:
 
761
            new += env
 
762
        return new
 
763
 
 
764
 
 
765
AvailableDistributions = Environment    # XXX backward compatibility
 
766
 
 
767
 
 
768
class ExtractionError(RuntimeError):
 
769
    """An error occurred extracting a resource
 
770
 
 
771
    The following attributes are available from instances of this exception:
 
772
 
 
773
    manager
 
774
        The resource manager that raised this exception
 
775
 
 
776
    cache_path
 
777
        The base directory for resource extraction
 
778
 
 
779
    original_error
 
780
        The exception instance that caused extraction to fail
 
781
    """
 
782
 
 
783
 
 
784
 
 
785
 
 
786
class ResourceManager:
 
787
    """Manage resource extraction and packages"""
 
788
    extraction_path = None
 
789
 
 
790
    def __init__(self):
 
791
        self.cached_files = {}
 
792
 
 
793
    def resource_exists(self, package_or_requirement, resource_name):
 
794
        """Does the named resource exist?"""
 
795
        return get_provider(package_or_requirement).has_resource(resource_name)
 
796
 
 
797
    def resource_isdir(self, package_or_requirement, resource_name):
 
798
        """Is the named resource an existing directory?"""
 
799
        return get_provider(package_or_requirement).resource_isdir(
 
800
            resource_name
 
801
        )
 
802
 
 
803
    def resource_filename(self, package_or_requirement, resource_name):
 
804
        """Return a true filesystem path for specified resource"""
 
805
        return get_provider(package_or_requirement).get_resource_filename(
 
806
            self, resource_name
 
807
        )
 
808
 
 
809
    def resource_stream(self, package_or_requirement, resource_name):
 
810
        """Return a readable file-like object for specified resource"""
 
811
        return get_provider(package_or_requirement).get_resource_stream(
 
812
            self, resource_name
 
813
        )
 
814
 
 
815
    def resource_string(self, package_or_requirement, resource_name):
 
816
        """Return specified resource as a string"""
 
817
        return get_provider(package_or_requirement).get_resource_string(
 
818
            self, resource_name
 
819
        )
 
820
 
 
821
    def resource_listdir(self, package_or_requirement, resource_name):
 
822
        """List the contents of the named resource directory"""
 
823
        return get_provider(package_or_requirement).resource_listdir(
 
824
            resource_name
 
825
        )
 
826
 
 
827
    def extraction_error(self):
 
828
        """Give an error message for problems extracting file(s)"""
 
829
 
 
830
        old_exc = sys.exc_info()[1]
 
831
        cache_path = self.extraction_path or get_default_cache()
 
832
 
 
833
        err = ExtractionError("""Can't extract file(s) to egg cache
 
834
 
 
835
The following error occurred while trying to extract file(s) to the Python egg
 
836
cache:
 
837
 
 
838
  %s
 
839
 
 
840
The Python egg cache directory is currently set to:
 
841
 
 
842
  %s
 
843
 
 
844
Perhaps your account does not have write access to this directory?  You can
 
845
change the cache directory by setting the PYTHON_EGG_CACHE environment
 
846
variable to point to an accessible directory.
 
847
"""         % (old_exc, cache_path)
 
848
        )
 
849
        err.manager        = self
 
850
        err.cache_path     = cache_path
 
851
        err.original_error = old_exc
 
852
        raise err
 
853
 
 
854
 
 
855
 
 
856
 
 
857
 
 
858
 
 
859
 
 
860
 
 
861
 
 
862
 
 
863
 
 
864
 
 
865
 
 
866
 
 
867
 
 
868
    def get_cache_path(self, archive_name, names=()):
 
869
        """Return absolute location in cache for `archive_name` and `names`
 
870
 
 
871
        The parent directory of the resulting path will be created if it does
 
872
        not already exist.  `archive_name` should be the base filename of the
 
873
        enclosing egg (which may not be the name of the enclosing zipfile!),
 
874
        including its ".egg" extension.  `names`, if provided, should be a
 
875
        sequence of path name parts "under" the egg's extraction location.
 
876
 
 
877
        This method should only be called by resource providers that need to
 
878
        obtain an extraction location, and only for names they intend to
 
879
        extract, as it tracks the generated names for possible cleanup later.
 
880
        """
 
881
        extract_path = self.extraction_path or get_default_cache()
 
882
        target_path = os.path.join(extract_path, archive_name+'-tmp', *names)
 
883
        try:
 
884
            ensure_directory(target_path)
 
885
        except:
 
886
            self.extraction_error()
 
887
 
 
888
        self.cached_files[target_path] = 1
 
889
        return target_path
 
890
 
 
891
 
 
892
    def postprocess(self, tempname, filename):
 
893
        """Perform any platform-specific postprocessing of `tempname`
 
894
 
 
895
        This is where Mac header rewrites should be done; other platforms don't
 
896
        have anything special they should do.
 
897
 
 
898
        Resource providers should call this method ONLY after successfully
 
899
        extracting a compressed resource.  They must NOT call it on resources
 
900
        that are already in the filesystem.
 
901
 
 
902
        `tempname` is the current (temporary) name of the file, and `filename`
 
903
        is the name it will be renamed to by the caller after this routine
 
904
        returns.
 
905
        """
 
906
        # XXX
 
907
 
 
908
 
 
909
    def set_extraction_path(self, path):
 
910
        """Set the base path where resources will be extracted to, if needed.
 
911
 
 
912
        If you do not call this routine before any extractions take place, the
 
913
        path defaults to the return value of ``get_default_cache()``.  (Which
 
914
        is based on the ``PYTHON_EGG_CACHE`` environment variable, with various
 
915
        platform-specific fallbacks.  See that routine's documentation for more
 
916
        details.)
 
917
 
 
918
        Resources are extracted to subdirectories of this path based upon
 
919
        information given by the ``IResourceProvider``.  You may set this to a
 
920
        temporary directory, but then you must call ``cleanup_resources()`` to
 
921
        delete the extracted files when done.  There is no guarantee that
 
922
        ``cleanup_resources()`` will be able to remove all extracted files.
 
923
 
 
924
        (Note: you may not change the extraction path for a given resource
 
925
        manager once resources have been extracted, unless you first call
 
926
        ``cleanup_resources()``.)
 
927
        """
 
928
        if self.cached_files:
 
929
            raise ValueError(
 
930
                "Can't change extraction path, files already extracted"
 
931
            )
 
932
 
 
933
        self.extraction_path = path
 
934
 
 
935
    def cleanup_resources(self, force=False):
 
936
        """
 
937
        Delete all extracted resource files and directories, returning a list
 
938
        of the file and directory names that could not be successfully removed.
 
939
        This function does not have any concurrency protection, so it should
 
940
        generally only be called when the extraction path is a temporary
 
941
        directory exclusive to a single process.  This method is not
 
942
        automatically called; you must call it explicitly or register it as an
 
943
        ``atexit`` function if you wish to ensure cleanup of a temporary
 
944
        directory used for extractions.
 
945
        """
 
946
        # XXX
 
947
 
 
948
 
 
949
 
 
950
def get_default_cache():
 
951
    """Determine the default cache location
 
952
 
 
953
    This returns the ``PYTHON_EGG_CACHE`` environment variable, if set.
 
954
    Otherwise, on Windows, it returns a "Python-Eggs" subdirectory of the
 
955
    "Application Data" directory.  On all other systems, it's "~/.python-eggs".
 
956
    """
 
957
    try:
 
958
        return os.environ['PYTHON_EGG_CACHE']
 
959
    except KeyError:
 
960
        pass
 
961
 
 
962
    if os.name!='nt':
 
963
        return os.path.expanduser('~/.python-eggs')
 
964
 
 
965
    app_data = 'Application Data'   # XXX this may be locale-specific!
 
966
    app_homes = [
 
967
        (('APPDATA',), None),       # best option, should be locale-safe
 
968
        (('USERPROFILE',), app_data),
 
969
        (('HOMEDRIVE','HOMEPATH'), app_data),
 
970
        (('HOMEPATH',), app_data),
 
971
        (('HOME',), None),
 
972
        (('WINDIR',), app_data),    # 95/98/ME
 
973
    ]
 
974
 
 
975
    for keys, subdir in app_homes:
 
976
        dirname = ''
 
977
        for key in keys:
 
978
            if key in os.environ:
 
979
                dirname = os.path.join(os.environ[key])
 
980
            else:
 
981
                break
 
982
        else:
 
983
            if subdir:
 
984
                dirname = os.path.join(dirname,subdir)
 
985
            return os.path.join(dirname, 'Python-Eggs')
 
986
    else:
 
987
        raise RuntimeError(
 
988
            "Please set the PYTHON_EGG_CACHE enviroment variable"
 
989
        )
 
990
 
 
991
def safe_name(name):
 
992
    """Convert an arbitrary string to a standard distribution name
 
993
 
 
994
    Any runs of non-alphanumeric/. characters are replaced with a single '-'.
 
995
    """
 
996
    return re.sub('[^A-Za-z0-9.]+', '-', name)
 
997
 
 
998
 
 
999
def safe_version(version):
 
1000
    """Convert an arbitrary string to a standard version string
 
1001
 
 
1002
    Spaces become dots, and all other non-alphanumeric characters become
 
1003
    dashes, with runs of multiple dashes condensed to a single dash.
 
1004
    """
 
1005
    version = version.replace(' ','.')
 
1006
    return re.sub('[^A-Za-z0-9.]+', '-', version)
 
1007
 
 
1008
 
 
1009
def safe_extra(extra):
 
1010
    """Convert an arbitrary string to a standard 'extra' name
 
1011
 
 
1012
    Any runs of non-alphanumeric characters are replaced with a single '_',
 
1013
    and the result is always lowercased.
 
1014
    """
 
1015
    return re.sub('[^A-Za-z0-9.]+', '_', extra).lower()
 
1016
 
 
1017
 
 
1018
def to_filename(name):
 
1019
    """Convert a project or version name to its filename-escaped form
 
1020
 
 
1021
    Any '-' characters are currently replaced with '_'.
 
1022
    """
 
1023
    return name.replace('-','_')
 
1024
 
 
1025
 
 
1026
 
 
1027
 
 
1028
 
 
1029
 
 
1030
 
 
1031
 
 
1032
class NullProvider:
 
1033
    """Try to implement resources and metadata for arbitrary PEP 302 loaders"""
 
1034
 
 
1035
    egg_name = None
 
1036
    egg_info = None
 
1037
    loader = None
 
1038
 
 
1039
    def __init__(self, module):
 
1040
        self.loader = getattr(module, '__loader__', None)
 
1041
        self.module_path = os.path.dirname(getattr(module, '__file__', ''))
 
1042
 
 
1043
    def get_resource_filename(self, manager, resource_name):
 
1044
        return self._fn(self.module_path, resource_name)
 
1045
 
 
1046
    def get_resource_stream(self, manager, resource_name):
 
1047
        return StringIO(self.get_resource_string(manager, resource_name))
 
1048
 
 
1049
    def get_resource_string(self, manager, resource_name):
 
1050
        return self._get(self._fn(self.module_path, resource_name))
 
1051
 
 
1052
    def has_resource(self, resource_name):
 
1053
        return self._has(self._fn(self.module_path, resource_name))
 
1054
 
 
1055
    def has_metadata(self, name):
 
1056
        return self.egg_info and self._has(self._fn(self.egg_info,name))
 
1057
 
 
1058
    def get_metadata(self, name):
 
1059
        if not self.egg_info:
 
1060
            return ""
 
1061
        return self._get(self._fn(self.egg_info,name))
 
1062
 
 
1063
    def get_metadata_lines(self, name):
 
1064
        return yield_lines(self.get_metadata(name))
 
1065
 
 
1066
    def resource_isdir(self,resource_name):
 
1067
        return self._isdir(self._fn(self.module_path, resource_name))
 
1068
 
 
1069
    def metadata_isdir(self,name):
 
1070
        return self.egg_info and self._isdir(self._fn(self.egg_info,name))
 
1071
 
 
1072
 
 
1073
    def resource_listdir(self,resource_name):
 
1074
        return self._listdir(self._fn(self.module_path,resource_name))
 
1075
 
 
1076
    def metadata_listdir(self,name):
 
1077
        if self.egg_info:
 
1078
            return self._listdir(self._fn(self.egg_info,name))
 
1079
        return []
 
1080
 
 
1081
    def run_script(self,script_name,namespace):
 
1082
        script = 'scripts/'+script_name
 
1083
        if not self.has_metadata(script):
 
1084
            raise ResolutionError("No script named %r" % script_name)
 
1085
        script_text = self.get_metadata(script).replace('\r\n','\n')
 
1086
        script_text = script_text.replace('\r','\n')
 
1087
        script_filename = self._fn(self.egg_info,script)
 
1088
        namespace['__file__'] = script_filename
 
1089
        if os.path.exists(script_filename):
 
1090
            execfile(script_filename, namespace, namespace)
 
1091
        else:
 
1092
            from linecache import cache
 
1093
            cache[script_filename] = (
 
1094
                len(script_text), 0, script_text.split('\n'), script_filename
 
1095
            )
 
1096
            script_code = compile(script_text,script_filename,'exec')
 
1097
            exec script_code in namespace, namespace
 
1098
 
 
1099
    def _has(self, path):
 
1100
        raise NotImplementedError(
 
1101
            "Can't perform this operation for unregistered loader type"
 
1102
        )
 
1103
 
 
1104
    def _isdir(self, path):
 
1105
        raise NotImplementedError(
 
1106
            "Can't perform this operation for unregistered loader type"
 
1107
        )
 
1108
 
 
1109
    def _listdir(self, path):
 
1110
        raise NotImplementedError(
 
1111
            "Can't perform this operation for unregistered loader type"
 
1112
        )
 
1113
 
 
1114
    def _fn(self, base, resource_name):
 
1115
        return os.path.join(base, *resource_name.split('/'))
 
1116
 
 
1117
    def _get(self, path):
 
1118
        if hasattr(self.loader, 'get_data'):
 
1119
            return self.loader.get_data(path)
 
1120
        raise NotImplementedError(
 
1121
            "Can't perform this operation for loaders without 'get_data()'"
 
1122
        )
 
1123
 
 
1124
register_loader_type(object, NullProvider)
 
1125
 
 
1126
 
 
1127
class EggProvider(NullProvider):
 
1128
    """Provider based on a virtual filesystem"""
 
1129
 
 
1130
    def __init__(self,module):
 
1131
        NullProvider.__init__(self,module)
 
1132
        self._setup_prefix()
 
1133
 
 
1134
    def _setup_prefix(self):
 
1135
        # we assume here that our metadata may be nested inside a "basket"
 
1136
        # of multiple eggs; that's why we use module_path instead of .archive
 
1137
        path = self.module_path
 
1138
        old = None
 
1139
        while path!=old:
 
1140
            if path.lower().endswith('.egg'):
 
1141
                self.egg_name = os.path.basename(path)
 
1142
                self.egg_info = os.path.join(path, 'EGG-INFO')
 
1143
                self.egg_root = path
 
1144
                break
 
1145
            old = path
 
1146
            path, base = os.path.split(path)
 
1147
 
 
1148
 
 
1149
 
 
1150
 
 
1151
 
 
1152
 
 
1153
 
 
1154
 
 
1155
class DefaultProvider(EggProvider):
 
1156
    """Provides access to package resources in the filesystem"""
 
1157
 
 
1158
    def _has(self, path):
 
1159
        return os.path.exists(path)
 
1160
 
 
1161
    def _isdir(self,path):
 
1162
        return os.path.isdir(path)
 
1163
 
 
1164
    def _listdir(self,path):
 
1165
        return os.listdir(path)
 
1166
 
 
1167
    def get_resource_stream(self, manager, resource_name):
 
1168
        return open(self._fn(self.module_path, resource_name), 'rb')
 
1169
 
 
1170
    def _get(self, path):
 
1171
        stream = open(path, 'rb')
 
1172
        try:
 
1173
            return stream.read()
 
1174
        finally:
 
1175
            stream.close()
 
1176
 
 
1177
register_loader_type(type(None), DefaultProvider)
 
1178
 
 
1179
 
 
1180
class EmptyProvider(NullProvider):
 
1181
    """Provider that returns nothing for all requests"""
 
1182
 
 
1183
    _isdir = _has = lambda self,path: False
 
1184
    _get          = lambda self,path: ''
 
1185
    _listdir      = lambda self,path: []
 
1186
    module_path   = None
 
1187
 
 
1188
    def __init__(self):
 
1189
        pass
 
1190
 
 
1191
empty_provider = EmptyProvider()
 
1192
 
 
1193
 
 
1194
 
 
1195
 
 
1196
class ZipProvider(EggProvider):
 
1197
    """Resource support for zips and eggs"""
 
1198
 
 
1199
    eagers = None
 
1200
 
 
1201
    def __init__(self, module):
 
1202
        EggProvider.__init__(self,module)
 
1203
        self.zipinfo = zipimport._zip_directory_cache[self.loader.archive]
 
1204
        self.zip_pre = self.loader.archive+os.sep
 
1205
 
 
1206
    def _zipinfo_name(self, fspath):
 
1207
        # Convert a virtual filename (full path to file) into a zipfile subpath
 
1208
        # usable with the zipimport directory cache for our target archive
 
1209
        if fspath.startswith(self.zip_pre):
 
1210
            return fspath[len(self.zip_pre):]
 
1211
        raise AssertionError(
 
1212
            "%s is not a subpath of %s" % (fspath,self.zip_pre)
 
1213
        )
 
1214
 
 
1215
    def _parts(self,zip_path):
 
1216
        # Convert a zipfile subpath into an egg-relative path part list
 
1217
        fspath = self.zip_pre+zip_path  # pseudo-fs path
 
1218
        if fspath.startswith(self.egg_root+os.sep):
 
1219
            return fspath[len(self.egg_root)+1:].split(os.sep)
 
1220
        raise AssertionError(
 
1221
            "%s is not a subpath of %s" % (fspath,self.egg_root)
 
1222
        )
 
1223
 
 
1224
    def get_resource_filename(self, manager, resource_name):
 
1225
        if not self.egg_name:
 
1226
            raise NotImplementedError(
 
1227
                "resource_filename() only supported for .egg, not .zip"
 
1228
            )
 
1229
        # no need to lock for extraction, since we use temp names
 
1230
        zip_path = self._resource_to_zip(resource_name)
 
1231
        eagers = self._get_eager_resources()
 
1232
        if '/'.join(self._parts(zip_path)) in eagers:
 
1233
            for name in eagers:
 
1234
                self._extract_resource(manager, self._eager_to_zip(name))
 
1235
        return self._extract_resource(manager, zip_path)
 
1236
 
 
1237
    def _extract_resource(self, manager, zip_path):
 
1238
 
 
1239
        if zip_path in self._index():
 
1240
            for name in self._index()[zip_path]:
 
1241
                last = self._extract_resource(
 
1242
                    manager, os.path.join(zip_path, name)
 
1243
                )
 
1244
            return os.path.dirname(last)  # return the extracted directory name
 
1245
 
 
1246
        zip_stat = self.zipinfo[zip_path]
 
1247
        t,d,size = zip_stat[5], zip_stat[6], zip_stat[3]
 
1248
        date_time = (
 
1249
            (d>>9)+1980, (d>>5)&0xF, d&0x1F,                      # ymd
 
1250
            (t&0xFFFF)>>11, (t>>5)&0x3F, (t&0x1F) * 2, 0, 0, -1   # hms, etc.
 
1251
        )
 
1252
        timestamp = time.mktime(date_time)
 
1253
 
 
1254
        try:
 
1255
            real_path = manager.get_cache_path(
 
1256
                self.egg_name, self._parts(zip_path)
 
1257
            )
 
1258
 
 
1259
            if os.path.isfile(real_path):
 
1260
                stat = os.stat(real_path)
 
1261
                if stat.st_size==size and stat.st_mtime==timestamp:
 
1262
                    # size and stamp match, don't bother extracting
 
1263
                    return real_path
 
1264
 
 
1265
            outf, tmpnam = _mkstemp(".$extract", dir=os.path.dirname(real_path))
 
1266
            os.write(outf, self.loader.get_data(zip_path))
 
1267
            os.close(outf)
 
1268
            utime(tmpnam, (timestamp,timestamp))
 
1269
            manager.postprocess(tmpnam, real_path)
 
1270
 
 
1271
            try:
 
1272
                rename(tmpnam, real_path)
 
1273
 
 
1274
            except os.error:
 
1275
                if os.path.isfile(real_path):
 
1276
                    stat = os.stat(real_path)
 
1277
 
 
1278
                    if stat.st_size==size and stat.st_mtime==timestamp:
 
1279
                        # size and stamp match, somebody did it just ahead of
 
1280
                        # us, so we're done
 
1281
                        return real_path
 
1282
                    elif os.name=='nt':     # Windows, del old file and retry
 
1283
                        unlink(real_path)
 
1284
                        rename(tmpnam, real_path)
 
1285
                        return real_path
 
1286
                raise
 
1287
 
 
1288
        except os.error:
 
1289
            manager.extraction_error()  # report a user-friendly error
 
1290
 
 
1291
        return real_path
 
1292
 
 
1293
    def _get_eager_resources(self):
 
1294
        if self.eagers is None:
 
1295
            eagers = []
 
1296
            for name in ('native_libs.txt', 'eager_resources.txt'):
 
1297
                if self.has_metadata(name):
 
1298
                    eagers.extend(self.get_metadata_lines(name))
 
1299
            self.eagers = eagers
 
1300
        return self.eagers
 
1301
 
 
1302
    def _index(self):
 
1303
        try:
 
1304
            return self._dirindex
 
1305
        except AttributeError:
 
1306
            ind = {}
 
1307
            for path in self.zipinfo:
 
1308
                parts = path.split(os.sep)
 
1309
                while parts:
 
1310
                    parent = os.sep.join(parts[:-1])
 
1311
                    if parent in ind:
 
1312
                        ind[parent].append(parts[-1])
 
1313
                        break
 
1314
                    else:
 
1315
                        ind[parent] = [parts.pop()]
 
1316
            self._dirindex = ind
 
1317
            return ind
 
1318
 
 
1319
    def _has(self, fspath):
 
1320
        zip_path = self._zipinfo_name(fspath)
 
1321
        return zip_path in self.zipinfo or zip_path in self._index()
 
1322
 
 
1323
    def _isdir(self,fspath):
 
1324
        return self._zipinfo_name(fspath) in self._index()
 
1325
 
 
1326
    def _listdir(self,fspath):
 
1327
        return list(self._index().get(self._zipinfo_name(fspath), ()))
 
1328
 
 
1329
    def _eager_to_zip(self,resource_name):
 
1330
        return self._zipinfo_name(self._fn(self.egg_root,resource_name))
 
1331
 
 
1332
    def _resource_to_zip(self,resource_name):
 
1333
        return self._zipinfo_name(self._fn(self.module_path,resource_name))
 
1334
 
 
1335
register_loader_type(zipimport.zipimporter, ZipProvider)
 
1336
 
 
1337
 
 
1338
 
 
1339
 
 
1340
 
 
1341
 
 
1342
 
 
1343
 
 
1344
 
 
1345
 
 
1346
 
 
1347
 
 
1348
 
 
1349
 
 
1350
 
 
1351
 
 
1352
 
 
1353
 
 
1354
 
 
1355
 
 
1356
 
 
1357
 
 
1358
 
 
1359
 
 
1360
class FileMetadata(EmptyProvider):
 
1361
    """Metadata handler for standalone PKG-INFO files
 
1362
 
 
1363
    Usage::
 
1364
 
 
1365
        metadata = FileMetadata("/path/to/PKG-INFO")
 
1366
 
 
1367
    This provider rejects all data and metadata requests except for PKG-INFO,
 
1368
    which is treated as existing, and will be the contents of the file at
 
1369
    the provided location.
 
1370
    """
 
1371
 
 
1372
    def __init__(self,path):
 
1373
        self.path = path
 
1374
 
 
1375
    def has_metadata(self,name):
 
1376
        return name=='PKG-INFO'
 
1377
 
 
1378
    def get_metadata(self,name):
 
1379
        if name=='PKG-INFO':
 
1380
            return open(self.path,'rU').read()
 
1381
        raise KeyError("No metadata except PKG-INFO is available")
 
1382
 
 
1383
    def get_metadata_lines(self,name):
 
1384
        return yield_lines(self.get_metadata(name))
 
1385
 
 
1386
 
 
1387
 
 
1388
 
 
1389
 
 
1390
 
 
1391
 
 
1392
 
 
1393
 
 
1394
 
 
1395
 
 
1396
 
 
1397
 
 
1398
 
 
1399
 
 
1400
 
 
1401
class PathMetadata(DefaultProvider):
 
1402
    """Metadata provider for egg directories
 
1403
 
 
1404
    Usage::
 
1405
 
 
1406
        # Development eggs:
 
1407
 
 
1408
        egg_info = "/path/to/PackageName.egg-info"
 
1409
        base_dir = os.path.dirname(egg_info)
 
1410
        metadata = PathMetadata(base_dir, egg_info)
 
1411
        dist_name = os.path.splitext(os.path.basename(egg_info))[0]
 
1412
        dist = Distribution(basedir,project_name=dist_name,metadata=metadata)
 
1413
 
 
1414
        # Unpacked egg directories:
 
1415
 
 
1416
        egg_path = "/path/to/PackageName-ver-pyver-etc.egg"
 
1417
        metadata = PathMetadata(egg_path, os.path.join(egg_path,'EGG-INFO'))
 
1418
        dist = Distribution.from_filename(egg_path, metadata=metadata)
 
1419
    """
 
1420
 
 
1421
    def __init__(self, path, egg_info):
 
1422
        self.module_path = path
 
1423
        self.egg_info = egg_info
 
1424
 
 
1425
 
 
1426
class EggMetadata(ZipProvider):
 
1427
    """Metadata provider for .egg files"""
 
1428
 
 
1429
    def __init__(self, importer):
 
1430
        """Create a metadata provider from a zipimporter"""
 
1431
 
 
1432
        self.zipinfo = zipimport._zip_directory_cache[importer.archive]
 
1433
        self.zip_pre = importer.archive+os.sep
 
1434
        self.loader = importer
 
1435
        if importer.prefix:
 
1436
            self.module_path = os.path.join(importer.archive, importer.prefix)
 
1437
        else:
 
1438
            self.module_path = importer.archive
 
1439
        self._setup_prefix()
 
1440
 
 
1441
 
 
1442
class ImpWrapper:
 
1443
    """PEP 302 Importer that wraps Python's "normal" import algorithm"""
 
1444
 
 
1445
    def __init__(self, path=None):
 
1446
        self.path = path
 
1447
 
 
1448
    def find_module(self, fullname, path=None):
 
1449
        subname = fullname.split(".")[-1]
 
1450
        if subname != fullname and self.path is None:
 
1451
            return None
 
1452
        if self.path is None:
 
1453
            path = None
 
1454
        else:
 
1455
            path = [self.path]
 
1456
        try:
 
1457
            file, filename, etc = imp.find_module(subname, path)
 
1458
        except ImportError:
 
1459
            return None
 
1460
        return ImpLoader(file, filename, etc)
 
1461
 
 
1462
 
 
1463
class ImpLoader:
 
1464
    """PEP 302 Loader that wraps Python's "normal" import algorithm"""
 
1465
 
 
1466
    def __init__(self, file, filename, etc):
 
1467
        self.file = file
 
1468
        self.filename = filename
 
1469
        self.etc = etc
 
1470
 
 
1471
    def load_module(self, fullname):
 
1472
        try:
 
1473
            mod = imp.load_module(fullname, self.file, self.filename, self.etc)
 
1474
        finally:
 
1475
            if self.file: self.file.close()
 
1476
        # Note: we don't set __loader__ because we want the module to look
 
1477
        # normal; i.e. this is just a wrapper for standard import machinery
 
1478
        return mod
 
1479
 
 
1480
 
 
1481
 
 
1482
 
 
1483
def get_importer(path_item):
 
1484
    """Retrieve a PEP 302 "importer" for the given path item
 
1485
 
 
1486
    If there is no importer, this returns a wrapper around the builtin import
 
1487
    machinery.  The returned importer is only cached if it was created by a
 
1488
    path hook.
 
1489
    """
 
1490
    try:
 
1491
        importer = sys.path_importer_cache[path_item]
 
1492
    except KeyError:
 
1493
        for hook in sys.path_hooks:
 
1494
            try:
 
1495
                importer = hook(path_item)
 
1496
            except ImportError:
 
1497
                pass
 
1498
            else:
 
1499
                break
 
1500
        else:
 
1501
            importer = None
 
1502
 
 
1503
    sys.path_importer_cache.setdefault(path_item,importer)
 
1504
    if importer is None:
 
1505
        try:
 
1506
            importer = ImpWrapper(path_item)
 
1507
        except ImportError:
 
1508
            pass
 
1509
    return importer
 
1510
 
 
1511
try:
 
1512
    from pkgutil import get_importer, ImpImporter
 
1513
except ImportError:
 
1514
    pass    # Python 2.3 or 2.4, use our own implementation
 
1515
else:
 
1516
    ImpWrapper = ImpImporter    # Python 2.5, use pkgutil's implementation
 
1517
    del ImpLoader, ImpImporter
 
1518
 
 
1519
 
 
1520
 
 
1521
 
 
1522
 
 
1523
 
 
1524
_distribution_finders = {}
 
1525
 
 
1526
def register_finder(importer_type, distribution_finder):
 
1527
    """Register `distribution_finder` to find distributions in sys.path items
 
1528
 
 
1529
    `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
 
1530
    handler), and `distribution_finder` is a callable that, passed a path
 
1531
    item and the importer instance, yields ``Distribution`` instances found on
 
1532
    that path item.  See ``pkg_resources.find_on_path`` for an example."""
 
1533
    _distribution_finders[importer_type] = distribution_finder
 
1534
 
 
1535
 
 
1536
def find_distributions(path_item, only=False):
 
1537
    """Yield distributions accessible via `path_item`"""
 
1538
    importer = get_importer(path_item)
 
1539
    finder = _find_adapter(_distribution_finders, importer)
 
1540
    return finder(importer, path_item, only)
 
1541
 
 
1542
def find_in_zip(importer, path_item, only=False):
 
1543
    metadata = EggMetadata(importer)
 
1544
    if metadata.has_metadata('PKG-INFO'):
 
1545
        yield Distribution.from_filename(path_item, metadata=metadata)
 
1546
    if only:
 
1547
        return  # don't yield nested distros
 
1548
    for subitem in metadata.resource_listdir('/'):
 
1549
        if subitem.endswith('.egg'):
 
1550
            subpath = os.path.join(path_item, subitem)
 
1551
            for dist in find_in_zip(zipimport.zipimporter(subpath), subpath):
 
1552
                yield dist
 
1553
 
 
1554
register_finder(zipimport.zipimporter, find_in_zip)
 
1555
 
 
1556
def StringIO(*args, **kw):
 
1557
    """Thunk to load the real StringIO on demand"""
 
1558
    global StringIO
 
1559
    try:
 
1560
        from cStringIO import StringIO
 
1561
    except ImportError:
 
1562
        from StringIO import StringIO
 
1563
    return StringIO(*args,**kw)
 
1564
 
 
1565
def find_nothing(importer, path_item, only=False):
 
1566
    return ()
 
1567
register_finder(object,find_nothing)
 
1568
 
 
1569
def find_on_path(importer, path_item, only=False):
 
1570
    """Yield distributions accessible on a sys.path directory"""
 
1571
    path_item = _normalize_cached(path_item)
 
1572
 
 
1573
    if os.path.isdir(path_item):
 
1574
        if path_item.lower().endswith('.egg'):
 
1575
            # unpacked egg
 
1576
            yield Distribution.from_filename(
 
1577
                path_item, metadata=PathMetadata(
 
1578
                    path_item, os.path.join(path_item,'EGG-INFO')
 
1579
                )
 
1580
            )
 
1581
        else:
 
1582
            # scan for .egg and .egg-info in directory
 
1583
            for entry in os.listdir(path_item):
 
1584
                lower = entry.lower()
 
1585
                if lower.endswith('.egg-info'):
 
1586
                    fullpath = os.path.join(path_item, entry)
 
1587
                    if os.path.isdir(fullpath):
 
1588
                        # egg-info directory, allow getting metadata
 
1589
                        metadata = PathMetadata(path_item, fullpath)
 
1590
                    else:
 
1591
                        metadata = FileMetadata(fullpath)
 
1592
                    yield Distribution.from_location(
 
1593
                        path_item,entry,metadata,precedence=DEVELOP_DIST
 
1594
                    )
 
1595
                elif not only and lower.endswith('.egg'):
 
1596
                    for dist in find_distributions(os.path.join(path_item, entry)):
 
1597
                        yield dist
 
1598
                elif not only and lower.endswith('.egg-link'):
 
1599
                    for line in file(os.path.join(path_item, entry)):
 
1600
                        if not line.strip(): continue
 
1601
                        for item in find_distributions(line.rstrip()):
 
1602
                            yield item
 
1603
 
 
1604
register_finder(ImpWrapper,find_on_path)
 
1605
 
 
1606
_namespace_handlers = {}
 
1607
_namespace_packages = {}
 
1608
 
 
1609
def register_namespace_handler(importer_type, namespace_handler):
 
1610
    """Register `namespace_handler` to declare namespace packages
 
1611
 
 
1612
    `importer_type` is the type or class of a PEP 302 "Importer" (sys.path item
 
1613
    handler), and `namespace_handler` is a callable like this::
 
1614
 
 
1615
        def namespace_handler(importer,path_entry,moduleName,module):
 
1616
            # return a path_entry to use for child packages
 
1617
 
 
1618
    Namespace handlers are only called if the importer object has already
 
1619
    agreed that it can handle the relevant path item, and they should only
 
1620
    return a subpath if the module __path__ does not already contain an
 
1621
    equivalent subpath.  For an example namespace handler, see
 
1622
    ``pkg_resources.file_ns_handler``.
 
1623
    """
 
1624
    _namespace_handlers[importer_type] = namespace_handler
 
1625
 
 
1626
def _handle_ns(packageName, path_item):
 
1627
    """Ensure that named package includes a subpath of path_item (if needed)"""
 
1628
    importer = get_importer(path_item)
 
1629
    if importer is None:
 
1630
        return None
 
1631
    loader = importer.find_module(packageName)
 
1632
    if loader is None:
 
1633
        return None
 
1634
    module = sys.modules.get(packageName)
 
1635
    if module is None:
 
1636
        module = sys.modules[packageName] = new.module(packageName)
 
1637
        module.__path__ = []; _set_parent_ns(packageName)
 
1638
    elif not hasattr(module,'__path__'):
 
1639
        raise TypeError("Not a package:", packageName)
 
1640
    handler = _find_adapter(_namespace_handlers, importer)
 
1641
    subpath = handler(importer,path_item,packageName,module)
 
1642
    if subpath is not None:
 
1643
        path = module.__path__; path.append(subpath)
 
1644
        loader.load_module(packageName); module.__path__ = path
 
1645
    return subpath
 
1646
 
 
1647
def declare_namespace(packageName):
 
1648
    """Declare that package 'packageName' is a namespace package"""
 
1649
 
 
1650
    imp.acquire_lock()
 
1651
    try:
 
1652
        if packageName in _namespace_packages:
 
1653
            return
 
1654
 
 
1655
        path, parent = sys.path, None
 
1656
        if '.' in packageName:
 
1657
            parent = '.'.join(packageName.split('.')[:-1])
 
1658
            declare_namespace(parent)
 
1659
            __import__(parent)
 
1660
            try:
 
1661
                path = sys.modules[parent].__path__
 
1662
            except AttributeError:
 
1663
                raise TypeError("Not a package:", parent)
 
1664
 
 
1665
        # Track what packages are namespaces, so when new path items are added,
 
1666
        # they can be updated
 
1667
        _namespace_packages.setdefault(parent,[]).append(packageName)
 
1668
        _namespace_packages.setdefault(packageName,[])
 
1669
 
 
1670
        for path_item in path:
 
1671
            # Ensure all the parent's path items are reflected in the child,
 
1672
            # if they apply
 
1673
            _handle_ns(packageName, path_item)
 
1674
 
 
1675
    finally:
 
1676
        imp.release_lock()
 
1677
 
 
1678
def fixup_namespace_packages(path_item, parent=None):
 
1679
    """Ensure that previously-declared namespace packages include path_item"""
 
1680
    imp.acquire_lock()
 
1681
    try:
 
1682
        for package in _namespace_packages.get(parent,()):
 
1683
            subpath = _handle_ns(package, path_item)
 
1684
            if subpath: fixup_namespace_packages(subpath,package)
 
1685
    finally:
 
1686
        imp.release_lock()
 
1687
 
 
1688
def file_ns_handler(importer, path_item, packageName, module):
 
1689
    """Compute an ns-package subpath for a filesystem or zipfile importer"""
 
1690
 
 
1691
    subpath = os.path.join(path_item, packageName.split('.')[-1])
 
1692
    normalized = _normalize_cached(subpath)
 
1693
    for item in module.__path__:
 
1694
        if _normalize_cached(item)==normalized:
 
1695
            break
 
1696
    else:
 
1697
        # Only return the path if it's not already there
 
1698
        return subpath
 
1699
 
 
1700
register_namespace_handler(ImpWrapper,file_ns_handler)
 
1701
register_namespace_handler(zipimport.zipimporter,file_ns_handler)
 
1702
 
 
1703
 
 
1704
def null_ns_handler(importer, path_item, packageName, module):
 
1705
    return None
 
1706
 
 
1707
register_namespace_handler(object,null_ns_handler)
 
1708
 
 
1709
 
 
1710
def normalize_path(filename):
 
1711
    """Normalize a file/dir name for comparison purposes"""
 
1712
    return os.path.normcase(os.path.realpath(filename))
 
1713
 
 
1714
def _normalize_cached(filename,_cache={}):
 
1715
    try:
 
1716
        return _cache[filename]
 
1717
    except KeyError:
 
1718
        _cache[filename] = result = normalize_path(filename)
 
1719
        return result
 
1720
 
 
1721
def _set_parent_ns(packageName):
 
1722
    parts = packageName.split('.')
 
1723
    name = parts.pop()
 
1724
    if parts:
 
1725
        parent = '.'.join(parts)
 
1726
        setattr(sys.modules[parent], name, sys.modules[packageName])
 
1727
 
 
1728
 
 
1729
def yield_lines(strs):
 
1730
    """Yield non-empty/non-comment lines of a ``basestring`` or sequence"""
 
1731
    if isinstance(strs,basestring):
 
1732
        for s in strs.splitlines():
 
1733
            s = s.strip()
 
1734
            if s and not s.startswith('#'):     # skip blank lines/comments
 
1735
                yield s
 
1736
    else:
 
1737
        for ss in strs:
 
1738
            for s in yield_lines(ss):
 
1739
                yield s
 
1740
 
 
1741
LINE_END = re.compile(r"\s*(#.*)?$").match         # whitespace and comment
 
1742
CONTINUE = re.compile(r"\s*\\\s*(#.*)?$").match    # line continuation
 
1743
DISTRO   = re.compile(r"\s*((\w|[-.])+)").match    # Distribution or extra
 
1744
VERSION  = re.compile(r"\s*(<=?|>=?|==|!=)\s*((\w|[-.])+)").match  # ver. info
 
1745
COMMA    = re.compile(r"\s*,").match               # comma between items
 
1746
OBRACKET = re.compile(r"\s*\[").match
 
1747
CBRACKET = re.compile(r"\s*\]").match
 
1748
MODULE   = re.compile(r"\w+(\.\w+)*$").match
 
1749
EGG_NAME = re.compile(
 
1750
    r"(?P<name>[^-]+)"
 
1751
    r"( -(?P<ver>[^-]+) (-py(?P<pyver>[^-]+) (-(?P<plat>.+))? )? )?",
 
1752
    re.VERBOSE | re.IGNORECASE
 
1753
).match
 
1754
 
 
1755
component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE)
 
1756
replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c'}.get
 
1757
 
 
1758
def _parse_version_parts(s):
 
1759
    for part in component_re.split(s):
 
1760
        part = replace(part,part)
 
1761
        if not part or part=='.':
 
1762
            continue
 
1763
        if part[:1] in '0123456789':
 
1764
            yield part.zfill(8)    # pad for numeric comparison
 
1765
        else:
 
1766
            yield '*'+part
 
1767
 
 
1768
    yield '*final'  # ensure that alpha/beta/candidate are before final
 
1769
 
 
1770
def parse_version(s):
 
1771
    """Convert a version string to a chronologically-sortable key
 
1772
 
 
1773
    This is a rough cross between distutils' StrictVersion and LooseVersion;
 
1774
    if you give it versions that would work with StrictVersion, then it behaves
 
1775
    the same; otherwise it acts like a slightly-smarter LooseVersion. It is
 
1776
    *possible* to create pathological version coding schemes that will fool
 
1777
    this parser, but they should be very rare in practice.
 
1778
 
 
1779
    The returned value will be a tuple of strings.  Numeric portions of the
 
1780
    version are padded to 8 digits so they will compare numerically, but
 
1781
    without relying on how numbers compare relative to strings.  Dots are
 
1782
    dropped, but dashes are retained.  Trailing zeros between alpha segments
 
1783
    or dashes are suppressed, so that e.g. "2.4.0" is considered the same as
 
1784
    "2.4". Alphanumeric parts are lower-cased.
 
1785
 
 
1786
    The algorithm assumes that strings like "-" and any alpha string that
 
1787
    alphabetically follows "final"  represents a "patch level".  So, "2.4-1"
 
1788
    is assumed to be a branch or patch of "2.4", and therefore "2.4.1" is
 
1789
    considered newer than "2.4-1", whic in turn is newer than "2.4".
 
1790
 
 
1791
    Strings like "a", "b", "c", "alpha", "beta", "candidate" and so on (that
 
1792
    come before "final" alphabetically) are assumed to be pre-release versions,
 
1793
    so that the version "2.4" is considered newer than "2.4a1".
 
1794
 
 
1795
    Finally, to handle miscellaneous cases, the strings "pre", "preview", and
 
1796
    "rc" are treated as if they were "c", i.e. as though they were release
 
1797
    candidates, and therefore are not as new as a version string that does not
 
1798
    contain them.
 
1799
    """
 
1800
    parts = []
 
1801
    for part in _parse_version_parts(s.lower()):
 
1802
        if part.startswith('*'):
 
1803
            if part<'*final':   # remove '-' before a prerelease tag
 
1804
                while parts and parts[-1]=='*final-': parts.pop()
 
1805
            # remove trailing zeros from each series of numeric parts
 
1806
            while parts and parts[-1]=='00000000':
 
1807
                parts.pop()
 
1808
        parts.append(part)
 
1809
    return tuple(parts)
 
1810
 
 
1811
class EntryPoint(object):
 
1812
    """Object representing an advertised importable object"""
 
1813
 
 
1814
    def __init__(self, name, module_name, attrs=(), extras=(), dist=None):
 
1815
        if not MODULE(module_name):
 
1816
            raise ValueError("Invalid module name", module_name)
 
1817
        self.name = name
 
1818
        self.module_name = module_name
 
1819
        self.attrs = tuple(attrs)
 
1820
        self.extras = Requirement.parse(("x[%s]" % ','.join(extras))).extras
 
1821
        self.dist = dist
 
1822
 
 
1823
    def __str__(self):
 
1824
        s = "%s = %s" % (self.name, self.module_name)
 
1825
        if self.attrs:
 
1826
            s += ':' + '.'.join(self.attrs)
 
1827
        if self.extras:
 
1828
            s += ' [%s]' % ','.join(self.extras)
 
1829
        return s
 
1830
 
 
1831
    def __repr__(self):
 
1832
        return "EntryPoint.parse(%r)" % str(self)
 
1833
 
 
1834
    def load(self, require=True, env=None, installer=None):
 
1835
        if require: self.require(env, installer)
 
1836
        entry = __import__(self.module_name, globals(),globals(), ['__name__'])
 
1837
        for attr in self.attrs:
 
1838
            try:
 
1839
                entry = getattr(entry,attr)
 
1840
            except AttributeError:
 
1841
                raise ImportError("%r has no %r attribute" % (entry,attr))
 
1842
        return entry
 
1843
 
 
1844
    def require(self, env=None, installer=None):
 
1845
        if self.extras and not self.dist:
 
1846
            raise UnknownExtra("Can't require() without a distribution", self)
 
1847
        map(working_set.add,
 
1848
            working_set.resolve(self.dist.requires(self.extras),env,installer))
 
1849
 
 
1850
 
 
1851
 
 
1852
    #@classmethod
 
1853
    def parse(cls, src, dist=None):
 
1854
        """Parse a single entry point from string `src`
 
1855
 
 
1856
        Entry point syntax follows the form::
 
1857
 
 
1858
            name = some.module:some.attr [extra1,extra2]
 
1859
 
 
1860
        The entry name and module name are required, but the ``:attrs`` and
 
1861
        ``[extras]`` parts are optional
 
1862
        """
 
1863
        try:
 
1864
            attrs = extras = ()
 
1865
            name,value = src.split('=',1)
 
1866
            if '[' in value:
 
1867
                value,extras = value.split('[',1)
 
1868
                req = Requirement.parse("x["+extras)
 
1869
                if req.specs: raise ValueError
 
1870
                extras = req.extras
 
1871
            if ':' in value:
 
1872
                value,attrs = value.split(':',1)
 
1873
                if not MODULE(attrs.rstrip()):
 
1874
                    raise ValueError
 
1875
                attrs = attrs.rstrip().split('.')
 
1876
        except ValueError:
 
1877
            raise ValueError(
 
1878
                "EntryPoint must be in 'name=module:attrs [extras]' format",
 
1879
                src
 
1880
            )
 
1881
        else:
 
1882
            return cls(name.strip(), value.strip(), attrs, extras, dist)
 
1883
 
 
1884
    parse = classmethod(parse)
 
1885
 
 
1886
 
 
1887
 
 
1888
 
 
1889
 
 
1890
 
 
1891
 
 
1892
 
 
1893
    #@classmethod
 
1894
    def parse_group(cls, group, lines, dist=None):
 
1895
        """Parse an entry point group"""
 
1896
        if not MODULE(group):
 
1897
            raise ValueError("Invalid group name", group)
 
1898
        this = {}
 
1899
        for line in yield_lines(lines):
 
1900
            ep = cls.parse(line, dist)
 
1901
            if ep.name in this:
 
1902
                raise ValueError("Duplicate entry point", group, ep.name)
 
1903
            this[ep.name]=ep
 
1904
        return this
 
1905
 
 
1906
    parse_group = classmethod(parse_group)
 
1907
 
 
1908
    #@classmethod
 
1909
    def parse_map(cls, data, dist=None):
 
1910
        """Parse a map of entry point groups"""
 
1911
        if isinstance(data,dict):
 
1912
            data = data.items()
 
1913
        else:
 
1914
            data = split_sections(data)
 
1915
        maps = {}
 
1916
        for group, lines in data:
 
1917
            if group is None:
 
1918
                if not lines:
 
1919
                    continue
 
1920
                raise ValueError("Entry points must be listed in groups")
 
1921
            group = group.strip()
 
1922
            if group in maps:
 
1923
                raise ValueError("Duplicate group name", group)
 
1924
            maps[group] = cls.parse_group(group, lines, dist)
 
1925
        return maps
 
1926
 
 
1927
    parse_map = classmethod(parse_map)
 
1928
 
 
1929
 
 
1930
 
 
1931
 
 
1932
 
 
1933
 
 
1934
class Distribution(object):
 
1935
    """Wrap an actual or potential sys.path entry w/metadata"""
 
1936
    def __init__(self,
 
1937
        location=None, metadata=None, project_name=None, version=None,
 
1938
        py_version=PY_MAJOR, platform=None, precedence = EGG_DIST
 
1939
    ):
 
1940
        self.project_name = safe_name(project_name or 'Unknown')
 
1941
        if version is not None:
 
1942
            self._version = safe_version(version)
 
1943
        self.py_version = py_version
 
1944
        self.platform = platform
 
1945
        self.location = location
 
1946
        self.precedence = precedence
 
1947
        self._provider = metadata or empty_provider
 
1948
 
 
1949
    #@classmethod
 
1950
    def from_location(cls,location,basename,metadata=None,**kw):
 
1951
        project_name, version, py_version, platform = [None]*4
 
1952
        basename, ext = os.path.splitext(basename)
 
1953
        if ext.lower() in (".egg",".egg-info"):
 
1954
            match = EGG_NAME(basename)
 
1955
            if match:
 
1956
                project_name, version, py_version, platform = match.group(
 
1957
                    'name','ver','pyver','plat'
 
1958
                )
 
1959
        return cls(
 
1960
            location, metadata, project_name=project_name, version=version,
 
1961
            py_version=py_version, platform=platform, **kw
 
1962
        )
 
1963
    from_location = classmethod(from_location)
 
1964
 
 
1965
    hashcmp = property(
 
1966
        lambda self: (
 
1967
            getattr(self,'parsed_version',()), self.precedence, self.key,
 
1968
            -len(self.location or ''), self.location, self.py_version,
 
1969
            self.platform
 
1970
        )
 
1971
    )
 
1972
    def __cmp__(self, other): return cmp(self.hashcmp, other)
 
1973
    def __hash__(self): return hash(self.hashcmp)
 
1974
 
 
1975
    # These properties have to be lazy so that we don't have to load any
 
1976
    # metadata until/unless it's actually needed.  (i.e., some distributions
 
1977
    # may not know their name or version without loading PKG-INFO)
 
1978
 
 
1979
    #@property
 
1980
    def key(self):
 
1981
        try:
 
1982
            return self._key
 
1983
        except AttributeError:
 
1984
            self._key = key = self.project_name.lower()
 
1985
            return key
 
1986
    key = property(key)
 
1987
 
 
1988
    #@property
 
1989
    def parsed_version(self):
 
1990
        try:
 
1991
            return self._parsed_version
 
1992
        except AttributeError:
 
1993
            self._parsed_version = pv = parse_version(self.version)
 
1994
            return pv
 
1995
 
 
1996
    parsed_version = property(parsed_version)
 
1997
 
 
1998
    #@property
 
1999
    def version(self):
 
2000
        try:
 
2001
            return self._version
 
2002
        except AttributeError:
 
2003
            for line in self._get_metadata('PKG-INFO'):
 
2004
                if line.lower().startswith('version:'):
 
2005
                    self._version = safe_version(line.split(':',1)[1].strip())
 
2006
                    return self._version
 
2007
            else:
 
2008
                raise ValueError(
 
2009
                    "Missing 'Version:' header and/or PKG-INFO file", self
 
2010
                )
 
2011
    version = property(version)
 
2012
 
 
2013
 
 
2014
 
 
2015
 
 
2016
    #@property
 
2017
    def _dep_map(self):
 
2018
        try:
 
2019
            return self.__dep_map
 
2020
        except AttributeError:
 
2021
            dm = self.__dep_map = {None: []}
 
2022
            for name in 'requires.txt', 'depends.txt':
 
2023
                for extra,reqs in split_sections(self._get_metadata(name)):
 
2024
                    if extra: extra = safe_extra(extra)
 
2025
                    dm.setdefault(extra,[]).extend(parse_requirements(reqs))
 
2026
            return dm
 
2027
    _dep_map = property(_dep_map)
 
2028
 
 
2029
    def requires(self,extras=()):
 
2030
        """List of Requirements needed for this distro if `extras` are used"""
 
2031
        dm = self._dep_map
 
2032
        deps = []
 
2033
        deps.extend(dm.get(None,()))
 
2034
        for ext in extras:
 
2035
            try:
 
2036
                deps.extend(dm[safe_extra(ext)])
 
2037
            except KeyError:
 
2038
                raise UnknownExtra(
 
2039
                    "%s has no such extra feature %r" % (self, ext)
 
2040
                )
 
2041
        return deps
 
2042
 
 
2043
    def _get_metadata(self,name):
 
2044
        if self.has_metadata(name):
 
2045
            for line in self.get_metadata_lines(name):
 
2046
                yield line
 
2047
 
 
2048
    def activate(self,path=None):
 
2049
        """Ensure distribution is importable on `path` (default=sys.path)"""
 
2050
        if path is None: path = sys.path
 
2051
        self.insert_on(path)
 
2052
        if path is sys.path:
 
2053
            fixup_namespace_packages(self.location)
 
2054
            map(declare_namespace, self._get_metadata('namespace_packages.txt'))
 
2055
 
 
2056
 
 
2057
    def egg_name(self):
 
2058
        """Return what this distribution's standard .egg filename should be"""
 
2059
        filename = "%s-%s-py%s" % (
 
2060
            to_filename(self.project_name), to_filename(self.version),
 
2061
            self.py_version or PY_MAJOR
 
2062
        )
 
2063
 
 
2064
        if self.platform:
 
2065
            filename += '-'+self.platform
 
2066
        return filename
 
2067
 
 
2068
    def __repr__(self):
 
2069
        if self.location:
 
2070
            return "%s (%s)" % (self,self.location)
 
2071
        else:
 
2072
            return str(self)
 
2073
 
 
2074
    def __str__(self):
 
2075
        try: version = getattr(self,'version',None)
 
2076
        except ValueError: version = None
 
2077
        version = version or "[unknown version]"
 
2078
        return "%s %s" % (self.project_name,version)
 
2079
 
 
2080
    def __getattr__(self,attr):
 
2081
        """Delegate all unrecognized public attributes to .metadata provider"""
 
2082
        if attr.startswith('_'):
 
2083
            raise AttributeError,attr
 
2084
        return getattr(self._provider, attr)
 
2085
 
 
2086
    #@classmethod
 
2087
    def from_filename(cls,filename,metadata=None, **kw):
 
2088
        return cls.from_location(
 
2089
            _normalize_cached(filename), os.path.basename(filename), metadata,
 
2090
            **kw
 
2091
        )
 
2092
    from_filename = classmethod(from_filename)
 
2093
 
 
2094
    def as_requirement(self):
 
2095
        """Return a ``Requirement`` that matches this distribution exactly"""
 
2096
        return Requirement.parse('%s==%s' % (self.project_name, self.version))
 
2097
 
 
2098
    def load_entry_point(self, group, name):
 
2099
        """Return the `name` entry point of `group` or raise ImportError"""
 
2100
        ep = self.get_entry_info(group,name)
 
2101
        if ep is None:
 
2102
            raise ImportError("Entry point %r not found" % ((group,name),))
 
2103
        return ep.load()
 
2104
 
 
2105
    def get_entry_map(self, group=None):
 
2106
        """Return the entry point map for `group`, or the full entry map"""
 
2107
        try:
 
2108
            ep_map = self._ep_map
 
2109
        except AttributeError:
 
2110
            ep_map = self._ep_map = EntryPoint.parse_map(
 
2111
                self._get_metadata('entry_points.txt'), self
 
2112
            )
 
2113
        if group is not None:
 
2114
            return ep_map.get(group,{})
 
2115
        return ep_map
 
2116
 
 
2117
    def get_entry_info(self, group, name):
 
2118
        """Return the EntryPoint object for `group`+`name`, or ``None``"""
 
2119
        return self.get_entry_map(group).get(name)
 
2120
 
 
2121
 
 
2122
 
 
2123
 
 
2124
 
 
2125
 
 
2126
 
 
2127
 
 
2128
 
 
2129
 
 
2130
 
 
2131
 
 
2132
 
 
2133
 
 
2134
 
 
2135
 
 
2136
 
 
2137
 
 
2138
 
 
2139
    def insert_on(self, path, loc = None):
 
2140
        """Insert self.location in path before its nearest parent directory"""
 
2141
 
 
2142
        loc = loc or self.location
 
2143
        if not loc:
 
2144
            return
 
2145
 
 
2146
        if path is sys.path:
 
2147
            self.check_version_conflict()
 
2148
 
 
2149
        nloc = _normalize_cached(loc)
 
2150
        bdir = os.path.dirname(nloc)
 
2151
        npath= map(_normalize_cached, path)
 
2152
 
 
2153
        bp = None
 
2154
        for p, item in enumerate(npath):
 
2155
            if item==nloc:
 
2156
                break
 
2157
            elif item==bdir:
 
2158
                path.insert(p, loc)
 
2159
                npath.insert(p, nloc)
 
2160
                break
 
2161
        else:
 
2162
            path.append(loc)
 
2163
            return
 
2164
 
 
2165
        # p is the spot where we found or inserted loc; now remove duplicates
 
2166
        while 1:
 
2167
            try:
 
2168
                np = npath.index(nloc, p+1)
 
2169
            except ValueError:
 
2170
                break
 
2171
            else:
 
2172
                del npath[np], path[np]
 
2173
                p = np  # ha!
 
2174
 
 
2175
        return
 
2176
 
 
2177
 
 
2178
 
 
2179
 
 
2180
    def check_version_conflict(self):
 
2181
        if self.key=='setuptools':
 
2182
            return      # ignore the inevitable setuptools self-conflicts  :(
 
2183
 
 
2184
        nsp = dict.fromkeys(self._get_metadata('namespace_packages.txt'))
 
2185
        loc = normalize_path(self.location)
 
2186
        for modname in self._get_metadata('top_level.txt'):
 
2187
            if (modname not in sys.modules or modname in nsp
 
2188
                or modname in _namespace_packages
 
2189
            ):
 
2190
                continue
 
2191
 
 
2192
            fn = getattr(sys.modules[modname], '__file__', None)
 
2193
            if fn and normalize_path(fn).startswith(loc):
 
2194
                continue
 
2195
            issue_warning(
 
2196
                "Module %s was already imported from %s, but %s is being added"
 
2197
                " to sys.path" % (modname, fn, self.location),
 
2198
            )
 
2199
 
 
2200
    def has_version(self):
 
2201
        try:
 
2202
            self.version
 
2203
        except ValueError:
 
2204
            issue_warning("Unbuilt egg for "+repr(self))
 
2205
            return False
 
2206
        return True
 
2207
 
 
2208
    def clone(self,**kw):
 
2209
        """Copy this distribution, substituting in any changed keyword args"""
 
2210
        for attr in (
 
2211
            'project_name', 'version', 'py_version', 'platform', 'location',
 
2212
            'precedence'
 
2213
        ):
 
2214
            kw.setdefault(attr, getattr(self,attr,None))
 
2215
        kw.setdefault('metadata', self._provider)
 
2216
        return self.__class__(**kw)
 
2217
 
 
2218
 
 
2219
 
 
2220
 
 
2221
    #@property
 
2222
    def extras(self):
 
2223
        return [dep for dep in self._dep_map if dep]
 
2224
    extras = property(extras)
 
2225
 
 
2226
 
 
2227
def issue_warning(*args,**kw):
 
2228
    level = 1
 
2229
    g = globals()
 
2230
    try:
 
2231
        # find the first stack frame that is *not* code in
 
2232
        # the pkg_resources module, to use for the warning
 
2233
        while sys._getframe(level).f_globals is g:
 
2234
            level += 1
 
2235
    except ValueError:
 
2236
        pass
 
2237
    from warnings import warn
 
2238
    warn(stacklevel = level+1, *args, **kw)
 
2239
 
 
2240
 
 
2241
 
 
2242
 
 
2243
 
 
2244
 
 
2245
 
 
2246
 
 
2247
 
 
2248
 
 
2249
 
 
2250
 
 
2251
 
 
2252
 
 
2253
 
 
2254
 
 
2255
 
 
2256
 
 
2257
 
 
2258
 
 
2259
 
 
2260
 
 
2261
 
 
2262
def parse_requirements(strs):
 
2263
    """Yield ``Requirement`` objects for each specification in `strs`
 
2264
 
 
2265
    `strs` must be an instance of ``basestring``, or a (possibly-nested)
 
2266
    iterable thereof.
 
2267
    """
 
2268
    # create a steppable iterator, so we can handle \-continuations
 
2269
    lines = iter(yield_lines(strs))
 
2270
 
 
2271
    def scan_list(ITEM,TERMINATOR,line,p,groups,item_name):
 
2272
 
 
2273
        items = []
 
2274
 
 
2275
        while not TERMINATOR(line,p):
 
2276
            if CONTINUE(line,p):
 
2277
                try:
 
2278
                    line = lines.next(); p = 0
 
2279
                except StopIteration:
 
2280
                    raise ValueError(
 
2281
                        "\\ must not appear on the last nonblank line"
 
2282
                    )
 
2283
 
 
2284
            match = ITEM(line,p)
 
2285
            if not match:
 
2286
                raise ValueError("Expected "+item_name+" in",line,"at",line[p:])
 
2287
 
 
2288
            items.append(match.group(*groups))
 
2289
            p = match.end()
 
2290
 
 
2291
            match = COMMA(line,p)
 
2292
            if match:
 
2293
                p = match.end() # skip the comma
 
2294
            elif not TERMINATOR(line,p):
 
2295
                raise ValueError(
 
2296
                    "Expected ',' or end-of-list in",line,"at",line[p:]
 
2297
                )
 
2298
 
 
2299
        match = TERMINATOR(line,p)
 
2300
        if match: p = match.end()   # skip the terminator, if any
 
2301
        return line, p, items
 
2302
 
 
2303
    for line in lines:
 
2304
        match = DISTRO(line)
 
2305
        if not match:
 
2306
            raise ValueError("Missing distribution spec", line)
 
2307
        project_name = match.group(1)
 
2308
        p = match.end()
 
2309
        extras = []
 
2310
 
 
2311
        match = OBRACKET(line,p)
 
2312
        if match:
 
2313
            p = match.end()
 
2314
            line, p, extras = scan_list(
 
2315
                DISTRO, CBRACKET, line, p, (1,), "'extra' name"
 
2316
            )
 
2317
 
 
2318
        line, p, specs = scan_list(VERSION,LINE_END,line,p,(1,2),"version spec")
 
2319
        specs = [(op,safe_version(val)) for op,val in specs]
 
2320
        yield Requirement(project_name, specs, extras)
 
2321
 
 
2322
 
 
2323
def _sort_dists(dists):
 
2324
    tmp = [(dist.hashcmp,dist) for dist in dists]
 
2325
    tmp.sort()
 
2326
    dists[::-1] = [d for hc,d in tmp]
 
2327
 
 
2328
 
 
2329
 
 
2330
 
 
2331
 
 
2332
 
 
2333
 
 
2334
 
 
2335
 
 
2336
 
 
2337
 
 
2338
 
 
2339
 
 
2340
 
 
2341
 
 
2342
 
 
2343
 
 
2344
class Requirement:
 
2345
    def __init__(self, project_name, specs, extras):
 
2346
        """DO NOT CALL THIS UNDOCUMENTED METHOD; use Requirement.parse()!"""
 
2347
        self.unsafe_name, project_name = project_name, safe_name(project_name)
 
2348
        self.project_name, self.key = project_name, project_name.lower()
 
2349
        index = [(parse_version(v),state_machine[op],op,v) for op,v in specs]
 
2350
        index.sort()
 
2351
        self.specs = [(op,ver) for parsed,trans,op,ver in index]
 
2352
        self.index, self.extras = index, tuple(map(safe_extra,extras))
 
2353
        self.hashCmp = (
 
2354
            self.key, tuple([(op,parsed) for parsed,trans,op,ver in index]),
 
2355
            ImmutableSet(self.extras)
 
2356
        )
 
2357
        self.__hash = hash(self.hashCmp)
 
2358
 
 
2359
    def __str__(self):
 
2360
        specs = ','.join([''.join(s) for s in self.specs])
 
2361
        extras = ','.join(self.extras)
 
2362
        if extras: extras = '[%s]' % extras
 
2363
        return '%s%s%s' % (self.project_name, extras, specs)
 
2364
 
 
2365
    def __eq__(self,other):
 
2366
        return isinstance(other,Requirement) and self.hashCmp==other.hashCmp
 
2367
 
 
2368
    def __contains__(self,item):
 
2369
        if isinstance(item,Distribution):
 
2370
            if item.key <> self.key: return False
 
2371
            if self.index: item = item.parsed_version  # only get if we need it
 
2372
        elif isinstance(item,basestring):
 
2373
            item = parse_version(item)
 
2374
        last = None
 
2375
        for parsed,trans,op,ver in self.index:
 
2376
            action = trans[cmp(item,parsed)]
 
2377
            if action=='F':     return False
 
2378
            elif action=='T':   return True
 
2379
            elif action=='+':   last = True
 
2380
            elif action=='-' or last is None:   last = False
 
2381
        if last is None: last = True    # no rules encountered
 
2382
        return last
 
2383
 
 
2384
 
 
2385
    def __hash__(self):
 
2386
        return self.__hash
 
2387
 
 
2388
    def __repr__(self): return "Requirement.parse(%r)" % str(self)
 
2389
 
 
2390
    #@staticmethod
 
2391
    def parse(s):
 
2392
        reqs = list(parse_requirements(s))
 
2393
        if reqs:
 
2394
            if len(reqs)==1:
 
2395
                return reqs[0]
 
2396
            raise ValueError("Expected only one requirement", s)
 
2397
        raise ValueError("No requirements found", s)
 
2398
 
 
2399
    parse = staticmethod(parse)
 
2400
 
 
2401
state_machine = {
 
2402
    #       =><
 
2403
    '<' :  '--T',
 
2404
    '<=':  'T-T',
 
2405
    '>' :  'F+F',
 
2406
    '>=':  'T+F',
 
2407
    '==':  'T..',
 
2408
    '!=':  'F++',
 
2409
}
 
2410
 
 
2411
 
 
2412
def _get_mro(cls):
 
2413
    """Get an mro for a type or classic class"""
 
2414
    if not isinstance(cls,type):
 
2415
        class cls(cls,object): pass
 
2416
        return cls.__mro__[1:]
 
2417
    return cls.__mro__
 
2418
 
 
2419
def _find_adapter(registry, ob):
 
2420
    """Return an adapter factory for `ob` from `registry`"""
 
2421
    for t in _get_mro(getattr(ob, '__class__', type(ob))):
 
2422
        if t in registry:
 
2423
            return registry[t]
 
2424
 
 
2425
 
 
2426
def ensure_directory(path):
 
2427
    """Ensure that the parent directory of `path` exists"""
 
2428
    dirname = os.path.dirname(path)
 
2429
    if not os.path.isdir(dirname):
 
2430
        os.makedirs(dirname)
 
2431
 
 
2432
def split_sections(s):
 
2433
    """Split a string or iterable thereof into (section,content) pairs
 
2434
 
 
2435
    Each ``section`` is a stripped version of the section header ("[section]")
 
2436
    and each ``content`` is a list of stripped lines excluding blank lines and
 
2437
    comment-only lines.  If there are any such lines before the first section
 
2438
    header, they're returned in a first ``section`` of ``None``.
 
2439
    """
 
2440
    section = None
 
2441
    content = []
 
2442
    for line in yield_lines(s):
 
2443
        if line.startswith("["):
 
2444
            if line.endswith("]"):
 
2445
                if section or content:
 
2446
                    yield section, content
 
2447
                section = line[1:-1].strip()
 
2448
                content = []
 
2449
            else:
 
2450
                raise ValueError("Invalid section heading", line)
 
2451
        else:
 
2452
            content.append(line)
 
2453
 
 
2454
    # wrap up last segment
 
2455
    yield section, content
 
2456
 
 
2457
def _mkstemp(*args,**kw):
 
2458
    from tempfile import mkstemp
 
2459
    old_open = os.open
 
2460
    try:
 
2461
        os.open = os_open   # temporarily bypass sandboxing
 
2462
        return mkstemp(*args,**kw)
 
2463
    finally:
 
2464
        os.open = old_open  # and then put it back
 
2465
 
 
2466
 
 
2467
# Set up global resource manager
 
2468
_manager = ResourceManager()
 
2469
def _initialize(g):
 
2470
    for name in dir(_manager):
 
2471
        if not name.startswith('_'):
 
2472
            g[name] = getattr(_manager, name)
 
2473
_initialize(globals())
 
2474
 
 
2475
# Prepare the master working set and make the ``require()`` API available
 
2476
working_set = WorkingSet()
 
2477
try:
 
2478
    # Does the main program list any requirements?
 
2479
    from __main__ import __requires__
 
2480
except ImportError:
 
2481
    pass # No: just use the default working set based on sys.path
 
2482
else:
 
2483
    # Yes: ensure the requirements are met, by prefixing sys.path if necessary
 
2484
    try:
 
2485
        working_set.require(__requires__)
 
2486
    except VersionConflict:     # try it without defaults already on sys.path
 
2487
        working_set = WorkingSet([])    # by starting with an empty path
 
2488
        for dist in working_set.resolve(
 
2489
            parse_requirements(__requires__), Environment()
 
2490
        ):
 
2491
            working_set.add(dist)
 
2492
        for entry in sys.path:  # add any missing entries from sys.path
 
2493
            if entry not in working_set.entries:
 
2494
                working_set.add_entry(entry)
 
2495
        sys.path[:] = working_set.entries   # then copy back to sys.path
 
2496
 
 
2497
require = working_set.require
 
2498
iter_entry_points = working_set.iter_entry_points
 
2499
add_activation_listener = working_set.subscribe
 
2500
run_script = working_set.run_script
 
2501
run_main = run_script   # backward compatibility
 
2502
# Activate all distributions already on sys.path, and ensure that
 
2503
# all distributions added to the working set in the future (e.g. by
 
2504
# calling ``require()``) will get activated as well.
 
2505
add_activation_listener(lambda dist: dist.activate())
 
2506
working_set.entries=[]; map(working_set.add_entry,sys.path) # match order
 
2507