"""Tools for loading and validating extensions."""

from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import pkg_resources
import semver


class MissingDependency(Exception):

    """No dependency found."""


class InvalidDependency(Exception):

    """Found dependency but with the wrong version."""


def load_extensions(whitelist=()):
    """Get an iterable of extensions in order."""
    whitelist = tuple(set(('core',) + tuple(whitelist)))
    extensions = pkg_resources.iter_entry_points('rpmvenv.extensions')
    extensions = (
        extension for extension in extensions if extension.name in whitelist
    )
    extensions = tuple(set(extensions))
    extensions = sorted(extensions, key=lambda ext: whitelist.index(ext.name))
    return tuple(extension.load() for extension in extensions)


def validate_extensions(extensions):
    """Process the extension dependencies."""
    ext_map = dict(
        (ext.name, ext) for ext in extensions
    )

    for ext in extensions:

        for dependency, versions in ext.requirements.items():

            ext_dependency = ext_map.get(dependency, None)
            if not ext_dependency:

                raise MissingDependency(
                    '{0} is required by {1} but is not loaded.'.format(
                        ext.name,
                        dependency,
                    )
                )

            for version in versions:

                if not semver.match(ext.version, version):

                    raise InvalidDependency(
                        '{0}-{1} required by {2} but found {0}-{3}.'.format(
                            dependency,
                            version,
                            ext.name,
                            ext.version,
                        )
                    )

    return extensions

"""Tools for loading and validating extensions."""

from __future__ import division
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import pkg_resources
import semver


class MissingDependency(Exception):

    """No dependency found."""


class InvalidDependency(Exception):

    """Found dependency but with the wrong version."""


def load_extensions(whitelist=()):
    """Get an iterable of extensions in order."""
    whitelist = ('core',) + tuple(whitelist)
    extensions = pkg_resources.iter_entry_points('rpmvenv.extensions')
    extensions = (
        extension for extension in extensions if extension.name in whitelist
    )
    extensions = tuple(set(extensions))
    extensions = sorted(extensions, key=lambda ext: whitelist.index(ext.name))
    return tuple(extension.load() for extension in extensions)


def validate_extensions(extensions):
    """Process the extension dependencies."""
    ext_map = dict(
        (ext.name, ext) for ext in extensions
    )

    for ext in extensions:

        for dependency, versions in ext.requirements.items():

            ext_dependency = ext_map.get(dependency, None)
            if not ext_dependency:

                raise MissingDependency(
                    '{0} is required by {1} but is not loaded.'.format(
                        ext.name,
                        dependency,
                    )
                )

            for version in versions:

                if not semver.match(ext.version, version):

                    raise InvalidDependency(
                        '{0}-{1} required by {2} but found {0}-{3}.'.format(
                            dependency,
                            version,
                            ext.name,
                            ext.version,
                        )
                    )

    return extensions

Fix indeterminate ordering issue for extensions
Fix indeterminate ordering issue for extensions

The original code used set() to dedupe enabled extensions. This resulted
in an arbitrary ordering of the values. The expected result was a
deterministic ordering of loaded extensions that matches the order given
by the whitelist. This removes the set() usage to preserve order.

Existing users subject to the arbitrary ordering should be unaffected as
their builds must already be tolerant to ordering changes to have worked
thus far.
