cargo-whatfeatures 0.9.13

display features, versions and dependencies of crates
Documentation
use std::collections::HashSet;

use cargo_whatfeatures::*;

fn real_main(mut args: Args) -> anyhow::Result<()> {
    let options = cargo_whatfeatures::Options {
        print_features: !args.no_features,
        show_deps: args.show_deps,
        verbose: args.verbose,
        show_private: args.show_private,
        theme: args.theme,
    };

    let client = if args.offline {
        None
    } else {
        Some(Client::new("https://crates.io"))
    };

    let name = args.pkgid.name();

    if args.list {
        let versions = client
            .as_ref()
            .ok_or_else(|| OfflineError::List.to_error())?
            .list_versions(name)
            .map_err(|_| anyhow::anyhow!("cannot find a crate matching '{}'", &args.pkgid))?;

        if versions.is_empty() {
            anyhow::bail!("no versions published for '{}'", &args.pkgid)
        }

        if versions.len() == 1 && versions[0].yanked {
            args.show_yanked.replace(YankStatus::Include);
        }

        if args.list && args.json {
            let unique = versions.iter().map(|c| &c.name).collect::<HashSet<_>>();
            anyhow::ensure!(!unique.is_empty(), "no crates were found");
            anyhow::ensure!(
                unique.len() == 1,
                "program is in an invalid state. expected 1 crate, found {}",
                unique.len()
            );

            let name = unique.into_iter().next().unwrap().clone();
            let json = cargo_whatfeatures::json::create_crates_from_versions(&name, versions);
            println!("{json}");
            std::process::exit(0)
        }

        return VersionPrinter::new(&mut std::io::stdout(), options)
            .write_versions(
                &versions,
                args.show_yanked.unwrap_or_default(),
                args.verbose,
            )
            .map_err(Into::into);
    }

    let mut out = std::io::stdout();

    let workspace = match cargo_whatfeatures::lookup(&args.pkgid, &client, args.local_only)? {
        Lookup::Partial(vers) => {
            let Version { name, version, .. } = &vers;

            if args.name_only {
                if args.json {
                    let json = cargo_whatfeatures::json::create_crates_from_versions(
                        name,
                        Some(vers.clone()),
                    );
                    println!("{json}");
                    std::process::exit(0)
                }

                return VersionPrinter::new(&mut std::io::stdout(), options)
                    .write_latest_version(&vers, args.verbose)
                    .map_err(Into::into);
            }

            let crate_ = match Registry::from_local()?.get(&name, &version) {
                Some(crate_) => crate_.clone(),
                None => match client
                    .as_ref()
                    .ok_or_else(|| OfflineError::CacheMiss.to_error())?
                    .cache_crate(&name, &version)
                {
                    Ok(res) => res,
                    Err(_err) => return cannot_lookup(&args.pkgid),
                },
            };

            if let YankState::Yanked = crate_.yanked {
                use yansi::*;
                println!(
                    "{}. {}/{} has been yanked on crates.io",
                    Paint::yellow("WARNING"),
                    crate_.name,
                    crate_.version
                );
            }

            crate_.get_features()?
        }

        pkg @ Lookup::LocalCache(..) | pkg @ Lookup::Workspace(..) => {
            let local = matches!(pkg, Lookup::LocalCache { .. });
            let pkg = match pkg {
                Lookup::LocalCache(pkg) | Lookup::Workspace(pkg) => pkg,
                _ => unreachable!(),
            };

            if local {
                use std::io::Write as _;
                let msg = args.theme.warning.paint(format!(
                    "WARNING: {}",
                    cargo_whatfeatures::labels::POSSIBLY_OLD_CRATE
                ));
                writeln!(out, "{}", msg)?;
            }

            if args.name_only {
                let mut packages = pkg
                    .map
                    .values()
                    .map(|pkg| (&pkg.name, &pkg.version, pkg.published))
                    .collect::<Vec<_>>();
                packages.sort_by(|(l, ..), (r, ..)| l.cmp(r));

                if args.json {
                    let json =
                        cargo_whatfeatures::json::create_crates_from_workspace(&pkg.hint, packages);
                    println!("{json}");
                    std::process::exit(0)
                }

                VersionPrinter::new(&mut out, options).write_many_versions(packages)?;
                return Ok(());
            }
            pkg
        }
    };

    if args.json {
        let json = cargo_whatfeatures::json::workspace(workspace);
        println!("{json}");
        std::process::exit(0)
    }

    WorkspacePrinter::new(&mut std::io::stdout(), workspace, options).print()?;
    Ok(())
}

fn cannot_lookup(pkgid: &PkgId) -> anyhow::Result<()> {
    let mut out = format!("cannot lookup crate '{}'.", &pkgid);
    if let PkgId::Remote {
        semver: Some(semver),
        ..
    } = pkgid
    {
        out.push_str(&format!(
            " perhaps this is an invalid semver: '{}'?",
            semver
        ));
    }

    anyhow::bail!(out)
}

fn main() -> anyhow::Result<()> {
    let args = Args::parse()?;
    let theme = args.theme;

    if let Err(err) = real_main(args) {
        eprintln!("{}: {}", theme.error.paint("ERROR"), err);
        std::process::exit(1)
    }

    Ok(())
}