vdev/commands/
crate_versions.rs

1use std::{
2    collections::{HashMap, HashSet},
3    process::Command,
4};
5
6use anyhow::Result;
7use clap::Args;
8use itertools::Itertools as _;
9use regex::Regex;
10
11use crate::{app::CommandExt as _, utils};
12
13/// Show information about crates versions pulled in by all dependencies
14#[derive(Args, Debug)]
15#[command()]
16pub struct Cli {
17    /// Show all versions, not just those that are duplicated
18    #[arg(long)]
19    all: bool,
20
21    /// The feature to active (multiple allowed). If none are specified, the default is used.
22    #[arg(short = 'F', long)]
23    feature: Vec<String>,
24}
25
26impl Cli {
27    pub fn exec(self) -> Result<()> {
28        let re_crate = Regex::new(r" (\S+) v([0-9.]+)").unwrap();
29        let mut versions: HashMap<String, HashSet<String>> = HashMap::default();
30
31        for line in Command::new("cargo")
32            .arg("tree")
33            .features(&self.feature)
34            .check_output()?
35            .lines()
36        {
37            if let Some(captures) = re_crate.captures(line) {
38                let package = &captures[1];
39                let version = &captures[2];
40                versions
41                    .entry(package.into())
42                    .or_default()
43                    .insert(version.into());
44            }
45        }
46
47        if !self.all {
48            versions.retain(|_, versions| versions.len() > 1);
49        }
50
51        let width = versions.keys().map(String::len).max().unwrap_or(0).max(7);
52        if *utils::IS_A_TTY {
53            println!("{:width$}  Version(s)", "Package");
54            println!("{:width$}  ----------", "-------");
55        }
56
57        for (package, versions) in versions {
58            let versions = versions.iter().join(" ");
59            println!("{package:width$}  {versions}");
60        }
61
62        Ok(())
63    }
64}