# Source code for taurus.core.util.plugin

    #!/usr/bin/env python

    # ###########################################################################
    #
    # This file is part of Taurus
    #
    # http://taurus-scada.org
    #
    # Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
    #
    # Taurus is free software: you can redistribute it and/or modify
    # it under the terms of the GNU Lesser General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # Taurus is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    # GNU Lesser General Public License for more details.
    #
    # You should have received a copy of the GNU Lesser General Public License
    # along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
    #
    # ###########################################################################

    """
    Utilities for plugin loading and selection
    """

    __all__ = ["selectEntryPoints", "EntryPointAlike"]

    import re
    import pkg_resources
    from re import fullmatch

    class EntryPointAlike(object):
        """
        A dummy EntryPoint substitute to be used with :func:`selectEntryPoints`
        for wrapping arbitrary objects and imitate the `name` and `load()` API of
        a :class:`pkg_resources.EntryPoint` instance.

        Pass an object and optionally a name to the constructor to get the wrapped
        `EntryPointAlike` instance. The `repr` of the object is used as the name
        if `name` arg is not provided.
        """

        def __init__(self, obj, name=None):
            self._obj = obj
            if name is None:
                name = repr(obj)
            self.name = name

        def load(self):
            """Returns the wrapped object"""
            return self._obj

    def selectEntryPoints(group=None, include=(".*",), exclude=()):
        """
        Selects and prioritizes entry points from an entry point group.

        The entry points are selected using their name.

        The selection is done by regex matching on the names: first the entry
        points whose name matches any of the patterns in `excluded` are discarded;
        then each pattern in `included` is used to select from the names of the
        remaining entry points (from highest to lowest priority).

        In this way, the entry points are selected in the order dictated by
        the `included` pattern list. If a pattern matches several names, these
        will be sorted alphabetically. If a name is matched by several patterns,
        it is only selected by the first pattern.

        For example, if there are the following registered entry point names:
        ['foo1', 'foo2', 'baz1', 'baz2', 'bar1', 'bar2']
        And we use `exclude=("foo2",)` and `include=("bar2", "b.*1", "f.*")` ,
        then the selection will be: ["bar2","bar1","baz1","foo1"]

        Note: apart from regex patterns (strings or compiled) the `include` list
        can also contain :class:`pkg_resources`EntryPoint`-like instances
        (more specifically, an object having `.name` and `.load()` members), in
        which case they are added directly to the selected list. If a member is
        something other than a pattern or an EntryPoint-like object, it will be
        wrapped in an :class:`EntryPointAlike` instance and also included in the
        selection.

        :param group: entry point group name from which the entry points are
            obtained.
        :type group: str
        :param include: The members of the tuple can either be patterns (both in
            the form of strings or of :class:`re.Pattern` objects), which will be
            matched against registered names in group; or EntryPoint-like objects
            which will be included as they are; or an arbitrary object which will
            be wrapped as an EntryPoint-like object before being included. Default
            is `(".*",)`, which matches all registered names in group and the sort
            is purely alphabetical.
        :type include: `tuple`
        :param exclude: Regexp patterns (either `str` or :class:`re.Pattern`
            objects) matching names to be excluded. Default is `()`, so no entry
            point is excluded.
        :type exclude: `tuple`
        :return: the selected entry points.
        :rtype: list of :class:`pkg_resources.EntryPoint`
        """
        ret = []

        remaining = list(pkg_resources.iter_entry_points(group))

        # filter out the entry points whose name matches a exclude pattern
        for p in exclude:
            remaining = [e for e in remaining if not fullmatch(p, e.name)]

        # sort the remaining entry points alphabetically
        remaining.sort(key=lambda e: e.name)

        # fill the ret list with the entry points whose name matches a pattern in
        # the `include` tuple. The inclusion order follows the order of the
        # patterns in `include` (and alphabetically for a pattern that produces
        # multiple matches)
        for p in include:
            try:
                # check if it is a pattern string or pattern object
                p = re.compile(p)
            except TypeError:
                # if p is not an entry point -like object, create one
                if not hasattr(p, "name") or not hasattr(p, "load"):
                    p = EntryPointAlike(p)
                # and add it directly
                ret.append(p)
                continue
            # if it is a pattern, match it against remaining entry points
            tmp = remaining
            remaining = []
            for e in tmp:
                if fullmatch(p, e.name):
                    ret.append(e)
                else:
                    remaining.append(e)
        return ret