crax_utils 0.2.0

Some common utils
Documentation
use std::path::{Path, PathBuf};

use globset::{Glob, GlobSetBuilder};
use owo_colors::OwoColorize;
use tokio::process::Command;

pub fn contains_subsequence(haystack: &[u8], needle: &[u8]) -> bool {
    haystack.windows(needle.len()).any(|window| window == needle)
}

pub fn contains_substring(vec: &[u8], substring: &str) -> bool {
    if let Ok(s) = std::str::from_utf8(vec) {
        s.contains(substring)
    } else {
        false
    }
}

pub fn unwrap_ref_option<T: Clone>(o: &Option<T>, default: T) -> T {
    if let Some(v) = o {
        v.clone()
    } else {
        default
    }
}

pub fn result_hint(s: bool) -> String {
    if s {
        "".green().to_string()
    } else {
        "x".red().to_string()
    }
}

pub fn create_glob_builder(glob: &Option<Vec<String>>) -> Option<GlobSetBuilder> {
    if let Some(v) = glob {
        let mut builder = GlobSetBuilder::new();

        v.iter().for_each(|f| {
            if let Ok(s) = Glob::new(f) {
                builder.add(s);
            }
        });

        Some(builder)
    } else {
        None
    }
}

pub async fn run_command(cmd: &str, args: &[&str], dir: impl AsRef<Path>) -> anyhow::Result<()> {
    let output = Command::new(cmd).current_dir(dir).args(args).output().await?;

    if output.status.success() {
        Ok(())
    } else {
        let status = output.status;
        let stderr = String::from_utf8_lossy(&output.stderr);
        let stdout = String::from_utf8_lossy(&output.stdout);
        let msg = format!(
            "Command failed with status: {}, stderr: {},\n{}",
            status,
            stderr,
            format!("stdout: {}", stdout).cyan()
        );
        Err(anyhow::Error::msg(msg.red().to_string()))
    }
}

pub fn get_relative_path(
    base: &Option<impl AsRef<Path>>,
    target: impl AsRef<Path>,
) -> Option<PathBuf> {
    if let Some(base) = base {
        target.as_ref().strip_prefix(base).ok().map(PathBuf::from)
    } else {
        Some(target.as_ref().to_path_buf())
    }
}

pub async fn is_clear(path: impl AsRef<Path>) -> anyhow::Result<()> {
    let output =
        Command::new("git").current_dir(path).arg("status").arg("--porcelain").output().await?;

    let error =
        Err(anyhow::Error::msg("Unclean working tree. Commit or stash changes first.".yellow()));

    if output.status.success() && output.stderr.is_empty() && output.stdout.is_empty() {
        return Ok(());
    }

    error
}

pub const EMPTY_MARKER: &'static str = "--";
pub const HOME_ICON: &'static str = "🏠";

pub fn base_pair(k: &str, v: &str, colon: bool) -> String {
    let colon = if let true = colon { ":" } else { "" };
    format!("{}{} {}", k, colon, v.bright_cyan())
}

pub fn pair(k: &str, v: &str) -> String {
    base_pair(k, v, true)
}

pub fn pair_no_colon(k: &str, v: &str) -> String {
    base_pair(k, v, false)
}

#[derive(Debug, PartialEq)]
pub enum RunState {
    Halt,
    Building,
    Finish,
}

impl Default for RunState {
    fn default() -> Self {
        Self::Halt
    }
}

impl RunState {
    pub fn display(&self) -> String {
        match self {
            RunState::Building => format!("{:?}", self),
            RunState::Finish => format!("{:?}", self),
            RunState::Halt => format!("{:?}", self),
        }
    }

    pub fn max_len() -> usize {
        [
            RunState::Building.display().len(),
            RunState::Finish.display().len(),
            RunState::Halt.display().len(),
        ]
        .iter()
        .copied()
        .max()
        .unwrap()
    }
}