cargo-parcel 0.0.4

Experimental extended cargo installer
Documentation
//! Miscellaneous utilities

use std::{
    ffi::{OsStr, OsString},
    fmt,
    path::PathBuf,
    process::{Command, ExitStatus},
};

use anyhow::Error;
use pico_args::Arguments;

#[derive(Debug)]
struct ExecError {
    program: OsString,
    args: Vec<OsString>,
    status: ExitStatus,
}

impl<'a> fmt::Display for ExecError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "subprocess {} failed with status {}",
            self.program.to_string_lossy(),
            self.status
        )
    }
}

impl std::error::Error for ExecError {}

/// Display a list of command-line arguments.
#[derive(Debug)]
pub struct DisplayArgs<T>(pub T);

impl<T> fmt::Display for DisplayArgs<T>
where
    T: Iterator + Clone,
    T::Item: AsRef<OsStr>,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut args = self.0.clone();
        let mut next = args.next();
        while let Some(arg) = next {
            // FIXME: quoting
            let s = arg.as_ref().to_string_lossy();
            write!(f, "{}", s)?;
            next = args.next();
            if next.is_some() {
                write!(f, " ")?;
            }
        }
        Ok(())
    }
}

/// Construct an empty set of environment variables.
pub fn empty_env() -> Vec<(&'static str, &'static OsStr)> {
    Vec::new()
}

/// Run a program in a subprocess.
///
/// Blockingly runs the given executable with the supplied arguments and
/// environment.
pub fn cmd<P, E, A, V>(program: P, args: A, env: E, verbose: bool) -> Result<(), Error>
where
    P: AsRef<OsStr>,
    A: IntoIterator,
    A::IntoIter: Clone,
    A::Item: AsRef<OsStr>,
    E: IntoIterator<Item = (&'static str, V)>,
    V: AsRef<OsStr>,
{
    let program = program.as_ref();
    let args = args.into_iter();
    let env = env.into_iter();
    if verbose {
        println!(
            "* {} {}",
            program.to_string_lossy(),
            DisplayArgs(args.clone())
        );
    }
    let status = Command::new(program)
        .args(args.clone())
        .envs(env)
        .status()?;
    if !status.success() {
        return Err(ExecError {
            program: program.to_owned(),
            args: args.map(|arg| arg.as_ref().to_owned()).collect(),
            status,
        }
        .into());
    }
    Ok(())
}

/// Get the path prefix to use.
///
/// If present in the supplied arguments, return the value given for
/// `--prefix`. Otherwise, it will return a default, currently `~/.local` for
/// all platforms.
pub fn get_prefix_opt(args: &mut Arguments) -> anyhow::Result<PathBuf> {
    match args.opt_value_from_str("--prefix")? {
        Some(prefix) => Ok(prefix),
        None => {
            let mut home = PathBuf::from(std::env::var("HOME")?);
            home.push(".local");
            Ok(home)
        }
    }
}