rust-utils 0.16.0

Various utility routines used in the rust programs I have written
Documentation
//! Miscellaneous convenience functions
//!
//! These functions make some menial tasks a little easier to do,
//! like getting the executable's name, or running a command

use std::{
    env, io,
    fmt::Display,
    path::{Path, PathBuf},
    ffi::OsStr,
    process::{
        Child,
        Command,
        Stdio
    }
};

/// Data returned by utils::run_command()
///
/// This has the command's output, status code, and whether it was successful or not
pub struct CommandOutput {
    /// Text output from the executed command
    pub output: String,

    /// The exit code of the executed command
    ///
    /// 0 means it was successfully executed
    pub code: u32
}

impl CommandOutput {
    /// Was this command successfully executed?
    pub fn success(&self) -> bool {
        self.code == 0
    }
}

/// Run a command and return the output, status code, and whether the command succeeded or not
#[track_caller]
pub fn run_command<A: IntoIterator<Item = S> + Clone, S: AsRef<OsStr>>(prog: &str, superuser: bool, args: A) -> CommandOutput {
    let mut cmd = if superuser {
        let mut cmd_t = Command::new("sudo");
        cmd_t.arg(prog);
        cmd_t
    }
    else { Command::new(prog) };

    cmd.args(args);

    let output = match cmd.output() {
        Ok(o) => o,
        Err(why) => {
            return CommandOutput {
                output: why.to_string(),
                code: 666
            };
        }
    };

    let code = output.status.code().unwrap_or(1) as u32;

    let mut o_fmt = if code == 0 {
        String::from_utf8(output.stdout).unwrap()
    }
    else {
        String::from_utf8(output.stderr).unwrap()
    };

    o_fmt.pop();

    CommandOutput {
        output: o_fmt,
        code: output.status.code().unwrap_or(1) as u32
    }
}

/// Spawn a detached process
#[track_caller]
pub fn spawn_process<A: IntoIterator<Item = S> + Clone, S: AsRef<OsStr>>(prog: &str, superuser: bool, should_output: bool, args: A) -> Child {
    let mut cmd = if superuser {
        let mut cmd_t = Command::new("sudo");
        cmd_t.arg(prog);
        cmd_t
    }
    else {
        Command::new(prog)
    };

    if !should_output {
        cmd.stdout(Stdio::null());
        cmd.stderr(Stdio::null());
    }

    cmd.args(args);
    cmd.spawn().unwrap()
}

/// Alphabetizes a vector of strings
pub fn alphabetize<L: Display>(list: &[L]) -> Vec<String> {
    // extract the strings from the generic value
    let mut string_list: Vec<String> = list.iter().map(L::to_string).collect();
    string_list.sort_unstable_by_key(|a| a.to_lowercase());
    string_list
}

/// Get the name of the file/directory from a file path
///
/// Returns an empty string if there is no file name
pub fn get_filename<D: Into<PathBuf>>(dir: D) -> String {
    let path = dir.into();
    if let Some(file_name) = path.file_name() {
        file_name.to_str().unwrap().to_string()
    }
    else { String::new() }
}

/// Gets the parent directory from a file path
///
/// Returns the current path if there is no parent
pub fn get_parent<D: Into<PathBuf>>(dir: D) -> String {
    let path = dir.into();
    if let Some(parent) = path.parent() {
        parent.display().to_string()
    }
    else {
        path.display().to_string()
    }
}

/// Capitalize the first letter in a string
pub fn capitalize<W: Display>(word: W) -> String {
    let s1 = word.to_string();
    let mut v: Vec<char> = s1.chars().collect();

    for item in &mut v {
        if item.is_ascii_alphanumeric() {
            *item = item.to_ascii_uppercase();
            break;
        }
    }

    let s2: String = v.into_iter().collect();
    s2
}

/// Get the name of the executable
#[track_caller]
pub fn get_execname() -> String {
    env::args().next()
        .as_ref()
        .map(Path::new)
        .and_then(Path::file_name)
        .and_then(OsStr::to_str)
        .map(String::from)
        .unwrap()
}

/// Show a message and
/// get the user's input from the standard input
#[track_caller]
pub fn get_input<M: Display>(msg: M) -> String {
    println!("{msg}");
    let mut buf_string = String::new();
    io::stdin().read_line(&mut buf_string).unwrap();
    buf_string.retain(|c| c != '\n');
    buf_string
}