#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
use std::fmt;
use serde::{Deserialize, Serialize};
use zng_txt::Txt;
#[derive(Serialize, Deserialize, Clone)]
pub struct LicenseUsed {
pub license: License,
pub used_by: Vec<User>,
}
impl fmt::Debug for LicenseUsed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("License")
.field("license.id", &self.license.id)
.field("used_by", &self.used_by)
.finish_non_exhaustive()
}
}
impl LicenseUsed {
pub fn user_licenses(&self) -> Vec<UserLicense> {
self.used_by
.iter()
.map(|u| UserLicense {
user: u.clone(),
license: self.license.clone(),
})
.collect()
}
}
pub fn user_licenses(licenses: &[LicenseUsed]) -> Vec<UserLicense> {
let mut r: Vec<_> = licenses.iter().flat_map(|l| l.user_licenses()).collect();
r.sort_by(|a, b| a.user.name.cmp(&b.user.name));
r
}
#[derive(Clone, PartialEq, Eq)]
pub struct UserLicense {
pub user: User,
pub license: License,
}
impl fmt::Debug for UserLicense {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UserLicense")
.field("user", &self.user)
.field("license.id", &self.license.id)
.finish()
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Hash)]
pub struct License {
pub id: Txt,
pub name: Txt,
pub text: Txt,
}
#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug, Clone)]
pub struct User {
pub name: Txt,
#[serde(default)]
pub version: Txt,
#[serde(default)]
pub url: Txt,
}
pub fn merge_licenses(into: &mut Vec<LicenseUsed>, licenses: Vec<LicenseUsed>) {
for license in licenses {
if let Some(l) = into.iter_mut().find(|l| l.license == license.license) {
for user in license.used_by {
if !l.used_by.contains(&user) {
l.used_by.push(user);
}
}
} else {
into.push(license);
}
}
}
pub fn sort_licenses(l: &mut Vec<LicenseUsed>) {
l.sort_by(|a, b| a.license.name.cmp(&b.license.name));
for l in l {
l.used_by.sort_by(|a, b| a.name.cmp(&b.name));
}
}
#[cfg(feature = "build")]
pub fn collect_cargo_about(about_cfg_path: &str) -> Vec<LicenseUsed> {
if std::env::var("DOCS_RS").is_ok() || std::env::var("ZNG_TP_LICENSES").unwrap_or_default() == "false" {
return vec![];
}
let mut cargo_about = std::process::Command::new("cargo");
cargo_about
.arg("about")
.arg("generate")
.arg("--format")
.arg("json")
.arg("--all-features");
if !about_cfg_path.is_empty() {
cargo_about.arg("-c").arg(about_cfg_path);
}
let output = cargo_about.output().expect("error calling `cargo about`");
let error = String::from_utf8(output.stderr).unwrap();
assert!(
output.status.success(),
"error code calling `cargo about`, {:?}\nstderr:\n{error}",
output.status
);
let json = String::from_utf8(output.stdout).unwrap();
parse_cargo_about(&json).expect("error parsing `cargo about` output")
}
#[cfg(feature = "build")]
pub fn parse_cargo_about(json: &str) -> Result<Vec<LicenseUsed>, serde_json::Error> {
#[derive(Deserialize)]
struct Output {
licenses: Vec<LicenseJson>,
}
#[derive(Deserialize)]
struct LicenseJson {
id: Txt,
name: Txt,
text: Txt,
used_by: Vec<UsedBy>,
}
impl LicenseJson {
fn into(self) -> LicenseUsed {
LicenseUsed {
license: License {
id: self.id,
name: self.name,
text: self.text,
},
used_by: self.used_by.into_iter().map(UsedBy::into).collect(),
}
}
}
#[derive(Deserialize)]
struct UsedBy {
#[serde(rename = "crate")]
crate_: Crate,
}
#[derive(Deserialize)]
struct Crate {
name: Txt,
version: Txt,
#[serde(default)]
repository: Option<Txt>,
}
impl UsedBy {
fn into(self) -> User {
let repo = self.crate_.repository.unwrap_or_default();
User {
version: self.crate_.version,
url: if repo.is_empty() {
zng_txt::formatx!("https://crates.io/crates/{}", self.crate_.name)
} else {
repo
},
name: self.crate_.name,
}
}
}
serde_json::from_str::<Output>(json).map(|o| o.licenses.into_iter().map(LicenseJson::into).collect())
}
#[cfg(feature = "build")]
pub fn encode_licenses(licenses: &[LicenseUsed]) -> Vec<u8> {
deflate::deflate_bytes(&bincode::serialize(licenses).expect("bincode error"))
}
#[cfg(feature = "build")]
pub fn write_bundle(licenses: &[LicenseUsed]) {
let bin = encode_licenses(licenses);
std::fs::write(format!("{}/zng-tp-licenses.bin", std::env::var("OUT_DIR").unwrap()), bin).expect("error writing file");
}
#[macro_export]
#[cfg(feature = "bundle")]
macro_rules! include_bundle {
() => {
$crate::include_bundle!(concat!(env!("OUT_DIR"), "/zng-tp-licenses.bin"))
};
($custom_name:expr) => {{
$crate::decode_licenses(include_bytes!($custom_name))
}};
}
#[cfg(feature = "bundle")]
pub fn decode_licenses(bin: &[u8]) -> Vec<LicenseUsed> {
let bin = inflate::inflate_bytes(bin).expect("invalid bundle deflate binary");
bincode::deserialize(&bin).expect("invalid bundle bincode binary")
}