1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9
10use std::fmt;
11
12use serde::{Deserialize, Serialize};
13use zng_txt::Txt;
14
15#[derive(Serialize, Deserialize, Clone)]
17pub struct LicenseUsed {
18 pub license: License,
20 pub used_by: Vec<User>,
22}
23impl fmt::Debug for LicenseUsed {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 f.debug_struct("License")
26 .field("license.id", &self.license.id)
27 .field("used_by", &self.used_by)
28 .finish_non_exhaustive()
29 }
30}
31impl LicenseUsed {
32 pub fn user_licenses(&self) -> Vec<UserLicense> {
34 self.used_by
35 .iter()
36 .map(|u| UserLicense {
37 user: u.clone(),
38 license: self.license.clone(),
39 })
40 .collect()
41 }
42}
43
44pub fn user_licenses(licenses: &[LicenseUsed]) -> Vec<UserLicense> {
46 let mut r: Vec<_> = licenses.iter().flat_map(|l| l.user_licenses()).collect();
47 r.sort_by(|a, b| a.user.name.cmp(&b.user.name));
48 r
49}
50
51#[derive(Clone, PartialEq, Eq)]
53pub struct UserLicense {
54 pub user: User,
56 pub license: License,
58}
59impl fmt::Debug for UserLicense {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 f.debug_struct("UserLicense")
62 .field("user", &self.user)
63 .field("license.id", &self.license.id)
64 .finish()
65 }
66}
67
68#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Hash)]
70#[non_exhaustive]
71pub struct License {
72 pub id: Txt,
76 pub name: Txt,
78 pub text: Txt,
80}
81
82impl License {
83 pub fn new(id: impl Into<Txt>, name: impl Into<Txt>, text: impl Into<Txt>) -> Self {
85 Self {
86 id: id.into(),
87 name: name.into(),
88 text: text.into(),
89 }
90 }
91}
92
93#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Debug, Clone)]
95#[non_exhaustive]
96pub struct User {
97 pub name: Txt,
99 #[serde(default)]
101 pub version: Txt,
102 #[serde(default)]
104 pub url: Txt,
105}
106impl User {
107 pub fn new(name: impl Into<Txt>, version: impl Into<Txt>, url: impl Into<Txt>) -> Self {
109 Self {
110 name: name.into(),
111 version: version.into(),
112 url: url.into(),
113 }
114 }
115}
116
117pub fn merge_licenses(into: &mut Vec<LicenseUsed>, licenses: Vec<LicenseUsed>) {
121 for license in licenses {
122 if let Some(l) = into.iter_mut().find(|l| l.license == license.license) {
123 for user in license.used_by {
124 if !l.used_by.contains(&user) {
125 l.used_by.push(user);
126 }
127 }
128 } else {
129 into.push(license);
130 }
131 }
132}
133
134pub fn sort_licenses(l: &mut Vec<LicenseUsed>) {
136 l.sort_by(|a, b| a.license.name.cmp(&b.license.name));
137 for l in l {
138 l.used_by.sort_by(|a, b| a.name.cmp(&b.name));
139 }
140}
141
142#[cfg(feature = "build")]
155pub fn collect_cargo_about(about_cfg_path: &str) -> Vec<LicenseUsed> {
156 if std::env::var("DOCS_RS").is_ok() || std::env::var("ZNG_TP_LICENSES").unwrap_or_default() == "false" {
157 return vec![];
158 }
159
160 let mut cargo_about = std::process::Command::new("cargo");
161 cargo_about
162 .arg("about")
163 .arg("generate")
164 .arg("--format")
165 .arg("json")
166 .arg("--all-features");
167
168 #[cfg(windows)]
170 let temp_file = tempfile::NamedTempFile::new().expect("cannot crate temp file for windows output");
171 #[cfg(windows)]
172 {
173 cargo_about.arg("--output-file").arg(temp_file.path());
174 }
175
176 if !about_cfg_path.is_empty() {
177 cargo_about.arg("-c").arg(about_cfg_path);
178 }
179
180 let output = cargo_about.output().expect("error calling `cargo about`");
181 let error = String::from_utf8(output.stderr).unwrap();
182 assert!(
183 output.status.success(),
184 "error code calling `cargo about`, {:?}\nstderr:\n{error}",
185 output.status
186 );
187
188 #[cfg(windows)]
189 let json = std::fs::read_to_string(temp_file.path()).expect("cannot read temp file with windows output");
190 #[cfg(not(windows))]
191 let json = String::from_utf8(output.stdout).unwrap();
192
193 parse_cargo_about(&json).expect("error parsing `cargo about` output")
194}
195
196#[cfg(feature = "build")]
208pub fn parse_cargo_about(json: &str) -> Result<Vec<LicenseUsed>, serde_json::Error> {
209 #[derive(Deserialize)]
210 struct Output {
211 licenses: Vec<LicenseJson>,
212 }
213 #[derive(Deserialize)]
214 struct LicenseJson {
215 id: Txt,
216 name: Txt,
217 text: Txt,
218 used_by: Vec<UsedBy>,
219 }
220 impl LicenseJson {
221 fn into(self) -> LicenseUsed {
222 LicenseUsed {
223 license: License {
224 id: self.id,
225 name: self.name,
226 text: self.text,
227 },
228 used_by: self.used_by.into_iter().map(UsedBy::into).collect(),
229 }
230 }
231 }
232 #[derive(Deserialize)]
233 struct UsedBy {
234 #[serde(rename = "crate")]
235 crate_: Crate,
236 }
237 #[derive(Deserialize)]
238 struct Crate {
239 name: Txt,
240 version: Txt,
241 #[serde(default)]
242 repository: Option<Txt>,
243 }
244 impl UsedBy {
245 fn into(self) -> User {
246 let repo = self.crate_.repository.unwrap_or_default();
247 User {
248 version: self.crate_.version,
249 url: if repo.is_empty() {
250 zng_txt::formatx!("https://crates.io/crates/{}", self.crate_.name)
251 } else {
252 repo
253 },
254 name: self.crate_.name,
255 }
256 }
257 }
258
259 serde_json::from_str::<Output>(json).map(|o| o.licenses.into_iter().map(LicenseJson::into).collect())
260}
261
262#[cfg(feature = "build")]
268pub fn encode_licenses(licenses: &[LicenseUsed]) -> Vec<u8> {
269 deflate::deflate_bytes(&bincode::serde::encode_to_vec(licenses, bincode::config::standard()).expect("bincode error"))
270}
271
272#[cfg(feature = "build")]
278pub fn write_bundle(licenses: &[LicenseUsed]) {
279 let bin = encode_licenses(licenses);
280 std::fs::write(format!("{}/zng-tp-licenses.bin", std::env::var("OUT_DIR").unwrap()), bin).expect("error writing file");
281}
282
283#[macro_export]
287#[cfg(feature = "bundle")]
288macro_rules! include_bundle {
289 () => {
290 $crate::include_bundle!(concat!(env!("OUT_DIR"), "/zng-tp-licenses.bin"))
291 };
292 ($custom_name:expr) => {{ $crate::decode_licenses(include_bytes!($custom_name)) }};
293}
294
295#[cfg(feature = "bundle")]
298pub fn decode_licenses(bin: &[u8]) -> Vec<LicenseUsed> {
299 let bin = inflate::inflate_bytes(bin).expect("invalid bundle deflate binary");
300 bincode::serde::decode_from_slice(&bin, bincode::config::standard())
301 .expect("invalid bundle bincode binary")
302 .0
303}