my eye

__init__.py

Raw

"""
terms of use for software, content & data

"""

import collections
import distutils.version
import json
import re

import pkg_resources

__all__ = ["get_license"]


index = {}
uris = {}
features = collections.defaultdict(list)
pattern = re.compile(
    r"""(?xi)
                         ^
                         ([\s\w]+?)
                         (
                           \s?
                           (license|l)
                           \s?
                         )?
                         (
                           \s?
                           (version|v)
                           \s?
                           ([.\w]+)
                         )?
                         $"""
)


def get_license(identifier):
    """
    return best matching license for given `identifier` (name *or* URI)

    An identifier may contain version information.

        >>> long = get_license("Affero General Public License version 3")
        >>> short = get_license("Affero General Public")
        >>> abbreviated = get_license("AGPL")
        >>> long == short == abbreviated
        True
        >>> long
        <licensing.License: Affero General Public License v3>

    """
    _load()

    if "/" in identifier and " " not in identifier:
        if identifier.startswith(("http://", "https://", "//")):
            identifier = identifier.partition("//")[2]
        try:
            req_name, req_version = uris[identifier]
        except KeyError:
            raise KeyError("`{}` not found in index".format(identifier))
    else:
        identifier = identifier.replace("_", " ")
        try:
            req_name, req_version = pattern.match(identifier).groups()[::5]
        except AttributeError:
            raise KeyError("`{}` not found in index".format(identifier))

    def cmp(kv):
        return distutils.version.LooseVersion(str(kv[0]))

    for name, versions in index.items():
        abbreviation = "".join(w[0] for w in name.split() if w not in {"of"}).lower()
        if req_name.lower().strip() not in (name.lower(), abbreviation):
            continue
        version, details = None, {}
        for version, details in sorted(versions.items(), key=cmp):
            if version == req_version:
                break
        if req_version and version != req_version:
            err_msg = "unknown version `{}` for `{}`"
            raise KeyError(err_msg.format(req_version, name))
        return _License(
            name=name,
            version=version,
            abbr=details["abbr"],
            uri=details["uri"],
            features=details.get("features", []),
        )
    raise KeyError("`{}` not found in index".format(identifier))


class _License(collections.namedtuple("License", "name version abbr uri features")):

    """
    a `NamedTuple` for licenses

        >>> license = _License(name="Affero General Public", version="3",
        ...                    abbr="AGPLv3", uri="gnu.org/licenses/agpl.html",
        ...                    features=["dfsg","gpl","fsf","osi","copyleft"])
        >>> license.name
        'Affero General Public'
        >>> license.version
        '3'
        >>> license.uri
        'gnu.org/licenses/agpl.html'
        >>> license.features
        ['dfsg', 'gpl', 'fsf', 'osi', 'copyleft']

        >>> repr(license)  #doctest: +ELLIPSIS
        '<licensing.License: Affero General Public License v3>'
        >>> str(license)
        'Affero General Public License v3 (gnu.org/licenses/agpl.html)'
        >>> license.is_compatible("copyleft")
        True

    """

    @property
    def canonical(self):
        version = ""
        if self.version != "0":
            version = " v" + self.version
        return "".join((self.name, " License", version))

    def is_compatible(self, compatibility):
        return compatibility in self.features

    def __repr__(self):
        return "<licensing.License: {}>".format(self.canonical)

    def __str__(self):
        return "{} ({})".format(self.canonical, self.uri)


def _load():
    """
    populate license index

    """
    global index
    global uris
    global features
    if index:
        return
    licenses = json.loads(
        pkg_resources.resource_string(__name__, "licenses.json").decode("utf-8")
    )
    index.update(licenses.items())
    for name, versions in licenses.items():
        for version, details in versions.items():
            license = name, version
            uris[details["uri"]] = license
            for feature in details.get("features", []):
                features[feature].append(license)