dylint_internal 2.1.3

Dylint internals
Documentation
use crate::env;
use ansi_term::Style;
use anyhow::{anyhow, ensure, Result};
use cargo_metadata::{Metadata, MetadataCommand, Package, PackageId};
use std::{
    io::Write,
    path::{Path, PathBuf},
    process::Stdio,
};

#[must_use]
pub fn build(description: &str, quiet: bool) -> crate::Command {
    cargo("build", "Building", description, quiet)
}

// smoelius: `cargo check` and `cargo fix` are never silenced.
#[must_use]
pub fn check(description: &str) -> crate::Command {
    cargo("check", "Checking", description, false)
}

// smoelius: `cargo check` and `cargo fix` are never silenced.
#[must_use]
pub fn fix(description: &str) -> crate::Command {
    cargo("fix", "Fixing", description, false)
}

#[must_use]
pub fn init(description: &str, quiet: bool) -> crate::Command {
    cargo("init", "Initializing", description, quiet)
}

#[must_use]
pub fn test(description: &str, quiet: bool) -> crate::Command {
    cargo("test", "Testing", description, quiet)
}

#[must_use]
pub fn update(description: &str, quiet: bool) -> crate::Command {
    cargo("update", "Updating", description, quiet)
}

fn cargo(subcommand: &str, verb: &str, description: &str, quiet: bool) -> crate::Command {
    if !quiet {
        // smoelius: Writing directly to `stderr` avoids capture by `libtest`.
        let message = format!("{verb} {description}");
        std::io::stderr()
            .write_fmt(format_args!(
                "{}\n",
                if atty::is(atty::Stream::Stdout) {
                    Style::new().bold()
                } else {
                    Style::new()
                }
                .paint(message)
            ))
            .expect("Could not write to stderr");
    }
    let mut command = crate::Command::new("cargo");
    #[cfg(windows)]
    {
        // smoelius: Work around https://github.com/rust-lang/rustup/pull/2978
        let cargo_home = cargo_home().unwrap();
        let old_path = crate::env::var(crate::env::PATH).unwrap();
        let new_path = std::env::join_paths(
            std::iter::once(Path::new(&cargo_home).join("bin"))
                .chain(std::env::split_paths(&old_path)),
        )
        .unwrap();
        command.envs(vec![(env::PATH, new_path)]);
    }
    command.args([subcommand]);
    if quiet {
        command.stderr(Stdio::null());
    }
    command
}

#[allow(clippy::module_name_repetitions)]
pub fn cargo_home() -> Result<PathBuf> {
    match env::var(env::CARGO_HOME) {
        Ok(value) => Ok(PathBuf::from(value)),
        Err(error) => dirs::home_dir()
            .map(|path| path.join(".cargo"))
            .ok_or(error),
    }
}

/// Get metadata based on the current directory.
pub fn current_metadata() -> Result<Metadata> {
    MetadataCommand::new().no_deps().exec().map_err(Into::into)
}

pub fn package_with_root(metadata: &Metadata, package_root: &Path) -> Result<Package> {
    let packages = metadata
        .packages
        .iter()
        .map(|package| {
            let path = package
                .manifest_path
                .parent()
                .ok_or_else(|| anyhow!("Could not get parent directory"))?;
            Ok(if path == package_root {
                Some(package)
            } else {
                None
            })
        })
        .filter_map(Result::transpose)
        .collect::<Result<Vec<_>>>()?;

    ensure!(
        packages.len() <= 1,
        "Found multiple packages in `{}`",
        package_root.to_string_lossy()
    );

    packages
        .into_iter()
        .next()
        .cloned()
        .ok_or_else(|| anyhow!("Found no packages in `{}`", package_root.to_string_lossy()))
}

pub fn package(metadata: &Metadata, package_id: &PackageId) -> Result<Package> {
    metadata
        .packages
        .iter()
        .find(|package| package.id == *package_id)
        .cloned()
        .ok_or_else(|| anyhow!("Could not find package"))
}