///|
/// Normalize common aliases to SPDX identifiers.
pub fn normalize_license(identifier : StringView) -> String {
match identifier.trim().to_upper() {
"APACHE 2" | "APACHE-2" | "APACHE2" | "APACHE-2.0" => "Apache-2.0"
"MIT" | "MIT LICENSE" => "MIT"
"BSD-2" | "BSD 2-CLAUSE" | "BSD-2-CLAUSE" => "BSD-2-Clause"
"BSD-3" | "BSD 3-CLAUSE" | "BSD-3-CLAUSE" => "BSD-3-Clause"
"MPL-2" | "MPL-2.0" => "MPL-2.0"
"GPL-2" | "GPL-2.0" | "GPL-2.0-ONLY" => "GPL-2.0-only"
"GPL-2.0+" | "GPL-2.0-OR-LATER" => "GPL-2.0-or-later"
"GPL-3" | "GPL-3.0" | "GPL-3.0-ONLY" => "GPL-3.0-only"
"GPL-3.0+" | "GPL-3.0-OR-LATER" => "GPL-3.0-or-later"
"LGPL-2.1" | "LGPL-2.1-ONLY" => "LGPL-2.1-only"
"LGPL-2.1+" | "LGPL-2.1-OR-LATER" => "LGPL-2.1-or-later"
"LGPL-3" | "LGPL-3.0" | "LGPL-3.0-ONLY" => "LGPL-3.0-only"
"LGPL-3.0+" | "LGPL-3.0-OR-LATER" => "LGPL-3.0-or-later"
"AGPL-3" | "AGPL-3.0" | "AGPL-3.0-ONLY" => "AGPL-3.0-only"
"AGPL-3.0+" | "AGPL-3.0-OR-LATER" => "AGPL-3.0-or-later"
"EPL-2" | "EPL-2.0" => "EPL-2.0"
"EUPL-1.2" => "EUPL-1.2"
"ISC" => "ISC"
"ZLIB" => "Zlib"
"UNLICENSE" => "Unlicense"
value => value.to_owned()
}
}
///|
fn is_known_spdx_atom(identifier : StringView) -> Bool {
match normalize_license(identifier) {
"Apache-2.0"
| "MIT"
| "BSD-2-Clause"
| "BSD-3-Clause"
| "MPL-2.0"
| "GPL-2.0-only"
| "GPL-2.0-or-later"
| "GPL-3.0-only"
| "GPL-3.0-or-later"
| "LGPL-2.1-only"
| "LGPL-2.1-or-later"
| "LGPL-3.0-only"
| "LGPL-3.0-or-later"
| "AGPL-3.0-only"
| "AGPL-3.0-or-later"
| "EPL-2.0"
| "EUPL-1.2"
| "ISC"
| "Zlib"
| "Unlicense"
| "CC0-1.0"
| "BSL-1.0" => true
_ => false
}
}
///|
fn is_known_spdx_exception(identifier : StringView) -> Bool {
match identifier.trim() {
"Classpath-exception-2.0"
| "LLVM-exception"
| "Autoconf-exception-3.0"
| "GCC-exception-3.1" => true
_ => false
}
}
///|
fn strip_parens(value : StringView) -> StringView {
let value = value.trim()
if value.has_prefix("(") && value.has_suffix(")") && value.length() >= 2 {
value[1:value.length() - 1].trim()
} else {
value
}
}
///|
/// Validate a practical subset of SPDX expressions: identifiers joined by
/// `AND` or `OR`, with optional grouping parentheses.
pub fn valid_spdx(expression : StringView) -> Bool {
let expression = strip_parens(expression)
guard !expression.is_empty() else { return false }
if expression.contains(" AND ") {
for part in expression.split(" AND ") {
guard valid_spdx(part) else { return false }
}
return true
}
if expression.contains(" OR ") {
for part in expression.split(" OR ") {
guard valid_spdx(part) else { return false }
}
return true
}
match expression.split_once(" WITH ") {
Some((license, exception)) =>
return is_known_spdx_atom(license) && is_known_spdx_exception(exception)
None => ()
}
is_known_spdx_atom(expression)
}
///|
/// Identify common license texts without relying on file names.
pub fn detect_license(text : StringView) -> String? {
let upper = text.to_upper()
if upper.contains("APACHE LICENSE") &&
upper.contains("VERSION 2.0, JANUARY 2004") {
Some("Apache-2.0")
} else if upper.contains("PERMISSION IS HEREBY GRANTED, FREE OF CHARGE") &&
upper.contains("THE SOFTWARE IS PROVIDED \"AS IS\"") {
Some("MIT")
} else if upper.contains("MOZILLA PUBLIC LICENSE") &&
upper.contains("VERSION 2.0") {
Some("MPL-2.0")
} else if upper.contains("ECLIPSE PUBLIC LICENSE") &&
(upper.contains("VERSION 2.0") || upper.contains("- V 2.0")) {
Some("EPL-2.0")
} else if upper.contains("GNU AFFERO GENERAL PUBLIC LICENSE") &&
upper.contains("VERSION 3") {
Some("AGPL-3.0-only")
} else if upper.contains("GNU GENERAL PUBLIC LICENSE") &&
upper.contains("VERSION 3") {
Some("GPL-3.0-only")
} else if upper.contains("GNU GENERAL PUBLIC LICENSE") &&
upper.contains("VERSION 2") {
Some("GPL-2.0-only")
} else if upper.contains("REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS") &&
upper.contains("NEITHER THE NAME") {
Some("BSD-3-Clause")
} else if upper.contains("REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS") {
Some("BSD-2-Clause")
} else if upper.contains("ISC LICENSE") ||
(
upper.contains("PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE") &&
upper.contains("THE SOFTWARE IS PROVIDED \"AS IS\"")
) {
Some("ISC")
} else if upper.contains("THIS SOFTWARE IS PROVIDED 'AS-IS'") &&
upper.contains("ALTERED SOURCE VERSIONS MUST BE PLAINLY MARKED") {
Some("Zlib")
} else if upper.contains("CC0 1.0 UNIVERSAL") &&
upper.contains("PUBLIC DOMAIN") {
Some("CC0-1.0")
} else {
None
}
}