1use crate::Error;
2
3use clap::Parser;
4use clap_verbosity::Verbosity;
5use colorful::Colorful;
6use tame_index::{KrateName, index::FileLock};
7
8#[derive(Parser, Debug, Default)]
9#[clap(author, version, about, long_about = None)]
10pub struct CrateVersions {
11 #[clap(flatten)]
12 logging: Verbosity,
13 crate_: String,
15 #[clap(short = 'e', long = "earliest")]
17 earliest: bool,
18 #[clap(short = 'n', long = "normal")]
20 normal: bool,
21 #[clap(short = 't', long = "top")]
23 highest: bool,
24 #[clap(short = 'r', long = "recent")]
26 recent: bool,
27 #[clap(short = 'l', long = "list")]
29 list: bool,
30 #[clap(short = 'k', long = "key")]
32 key: bool,
33 #[clap(short = 'a', long = "all")]
35 all: bool,
36}
37
38impl CrateVersions {
39 pub fn run(&self, no_colour: bool) -> Result<String, Error> {
40 log::info!("Getting details for crate: {}", self.crate_);
41 let lock = FileLock::unlocked();
42 let index = crate::get_remote_combo_index()?;
43 let index_crate = index.krate(KrateName::crates_io(&self.crate_)?, true, &lock)?;
44
45 let Some(index_crate) = index_crate else {
46 return Err(Error::CrateNotFoundOnIndex);
47 };
48
49 let mut output = format!(
50 "\n {}",
51 if no_colour {
52 format!("Crate versions for {}.", index_crate.name())
53 } else {
54 format!("Crate versions for {}.", index_crate.name().cyan())
55 .bold()
56 .to_string()
57 }
58 );
59
60 let mut i = 0;
61 let mut line = String::from(" ");
62
63 while i < 20 + index_crate.name().len() {
64 line.push('🭶');
65 i += 1;
66 }
67
68 output = format!("{output}\n{line}\n");
69
70 if self.earliest | self.all | self.key {
71 output = format!(
72 "{} Earliest version: {}\n",
73 output,
74 index_crate.earliest_version().version
75 );
76 };
77
78 if self.normal | self.all | self.key {
79 output = format!(
80 "{} {}\n",
81 output,
82 if no_colour {
83 format!(
84 "Highest normal version: {}",
85 index_crate
86 .highest_normal_version()
87 .unwrap_or_else(|| index_crate.highest_version())
88 .version
89 )
90 } else {
91 format!(
92 "Highest normal version: {}",
93 index_crate
94 .highest_normal_version()
95 .unwrap_or_else(|| index_crate.highest_version())
96 .version
97 )
98 .blue()
99 .to_string()
100 }
101 );
102 };
103
104 if self.highest | self.all | self.key {
105 output = format!(
106 "{} {}\n",
107 output,
108 if no_colour {
109 format!("Highest version: {}", index_crate.highest_version().version)
110 } else {
111 format!("Highest version: {}", index_crate.highest_version().version)
112 .green()
113 .to_string()
114 }
115 );
116 };
117
118 if self.recent | self.all | self.key {
119 output = format!(
120 "{} {}\n",
121 output,
122 if no_colour {
123 format!(
124 "Most recent version: {}",
125 index_crate.most_recent_version().version
126 )
127 } else {
128 format!(
129 "Most recent version: {}",
130 index_crate.most_recent_version().version
131 )
132 .yellow()
133 .to_string()
134 }
135 );
136 };
137
138 if self.list | self.all {
139 const BASE_HEADER: &str = " Yanked Version ";
140
141 let mut header = BASE_HEADER.to_string();
142
143 let rows = index_crate
144 .versions
145 .iter()
146 .map(|x| {
147 format!(
148 " {} {}",
149 match (x.yanked, no_colour) {
150 (true, true) => "Yes".to_string(),
151 (false, true) => " No".to_string(),
152 (true, false) => "Yes".red().to_string(),
153 (false, false) => " No".green().to_string(),
154 },
155 x.version
156 )
157 })
158 .collect::<Vec<String>>();
159
160 log::debug!("Rows: {rows:#?}!");
161
162 let max_row = &rows
163 .iter()
164 .map(|x| {
165 log::debug!("Line: `{}`, len: `{}`!", x, x.chars().count(),);
166 x.len() - 12
167 })
168 .max()
169 .unwrap_or(BASE_HEADER.len());
170 log::debug!("Max row length: {max_row}!");
171
172 while header.len() < *max_row {
173 header = format!("{header} ");
174 }
175 log::debug!("Output: {output}!");
176 log::debug!("Header: {header}!");
177
178 let rows = format!(" {}\n", rows.join("\n "));
179
180 output = format!(
181 "{} {}\n{}",
182 output,
183 if no_colour {
184 header.to_string()
185 } else {
186 header.underlined().to_string()
187 },
188 rows
189 );
190 }
191
192 Ok(output)
193 }
194}
195
196#[cfg(test)]
197mod tests {
198
199 use colorful::Colorful;
200 use rstest::fixture;
201
202 use crate::crate_versions::CrateVersions;
203
204 #[fixture]
205 fn header(#[default("some_crate")] name: &str) -> String {
206 let output = format!(
207 "\n {}",
208 format!("Crate versions for {}.", name.cyan()).bold()
209 );
210
211 let mut i = 0;
212 let mut line = String::from(" ");
213
214 while i < 20 + name.len() {
215 line.push('🭶');
216 i += 1;
217 }
218
219 format!("{output}\n{line}\n")
220 }
221
222 #[fixture]
223 fn earliest() -> String {
224 " Earliest version: 0.1.0\n".to_string()
225 }
226
227 #[fixture]
228 fn highest_normal() -> String {
229 format!(" {}\n", "Highest normal version: 0.2.1".blue())
230 }
231
232 #[fixture]
233 fn highest() -> String {
234 format!(" {}\n", "Highest version: 0.2.1".green())
235 }
236
237 #[fixture]
238 fn recent() -> String {
239 format!(" {}\n", "Most recent version: 0.2.1".yellow())
240 }
241
242 #[fixture]
243 fn list() -> String {
244 " \u{1b}[4m Yanked Version \u{1b}[0m\n \u{1b}[38;5;2m No\u{1b}[0m 0.1.0\n \u{1b}[38;5;2m No\u{1b}[0m 0.1.1\n \u{1b}[38;5;2m No\u{1b}[0m 0.1.3\n \u{1b}[38;5;2m No\u{1b}[0m 0.2.1\n".to_string()
245 }
246
247 #[test]
248 fn test_run_earliest() {
249 let name = "some_crate";
250 let expected = format!("{}{}", header(name), &earliest());
251
252 let crate_versions = CrateVersions {
253 crate_: "some_crate".to_string(),
254 earliest: true,
255 ..Default::default()
256 };
257
258 assert_eq!(crate_versions.crate_, "some_crate".to_string());
259 assert!(crate_versions.earliest);
260 assert!(!crate_versions.normal);
261 assert!(!crate_versions.highest);
262 assert!(!crate_versions.recent);
263 assert!(!crate_versions.list);
264 assert!(!crate_versions.all);
265 assert!(!crate_versions.key);
266
267 let result = crate_versions.run(false);
268 assert!(result.is_ok());
269 let output = result.unwrap();
270 assert_eq!(output, expected);
271 }
272
273 #[test]
274 fn test_run_normal() {
275 let name = "some_crate";
276 let expected = format!("{}{}", header(name), &highest_normal());
277
278 let crate_versions = CrateVersions {
279 crate_: "some_crate".to_string(),
280 normal: true,
281 ..Default::default()
282 };
283
284 let result = crate_versions.run(false);
285 assert!(result.is_ok());
286 let output = result.unwrap();
287 assert_eq!(output, expected);
288 }
289
290 #[test]
291 fn test_run_top() {
292 let name = "some_crate";
293 let expected = format!("{}{}", header(name), &highest());
294
295 let crate_versions = CrateVersions {
296 crate_: "some_crate".to_string(),
297 highest: true,
298 ..Default::default()
299 };
300
301 let result = crate_versions.run(false);
302 assert!(result.is_ok());
303 let output = result.unwrap();
304 assert_eq!(output, expected);
305 }
306
307 #[test]
308 fn test_run_recent() {
309 let name = "some_crate";
310 let expected = format!("{}{}", header(name), &recent());
311
312 let crate_versions = CrateVersions {
313 crate_: "some_crate".to_string(),
314 recent: true,
315 ..Default::default()
316 };
317
318 let result = crate_versions.run(false);
319 assert!(result.is_ok());
320 let output = result.unwrap();
321 assert_eq!(output, expected);
322 }
323
324 #[test]
325 fn test_run_list() {
326 let name = "some_crate";
327 let expected = format!("{}{}", header(name), &list());
328
329 let crate_versions = CrateVersions {
330 crate_: "some_crate".to_string(),
331 list: true,
332 ..Default::default()
333 };
334
335 let result = crate_versions.run(false);
336 assert!(result.is_ok());
337 let output = result.unwrap();
338 assert_eq!(output, expected);
339 }
340
341 #[test]
342 fn test_run_all() {
343 let name = "some_crate";
344 let expected = format!(
345 "{}{}{}{}{}{}",
346 header(name),
347 &earliest(),
348 &highest_normal(),
349 &highest(),
350 &recent(),
351 &list()
352 );
353
354 let crate_versions = CrateVersions {
355 crate_: "some_crate".to_string(),
356 all: true,
357 ..Default::default()
358 };
359
360 let result = crate_versions.run(false);
361 assert!(result.is_ok());
362 let output = result.unwrap();
363 assert_eq!(output, expected);
364 }
365
366 #[test]
367 fn test_run_key() {
368 let name = "some_crate";
369 let expected = format!(
370 "{}{}{}{}{}",
371 header(name),
372 &earliest(),
373 &highest_normal(),
374 &highest(),
375 &recent(),
376 );
377
378 let crate_versions = CrateVersions {
379 crate_: "some_crate".to_string(),
380 key: true,
381 ..Default::default()
382 };
383
384 let result = crate_versions.run(false);
385 assert!(result.is_ok());
386 let output = result.unwrap();
387 assert_eq!(output, expected);
388 }
389
390 #[test]
391 fn test_run_invalid_crate() {
392 let crate_versions = CrateVersions {
393 crate_: "some_non-existing_crate".to_string(),
394 ..Default::default()
395 };
396
397 let result = crate_versions.run(false);
398 assert!(result.is_err());
399 }
400
401 #[test]
402 fn test_run_invalid_crate_earliest() {
403 let crate_versions = CrateVersions {
404 crate_: "sdc_apis".to_string(),
405 earliest: true,
406 ..Default::default()
407 };
408
409 let result = crate_versions.run(false);
410 assert!(result.is_ok());
411 }
412}