lib_cargo_crate/
info.rs

1use crate::api_reponse::*;
2use crate::info_opts::InfoOpts;
3use crate::WrappedVersion;
4use chrono_humanize::HumanTime;
5use crates_io_api::{Crate, CratesQuery, Error, Sort, SyncClient};
6use std::time::Duration;
7
8pub struct Info {
9	client: SyncClient,
10}
11
12impl Default for Info {
13	fn default() -> Self {
14		Info::new()
15	}
16}
17
18impl Info {
19	pub fn new() -> Self {
20		let client = SyncClient::new("cargo-crate", Duration::from_millis(10)).expect("failed getting client");
21
22		Self { client }
23	}
24
25	/// Fetch all the information about the crate
26	pub fn fetch(&self, crates: Vec<&str>, _opts: &InfoOpts) -> anyhow::Result<Vec<ApiResponse>> {
27		// TODO: check out full_crate
28		Ok(crates
29			.iter()
30			.map(|krate| {
31				let response: Result<ApiResponse, Error> =
32					Ok(ApiResponse { krate: self.client.get_crate(krate)?, owners: self.client.crate_owners(krate)? });
33				response
34			})
35			.collect::<Result<Vec<_>, _>>()?)
36	}
37
38	pub fn show(response: Vec<ApiResponse>, opts: &InfoOpts) {
39		if opts.json {
40			Info::show_json(&response);
41		} else {
42			Info::show_txt(&response, Some(opts));
43		}
44	}
45
46	/// Print a formatted output to the console.
47	pub fn show_txt(response: &[ApiResponse], opts: Option<&InfoOpts>) {
48		let col_size = 16;
49		let emoji_size = col_size - 1;
50		response.iter().for_each(|r| {
51			println!("{:<emoji_size$} {:<}", "🦀 Crate:", r.krate.crate_data.name,);
52
53			if let Some(h) = r.krate.crate_data.homepage.as_ref() {
54				println!("{:<col_size$} {:<}", "Homepage:", h);
55			}
56
57			if let Some(h) = r.krate.crate_data.repository.as_ref() {
58				println!("{:<col_size$} {:<}", "Repository:", h);
59			}
60
61			if let Some(h) = r.krate.crate_data.documentation.as_ref() {
62				println!("{:<col_size$} {:<}", "Documentation:", h);
63			}
64
65			match r.owners.len() {
66				1 => println!(
67					"{:<col_size$} {:<}",
68					"Owner:",
69					r.owners.first().expect("Missing user").name.as_ref().unwrap_or(&String::from("n/a"))
70				),
71				x if x > 1 => {
72					print!("{:<col_size$} ", "Owners:");
73					r.owners.iter().for_each(|user| print!("{}, ", user.name.as_ref().unwrap_or(&String::from("n/a"))));
74					println!();
75				}
76				_ => {}
77			}
78
79			if let Some(latest) = r.krate.versions.first() {
80				let latest = WrappedVersion::from(latest).version;
81
82				let publisher_name = match &latest.published_by {
83					Some(user) => match &user.name {
84						Some(name) => name.into(),
85						_ => String::from("n/a"),
86					},
87					_ => String::from("n/a"),
88				};
89				println!(
90					"{:<col_size$} v{} by {} {} with {} downloads",
91					"Latest:",
92					latest.num,
93					publisher_name,
94					HumanTime::from(latest.updated_at),
95					latest.downloads
96				);
97			}
98
99			// println!("categories =\t{:?}", krate.categories);
100			// println!("keywords =\t{:?}", krate.keywords);
101
102			if let Some(options) = opts {
103				if options.max_versions > 0 {
104					// println!("Versions:");
105					println!(
106						"  {version:<9}\t{time:<16}\t{size:<10}\t{publisher:<20}\t{downloads:<10}\t{yanked:>8}",
107						version = "VERSION",
108						time = "UPDATED",
109						size = "SIZE",
110						publisher = "PUBLISHED BY",
111						downloads = "DOWNLOADS",
112						yanked = "YANKED",
113					);
114
115					r.krate
116						.versions
117						.iter()
118						.enumerate()
119						.take_while(|(i, _v)| i < &(options.max_versions as usize))
120						.for_each(|(_i, v)| {
121							let wv: WrappedVersion = WrappedVersion::from(v);
122							println!("{wv}");
123						});
124					let total: u64 = r.krate.versions.len().try_into().unwrap_or(u64::MAX);
125					if total > options.max_versions {
126						println!("... and {} more", total - options.max_versions)
127					}
128				}
129			}
130
131			println!();
132		});
133	}
134
135	/// Show the reponse as a json object. If there is a single object,
136	/// it will be taken out of the array. An json array is returned oherwise.
137	pub fn show_json(response: &Vec<ApiResponse>) {
138		if response.len() == 1 {
139			println!("{}", serde_json::to_string_pretty(&response.first()).unwrap());
140		} else {
141			println!("{}", serde_json::to_string_pretty(&response).unwrap());
142		}
143	}
144
145	pub fn search(&self, pattern: &str, page_size: u64) -> anyhow::Result<Vec<Crate>> {
146		let q = CratesQuery::builder().sort(Sort::Alphabetical).search(pattern).page_size(page_size).build();
147		let crates = self.client.crates(q)?;
148
149		Ok(crates.crates)
150	}
151}
152
153#[cfg(test)]
154mod test_super {
155	use super::*;
156
157	#[test]
158	fn test_fetch() {
159		let crates: Vec<&str> = vec!["cargo-crate", "sshq"];
160		let opts = InfoOpts::default();
161
162		let res = Info::new().fetch(crates, &opts);
163		assert!(res.is_ok());
164		assert_eq!(2, res.unwrap().len());
165	}
166
167	#[test]
168	fn test_search() {
169		let pattern = "cargo-crate";
170
171		let crates = Info::new().search(pattern, 32).unwrap();
172		println!("crates = {:?}", &crates.len());
173		crates.iter().for_each(|c| println!("- {}", c.name));
174		assert!(crates.len() > 10);
175	}
176}