#![deny(unsafe_code)]
#![warn(
clippy::all,
clippy::await_holding_lock,
clippy::dbg_macro,
clippy::debug_assert_with_mut_call,
clippy::doc_markdown,
clippy::empty_enum,
clippy::enum_glob_use,
clippy::exit,
clippy::explicit_into_iter_loop,
clippy::filter_map_next,
clippy::fn_params_excessive_bools,
clippy::if_let_mutex,
clippy::imprecise_flops,
clippy::inefficient_to_string,
clippy::large_types_passed_by_value,
clippy::let_unit_value,
clippy::linkedlist,
clippy::lossy_float_literal,
clippy::macro_use_imports,
clippy::map_err_ignore,
clippy::map_flatten,
clippy::map_unwrap_or,
clippy::match_on_vec_items,
clippy::match_same_arms,
clippy::match_wildcard_for_single_variants,
clippy::mem_forget,
clippy::mismatched_target_os,
clippy::needless_borrow,
clippy::needless_continue,
clippy::option_option,
clippy::pub_enum_variant_names,
clippy::ref_option_ref,
clippy::rest_pat_in_fully_bound_structs,
clippy::string_add_assign,
clippy::string_add,
clippy::string_to_string,
clippy::suboptimal_flops,
clippy::todo,
clippy::unimplemented,
clippy::unnested_or_patterns,
clippy::unused_self,
clippy::verbose_file_reads,
future_incompatible,
nonstandard_style,
rust_2018_idioms
)]
pub mod error;
pub mod expression;
pub mod identifiers;
pub mod lexer;
mod licensee;
pub use error::ParseError;
pub use expression::Expression;
use identifiers::{IS_COPYLEFT, IS_DEPRECATED, IS_FSF_LIBRE, IS_GNU, IS_OSI_APPROVED};
pub use lexer::ParseMode;
pub use licensee::Licensee;
use std::{cmp, fmt};
#[derive(Copy, Clone, Eq)]
pub struct LicenseId {
pub name: &'static str,
pub full_name: &'static str,
index: usize,
flags: u8,
}
impl PartialEq for LicenseId {
#[inline]
fn eq(&self, o: &Self) -> bool {
self.index == o.index
}
}
impl Ord for LicenseId {
#[inline]
fn cmp(&self, o: &Self) -> cmp::Ordering {
self.index.cmp(&o.index)
}
}
impl PartialOrd for LicenseId {
#[inline]
fn partial_cmp(&self, o: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(o))
}
}
impl LicenseId {
#[inline]
pub fn is_fsf_free_libre(self) -> bool {
self.flags & IS_FSF_LIBRE != 0
}
#[inline]
pub fn is_osi_approved(self) -> bool {
self.flags & IS_OSI_APPROVED != 0
}
#[inline]
pub fn is_deprecated(self) -> bool {
self.flags & IS_DEPRECATED != 0
}
#[inline]
pub fn is_copyleft(self) -> bool {
self.flags & IS_COPYLEFT != 0
}
#[inline]
pub fn is_gnu(self) -> bool {
self.flags & IS_GNU != 0
}
}
impl fmt::Debug for LicenseId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
#[derive(Copy, Clone, Eq)]
pub struct ExceptionId {
pub name: &'static str,
index: usize,
flags: u8,
}
impl PartialEq for ExceptionId {
#[inline]
fn eq(&self, o: &Self) -> bool {
self.index == o.index
}
}
impl Ord for ExceptionId {
#[inline]
fn cmp(&self, o: &Self) -> cmp::Ordering {
self.index.cmp(&o.index)
}
}
impl PartialOrd for ExceptionId {
#[inline]
fn partial_cmp(&self, o: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(o))
}
}
impl ExceptionId {
#[inline]
pub fn is_deprecated(self) -> bool {
self.flags & IS_DEPRECATED != 0
}
}
impl fmt::Debug for ExceptionId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct LicenseReq {
pub license: LicenseItem,
pub exception: Option<ExceptionId>,
}
impl From<LicenseId> for LicenseReq {
fn from(id: LicenseId) -> Self {
let mut or_later = false;
let id = if id.is_gnu() {
let root = if id.name.ends_with("-or-later") {
or_later = true;
&id.name[..id.name.len() - 9]
} else if id.name.ends_with("-only") {
&id.name[..id.name.len() - 5]
} else {
id.name
};
license_id(root).expect("Unable to find root GNU license")
} else {
id
};
Self {
license: LicenseItem::Spdx { id, or_later },
exception: None,
}
}
}
impl fmt::Display for LicenseReq {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
self.license.fmt(f)?;
if let Some(ref exe) = self.exception {
write!(f, " WITH {}", exe.name)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Eq)]
pub enum LicenseItem {
Spdx {
id: LicenseId,
or_later: bool,
},
Other {
doc_ref: Option<String>,
lic_ref: String,
},
}
impl LicenseItem {
pub fn id(&self) -> Option<LicenseId> {
match self {
Self::Spdx { id, .. } => Some(*id),
_ => None,
}
}
}
impl Ord for LicenseItem {
fn cmp(&self, o: &Self) -> cmp::Ordering {
match (self, o) {
(
Self::Spdx {
id: a,
or_later: la,
},
Self::Spdx {
id: b,
or_later: lb,
},
) => match a.cmp(b) {
cmp::Ordering::Equal => la.cmp(lb),
o => o,
},
(
Self::Other {
doc_ref: ad,
lic_ref: al,
},
Self::Other {
doc_ref: bd,
lic_ref: bl,
},
) => match ad.cmp(bd) {
cmp::Ordering::Equal => al.cmp(bl),
o => o,
},
(Self::Spdx { .. }, Self::Other { .. }) => cmp::Ordering::Less,
(Self::Other { .. }, Self::Spdx { .. }) => cmp::Ordering::Greater,
}
}
}
impl PartialOrd for LicenseItem {
fn partial_cmp(&self, o: &Self) -> Option<cmp::Ordering> {
match (self, o) {
(Self::Spdx { id: a, .. }, Self::Spdx { id: b, .. }) => a.partial_cmp(b),
(
Self::Other {
doc_ref: ad,
lic_ref: al,
},
Self::Other {
doc_ref: bd,
lic_ref: bl,
},
) => match ad.cmp(bd) {
cmp::Ordering::Equal => al.partial_cmp(bl),
o => Some(o),
},
(Self::Spdx { .. }, Self::Other { .. }) => Some(cmp::Ordering::Less),
(Self::Other { .. }, Self::Spdx { .. }) => Some(cmp::Ordering::Greater),
}
}
}
impl PartialEq for LicenseItem {
fn eq(&self, o: &Self) -> bool {
matches!(self.partial_cmp(o), Some(cmp::Ordering::Equal))
}
}
impl fmt::Display for LicenseItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
LicenseItem::Spdx { id, or_later } => {
id.name.fmt(f)?;
if *or_later {
f.write_str("+")?;
}
Ok(())
}
LicenseItem::Other {
doc_ref: Some(d),
lic_ref: l,
} => write!(f, "DocumentRef-{}:LicenseRef-{}", d, l),
LicenseItem::Other {
doc_ref: None,
lic_ref: l,
} => write!(f, "LicenseRef-{}", l),
}
}
}
#[inline]
pub fn license_id(name: &str) -> Option<LicenseId> {
let name = name.trim_end_matches('+');
identifiers::LICENSES
.binary_search_by(|lic| lic.0.cmp(name))
.map(|index| {
let (name, full_name, flags) = identifiers::LICENSES[index];
LicenseId {
name,
full_name,
index,
flags,
}
})
.ok()
}
#[inline]
pub fn imprecise_license_id(name: &str) -> Option<(LicenseId, usize)> {
for (prefix, correct_name) in identifiers::IMPRECISE_NAMES {
if let Some(name_prefix) = name.as_bytes().get(0..prefix.len()) {
if prefix.as_bytes().eq_ignore_ascii_case(name_prefix) {
let mut len = prefix.len();
if name.as_bytes().get(len).copied() == Some(b'+') {
len += 1;
}
return license_id(correct_name).map(|lic| (lic, len));
}
}
}
None
}
#[inline]
pub fn exception_id(name: &str) -> Option<ExceptionId> {
identifiers::EXCEPTIONS
.binary_search_by(|exc| exc.0.cmp(name))
.map(|index| {
let (name, flags) = identifiers::EXCEPTIONS[index];
ExceptionId { name, index, flags }
})
.ok()
}
#[inline]
pub fn license_version() -> &'static str {
identifiers::VERSION
}