cargo-equip 0.19.0

A Cargo subcommand to bundle your code into one `.rs` file for competitive programming.
Documentation
use crate::{process::ProcessBuilderExt as _, shell::Shell, toolchain, workspace::TargetExt as _};
use cargo_metadata as cm;
use cargo_util::ProcessBuilder;
use serde::Deserialize;
use std::{
    collections::{HashMap, HashSet},
    path::PathBuf,
};

pub(crate) fn cargo_udeps(
    package: &cm::Package,
    target: &cm::Target,
    toolchain: &str,
    shell: &mut Shell,
) -> Result<HashSet<String>, String> {
    let cwd = &package.manifest_path.with_file_name("");

    let rustup_exe = toolchain::rustup_exe(cwd).map_err(|e| e.to_string())?;

    let output = ProcessBuilder::new(rustup_exe)
        .arg("run")
        .arg(toolchain)
        .arg("cargo")
        .arg("udeps")
        .arg("--output")
        .arg("json")
        .arg("-p")
        .arg(&package.name)
        .args(&target.target_option())
        .cwd(cwd)
        .try_inspect(|this| shell.status("Running", this))
        .map_err(|e| e.to_string())?
        .read_stdout_unchecked::<String>()
        .map_err(|e| e.to_string())?;

    let Outcome { unused_deps } = serde_json::from_str(&output)
        .map_err(|e| format!("could not parse the output of `cargo-udeps`: {}", e))?;

    Ok(unused_deps
        .into_iter()
        .find(|(_, OutcomeUnusedDeps { manifest_path, .. })| {
            *manifest_path == package.manifest_path
        })
        .map(
            |(
                _,
                OutcomeUnusedDeps {
                    normal,
                    development,
                    ..
                },
            )| &normal | &development,
        )
        .unwrap_or_default())
}

#[derive(Deserialize)]
struct Outcome {
    unused_deps: HashMap<String, OutcomeUnusedDeps>,
}

#[derive(Deserialize)]
struct OutcomeUnusedDeps {
    manifest_path: PathBuf,
    normal: HashSet<String>,
    development: HashSet<String>,
}