tuicr 0.21.0

Review AI-generated diffs like a GitHub pull request, right from your terminal.
Documentation
use std::fmt;
use std::path::{Component, Path};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InstallMethod {
    Homebrew,
    Cargo,
    Mise,
    Nix,
    Direct,
}

impl fmt::Display for InstallMethod {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match self {
            Self::Homebrew => "Homebrew",
            Self::Cargo => "Cargo",
            Self::Mise => "Mise",
            Self::Nix => "Nix",
            Self::Direct => "direct binary",
        })
    }
}

pub(super) struct ManagerCommand {
    pub program: &'static str,
    pub args: &'static [&'static str],
}

pub(super) fn manager_command(method: InstallMethod) -> Option<ManagerCommand> {
    let (program, args): (_, &'static [&'static str]) = match method {
        InstallMethod::Homebrew => ("brew", &["upgrade", "agavra/tap/tuicr"]),
        InstallMethod::Cargo => ("cargo", &["install", "tuicr", "--force"]),
        InstallMethod::Mise => (
            "mise",
            &["upgrade", "github:agavra/tuicr", "--bump", "--yes"],
        ),
        InstallMethod::Nix => ("nix", &["profile", "upgrade", ".*tuicr.*"]),
        InstallMethod::Direct => return None,
    };
    Some(ManagerCommand { program, args })
}

pub(super) fn detect_install_method(
    executable: &Path,
    home: Option<&Path>,
    cargo_home: Option<&Path>,
) -> InstallMethod {
    let components = normalized_components(executable);

    if components.starts_with(&["nix".to_string(), "store".to_string()]) {
        return InstallMethod::Nix;
    }
    if has_sequence(&components, &["cellar", "tuicr"]) {
        return InstallMethod::Homebrew;
    }
    if components.iter().any(|part| part == "installs")
        && components
            .iter()
            .any(|part| part == "mise" || part.contains("agavra-tuicr"))
    {
        return InstallMethod::Mise;
    }

    let cargo_root = cargo_home
        .map(Path::to_path_buf)
        .or_else(|| home.map(|path| path.join(".cargo")));
    if cargo_root.is_some_and(|root| executable.starts_with(root.join("bin"))) {
        return InstallMethod::Cargo;
    }

    InstallMethod::Direct
}

fn has_sequence(components: &[String], parts: &[&str]) -> bool {
    components
        .windows(parts.len())
        .any(|window| window.iter().map(String::as_str).eq(parts.iter().copied()))
}

fn normalized_components(path: &Path) -> Vec<String> {
    path.components()
        .filter_map(|component| match component {
            Component::Normal(value) => Some(value.to_string_lossy().to_ascii_lowercase()),
            Component::Prefix(prefix) => Some(
                prefix
                    .as_os_str()
                    .to_string_lossy()
                    .trim_end_matches(['/', '\\'])
                    .to_ascii_lowercase(),
            ),
            Component::RootDir | Component::CurDir | Component::ParentDir => None,
        })
        .collect()
}