armup 1.1.0

Install embedded Cortex-M tools on Windows
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display};
use std::str::FromStr;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum EnvScope {
    User,
    None,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ToolKind {
    ArmNoneEabiGcc,
    Clangd,
    Cmake,
    Ninja,
    ProbeRs,
    XpackOpenocd,
}

impl ToolKind {
    pub fn all() -> Vec<Self> {
        vec![
            Self::ArmNoneEabiGcc,
            Self::Clangd,
            Self::Cmake,
            Self::Ninja,
            Self::ProbeRs,
            Self::XpackOpenocd,
        ]
    }

    pub fn id(self) -> &'static str {
        match self {
            Self::ArmNoneEabiGcc => "arm-none-eabi-gcc",
            Self::Clangd => "clangd",
            Self::Cmake => "cmake",
            Self::Ninja => "ninja",
            Self::ProbeRs => "probe-rs",
            Self::XpackOpenocd => "xpack-openocd",
        }
    }

    pub fn executable_names(self) -> &'static [&'static str] {
        match self {
            Self::ArmNoneEabiGcc => &["arm-none-eabi-gcc.exe"],
            Self::Clangd => &["clangd.exe"],
            Self::Cmake => &["cmake.exe"],
            Self::Ninja => &["ninja.exe"],
            Self::ProbeRs => &["probe-rs.exe"],
            Self::XpackOpenocd => &["openocd.exe"],
        }
    }

    pub fn matches_github_asset(self, asset_name: &str) -> bool {
        match self {
            Self::Clangd => {
                asset_name.starts_with("clangd-windows-")
                    && asset_name.ends_with(".zip")
                    && !asset_name.contains("indexing-tools")
            }
            Self::Cmake => {
                asset_name.starts_with("cmake-") && asset_name.ends_with("-windows-x86_64.zip")
            }
            Self::Ninja => asset_name.eq_ignore_ascii_case("ninja-win.zip"),
            Self::ProbeRs => {
                asset_name.eq_ignore_ascii_case("probe-rs-tools-x86_64-pc-windows-msvc.zip")
            }
            Self::XpackOpenocd => {
                asset_name.starts_with("xpack-openocd-") && asset_name.ends_with("-win32-x64.zip")
            }
            Self::ArmNoneEabiGcc => false,
        }
    }

    pub fn picker_label(self) -> String {
        match self {
            Self::ArmNoneEabiGcc => {
                "arm-none-eabi-gcc | Arm GNU Toolchain for Cortex-M".to_string()
            }
            Self::Clangd => "clangd | C/C++ language server".to_string(),
            Self::Cmake => "cmake | Build system generator".to_string(),
            Self::Ninja => "ninja | Fast build executor".to_string(),
            Self::ProbeRs => "probe-rs | Flash and debug embedded targets".to_string(),
            Self::XpackOpenocd => "xpack-openocd | Debug probe and flash server".to_string(),
        }
    }
}

impl Display for ToolKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.id())
    }
}

impl FromStr for ToolKind {
    type Err = String;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        let normalized = value.trim().to_ascii_lowercase();
        match normalized.as_str() {
            "arm-none-eabi-gcc" | "gcc" | "arm-gcc" => Ok(Self::ArmNoneEabiGcc),
            "clangd" => Ok(Self::Clangd),
            "cmake" => Ok(Self::Cmake),
            "ninja" => Ok(Self::Ninja),
            "probe-rs" | "probe-rs-tools" | "probe" => Ok(Self::ProbeRs),
            "xpack-openocd" | "openocd" => Ok(Self::XpackOpenocd),
            _ => Err(format!("unsupported tool {value:?}")),
        }
    }
}