new: drive the license menu, parsing and sniffing from data/licenses.yml
This commit is contained in:
+86
-19
@@ -15,6 +15,7 @@ use crate::debian::DebianVersion;
|
||||
use crate::debian::deps::{Deps, ParseOpts};
|
||||
use crate::distro_info;
|
||||
use crate::new::detect::{self, Detection};
|
||||
use crate::new::licenses;
|
||||
use crate::new::origin::{Forge, GitOrigin};
|
||||
use crate::new::templates;
|
||||
|
||||
@@ -213,6 +214,10 @@ impl OrigOrigin {
|
||||
|
||||
/// Upstream license of the package: a curated SPDX list plus a free-text
|
||||
/// fallback for anything else (including "unknown" until the user picks one).
|
||||
/// The curated identifiers, their accepted spellings and the menu labels
|
||||
/// come from the bundled license table (`data/licenses.yml`); this enum
|
||||
/// stays code because [`NewOptions`] and the template rendering match on
|
||||
/// its variants — a consistency test keeps the two in lockstep.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum License {
|
||||
/// MIT
|
||||
@@ -239,21 +244,34 @@ pub enum License {
|
||||
}
|
||||
|
||||
impl License {
|
||||
/// Map a license string to a [`License`]: curated SPDX identifiers are
|
||||
/// matched case-insensitively, anything else becomes
|
||||
/// [`License::Custom`] verbatim.
|
||||
/// Map a license string to a [`License`]: the curated SPDX identifiers
|
||||
/// of the bundled license table (`data/licenses.yml`, matched through
|
||||
/// their spellings, case-insensitively) become their variant,
|
||||
/// anything else becomes [`License::Custom`] verbatim (lowercased).
|
||||
pub fn parse(s: &str) -> License {
|
||||
match s.to_ascii_lowercase().as_str() {
|
||||
"mit" => License::Mit,
|
||||
"apache-2.0" => License::Apache2,
|
||||
"gpl-2.0+" => License::Gpl2Plus,
|
||||
"gpl-3.0+" => License::Gpl3Plus,
|
||||
"lgpl-2.1+" => License::Lgpl21Plus,
|
||||
"lgpl-3.0+" => License::Lgpl3Plus,
|
||||
"bsd-2-clause" => License::Bsd2Clause,
|
||||
"bsd-3-clause" => License::Bsd3Clause,
|
||||
"isc" => License::Isc,
|
||||
other => License::Custom(other.to_string()),
|
||||
match licenses::entry_for_spelling(s) {
|
||||
Some(entry) => Self::from_spdx(&entry.id)
|
||||
.expect("licenses.yml ids and License variants are kept in sync by a test"),
|
||||
None => License::Custom(s.to_ascii_lowercase()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The variant of a curated license's SPDX identifier — the inverse of
|
||||
/// [`License::spdx`] for the non-`Custom` variants, `None` for any
|
||||
/// other string. The ids accepted here are locked to the bundled
|
||||
/// license table by a consistency test.
|
||||
fn from_spdx(id: &str) -> Option<License> {
|
||||
match id {
|
||||
"MIT" => Some(License::Mit),
|
||||
"Apache-2.0" => Some(License::Apache2),
|
||||
"GPL-2.0+" => Some(License::Gpl2Plus),
|
||||
"GPL-3.0+" => Some(License::Gpl3Plus),
|
||||
"LGPL-2.1+" => Some(License::Lgpl21Plus),
|
||||
"LGPL-3.0+" => Some(License::Lgpl3Plus),
|
||||
"BSD-2-Clause" => Some(License::Bsd2Clause),
|
||||
"BSD-3-Clause" => Some(License::Bsd3Clause),
|
||||
"ISC" => Some(License::Isc),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,12 +292,11 @@ impl License {
|
||||
}
|
||||
|
||||
/// SPDX license data page URL (without the trailing `+` of the
|
||||
/// "or later" spellings), for the copyright reference paragraph.
|
||||
/// "or later" spellings), for the copyright reference paragraph: the
|
||||
/// identifier substituted into the URL template of the bundled
|
||||
/// license table.
|
||||
pub fn spdx_url(&self) -> String {
|
||||
format!(
|
||||
"https://spdx.org/licenses/{}.html",
|
||||
self.spdx().trim_end_matches('+')
|
||||
)
|
||||
licenses::url_template().replace("{id}", self.spdx().trim_end_matches('+'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1177,6 +1194,56 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// The bundled license table and the `License` enum are two halves of
|
||||
/// one list and must never drift apart: every entry id must map to a
|
||||
/// variant (and the entry must accept its own id as a spelling, so
|
||||
/// the menu answers parse back), every variant must have an entry,
|
||||
/// and the counts must agree. The hardcoded variant list here is the
|
||||
/// lock: adding a variant or a YAML entry without the other half
|
||||
/// fails this test.
|
||||
#[test]
|
||||
fn licenses_table_and_enum_are_in_sync() {
|
||||
let variants = [
|
||||
License::Mit,
|
||||
License::Apache2,
|
||||
License::Gpl2Plus,
|
||||
License::Gpl3Plus,
|
||||
License::Lgpl21Plus,
|
||||
License::Lgpl3Plus,
|
||||
License::Bsd2Clause,
|
||||
License::Bsd3Clause,
|
||||
License::Isc,
|
||||
];
|
||||
assert_eq!(
|
||||
licenses::entries().len(),
|
||||
variants.len(),
|
||||
"licenses.yml and the License enum carry a different number of licenses"
|
||||
);
|
||||
for variant in variants {
|
||||
let entry = licenses::entries()
|
||||
.iter()
|
||||
.find(|entry| entry.id == variant.spdx())
|
||||
.unwrap_or_else(|| panic!("no licenses.yml entry for '{}'", variant.spdx()));
|
||||
assert_eq!(entry.id, variant.spdx());
|
||||
assert!(
|
||||
entry
|
||||
.spellings
|
||||
.iter()
|
||||
.any(|s| s.eq_ignore_ascii_case(&entry.id)),
|
||||
"entry '{}' must accept its own id as a spelling",
|
||||
entry.id
|
||||
);
|
||||
// The menu label parses back into the same variant.
|
||||
assert_eq!(License::parse(&entry.menu), variant);
|
||||
}
|
||||
for entry in licenses::entries() {
|
||||
let variant = License::from_spdx(&entry.id).unwrap_or_else(|| {
|
||||
panic!("licenses.yml entry '{}' has no License variant", entry.id)
|
||||
});
|
||||
assert_eq!(variant.spdx(), entry.id);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collision_detection() {
|
||||
assert!(check_file_collisions(&["a".into(), "b".into()]).is_ok());
|
||||
|
||||
Reference in New Issue
Block a user