|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations |
|
|
|
|
|
import re |
|
|
from typing import NewType, cast |
|
|
|
|
|
from packaging.licenses._spdx import EXCEPTIONS, LICENSES |
|
|
|
|
|
__all__ = [ |
|
|
"InvalidLicenseExpression", |
|
|
"NormalizedLicenseExpression", |
|
|
"canonicalize_license_expression", |
|
|
] |
|
|
|
|
|
license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$") |
|
|
|
|
|
NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str) |
|
|
|
|
|
|
|
|
class InvalidLicenseExpression(ValueError): |
|
|
"""Raised when a license-expression string is invalid |
|
|
|
|
|
>>> canonicalize_license_expression("invalid") |
|
|
Traceback (most recent call last): |
|
|
... |
|
|
packaging.licenses.InvalidLicenseExpression: Invalid license expression: 'invalid' |
|
|
""" |
|
|
|
|
|
|
|
|
def canonicalize_license_expression( |
|
|
raw_license_expression: str, |
|
|
) -> NormalizedLicenseExpression: |
|
|
if not raw_license_expression: |
|
|
message = f"Invalid license expression: {raw_license_expression!r}" |
|
|
raise InvalidLicenseExpression(message) |
|
|
|
|
|
|
|
|
|
|
|
license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ") |
|
|
licenseref_prefix = "LicenseRef-" |
|
|
license_refs = { |
|
|
ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :] |
|
|
for ref in license_expression.split() |
|
|
if ref.lower().startswith(licenseref_prefix.lower()) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
license_expression = license_expression.lower() |
|
|
|
|
|
tokens = license_expression.split() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
python_tokens = [] |
|
|
for token in tokens: |
|
|
if token not in {"or", "and", "with", "(", ")"}: |
|
|
python_tokens.append("False") |
|
|
elif token == "with": |
|
|
python_tokens.append("or") |
|
|
elif token == "(" and python_tokens and python_tokens[-1] not in {"or", "and"}: |
|
|
message = f"Invalid license expression: {raw_license_expression!r}" |
|
|
raise InvalidLicenseExpression(message) |
|
|
else: |
|
|
python_tokens.append(token) |
|
|
|
|
|
python_expression = " ".join(python_tokens) |
|
|
try: |
|
|
invalid = eval(python_expression, globals(), locals()) |
|
|
except Exception: |
|
|
invalid = True |
|
|
|
|
|
if invalid is not False: |
|
|
message = f"Invalid license expression: {raw_license_expression!r}" |
|
|
raise InvalidLicenseExpression(message) from None |
|
|
|
|
|
|
|
|
normalized_tokens = [] |
|
|
for token in tokens: |
|
|
if token in {"or", "and", "with", "(", ")"}: |
|
|
normalized_tokens.append(token.upper()) |
|
|
continue |
|
|
|
|
|
if normalized_tokens and normalized_tokens[-1] == "WITH": |
|
|
if token not in EXCEPTIONS: |
|
|
message = f"Unknown license exception: {token!r}" |
|
|
raise InvalidLicenseExpression(message) |
|
|
|
|
|
normalized_tokens.append(EXCEPTIONS[token]["id"]) |
|
|
else: |
|
|
if token.endswith("+"): |
|
|
final_token = token[:-1] |
|
|
suffix = "+" |
|
|
else: |
|
|
final_token = token |
|
|
suffix = "" |
|
|
|
|
|
if final_token.startswith("licenseref-"): |
|
|
if not license_ref_allowed.match(final_token): |
|
|
message = f"Invalid licenseref: {final_token!r}" |
|
|
raise InvalidLicenseExpression(message) |
|
|
normalized_tokens.append(license_refs[final_token] + suffix) |
|
|
else: |
|
|
if final_token not in LICENSES: |
|
|
message = f"Unknown license: {final_token!r}" |
|
|
raise InvalidLicenseExpression(message) |
|
|
normalized_tokens.append(LICENSES[final_token]["id"] + suffix) |
|
|
|
|
|
normalized_expression = " ".join(normalized_tokens) |
|
|
|
|
|
return cast( |
|
|
NormalizedLicenseExpression, |
|
|
normalized_expression.replace("( ", "(").replace(" )", ")"), |
|
|
) |
|
|
|