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 pub fn fetch(&self, crates: Vec<&str>, _opts: &InfoOpts) -> anyhow::Result<Vec<ApiResponse>> {
27 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 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 if let Some(options) = opts {
103 if options.max_versions > 0 {
104 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 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}