use std::{env, process::Command};
#[derive(Debug, PartialEq, Eq)]
pub enum InstallMethod {
Homebrew,
Scoop,
Cargo,
FromSource,
Unknown,
}
pub fn get_install_method(override_arg: Option<&str>) -> InstallMethod {
if let Some(value) = override_arg {
match value.trim().to_lowercase().as_str() {
"cargo" => InstallMethod::Cargo,
"scoop" => InstallMethod::Scoop,
"homebrew" => InstallMethod::Homebrew,
"source" | "fromsource" => InstallMethod::FromSource,
_ => InstallMethod::Unknown,
}
} else {
detect_install_method()
}
}
pub fn get_install_method_string(override_arg: Option<&str>) -> &'static str {
match get_install_method(override_arg) {
InstallMethod::Homebrew => "homebrew",
InstallMethod::Scoop => "scoop",
InstallMethod::Cargo => "cargo",
InstallMethod::FromSource => "from source",
InstallMethod::Unknown => "unknown",
}
}
pub fn get_update_command_args(
override_arg: Option<&str>,
) -> Result<(&'static str, Vec<&'static str>), String> {
match get_install_method(override_arg) {
InstallMethod::Homebrew => Ok(("brew", vec!["upgrade", "tod"])),
InstallMethod::Scoop => Ok(("scoop", vec!["update", "tod"])),
InstallMethod::Cargo => Ok(("cargo", vec!["install", "tod", "--force"])),
InstallMethod::FromSource | InstallMethod::Unknown => {
let url = "https://github.com/tod-org/tod#installation";
Err(format!(
"Automatic update is not supported for this installation method.\nPlease visit: {url}"
))
}
}
}
pub fn perform_auto_update(override_arg: Option<&str>) -> Result<String, String> {
let cmd = get_update_command_args(override_arg)?;
let command_str = format!("{} {}", cmd.0, cmd.1.join(" "));
println!("Executing command.... {command_str}");
let status = Command::new(cmd.0)
.args(&cmd.1)
.status()
.map_err(|e| format!("Failed to execute '{}': {}", cmd.0, e))?;
if status.success() {
Ok("Upgraded successfully!".into())
} else {
let upgrade_cmd = get_upgrade_command(override_arg);
Err(format!(
"Automatic update failed. Please run '{upgrade_cmd}' manually."
))
}
}
pub fn get_upgrade_command(override_arg: Option<&str>) -> String {
match get_install_method(override_arg) {
InstallMethod::Homebrew => "brew upgrade tod".to_string(),
InstallMethod::Scoop => "scoop update tod".to_string(),
InstallMethod::Cargo => "cargo install tod --force".to_string(),
InstallMethod::FromSource | InstallMethod::Unknown => {
"https://github.com/tod-org/tod#installation".to_string()
}
}
}
fn detect_install_method() -> InstallMethod {
let Ok(path) = env::current_exe() else {
return InstallMethod::Unknown;
};
let components: Vec<_> = path
.components()
.map(|c| c.as_os_str().to_string_lossy().to_lowercase())
.collect();
if cfg!(debug_assertions) || components.iter().any(|c| c == "target") {
InstallMethod::FromSource
} else if components.iter().any(|c| c.contains(".cargo")) {
InstallMethod::Cargo
} else if components.iter().any(|c| c.contains("scoop")) {
InstallMethod::Scoop
} else if components.iter().any(|c| c.contains("homebrew")) {
InstallMethod::Homebrew
} else {
InstallMethod::Unknown
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_get_install_method_override() {
assert_eq!(get_install_method(Some("cargo")), InstallMethod::Cargo);
assert_eq!(get_install_method(Some("scoop")), InstallMethod::Scoop);
assert_eq!(
get_install_method(Some("homebrew")),
InstallMethod::Homebrew
);
assert_eq!(
get_install_method(Some("source")),
InstallMethod::FromSource
);
assert_eq!(get_install_method(Some("unknown")), InstallMethod::Unknown);
assert_eq!(get_install_method(None), detect_install_method());
}
#[test]
fn detect_install_method_returns_from_source_in_debug_builds() {
assert_eq!(detect_install_method(), InstallMethod::FromSource);
}
#[test]
fn test_get_install_method_string() {
assert_eq!(get_install_method_string(Some("cargo")), "cargo");
assert_eq!(get_install_method_string(Some("scoop")), "scoop");
assert_eq!(get_install_method_string(Some("homebrew")), "homebrew");
assert_eq!(get_install_method_string(Some("source")), "from source");
assert_eq!(get_install_method_string(Some("unknown")), "unknown");
}
#[test]
fn test_get_upgrade_command() {
assert_eq!(
get_upgrade_command(Some("cargo")),
"cargo install tod --force"
);
assert_eq!(get_upgrade_command(Some("scoop")), "scoop update tod");
assert_eq!(get_upgrade_command(Some("homebrew")), "brew upgrade tod");
assert_eq!(
get_upgrade_command(Some("source")),
"https://github.com/tod-org/tod#installation"
);
assert_eq!(
get_upgrade_command(Some("unknown")),
"https://github.com/tod-org/tod#installation"
);
}
#[test]
fn test_get_update_command_args_homebrew() {
let cmd = get_update_command_args(Some("homebrew"))
.expect("Failed to get update command args for homebrew");
assert_eq!(cmd.0, "brew");
assert_eq!(cmd.1, vec!["upgrade", "tod"]);
}
#[test]
fn test_get_update_command_args_scoop() {
let cmd = get_update_command_args(Some("scoop"))
.expect("Failed to get update command args for scoop");
assert_eq!(cmd.0, "scoop");
assert_eq!(cmd.1, vec!["update", "tod"]);
}
#[test]
fn test_get_update_command_args_cargo() {
let cmd = get_update_command_args(Some("cargo"))
.expect("Failed to get update command args for cargo");
assert_eq!(cmd.0, "cargo");
assert_eq!(cmd.1, vec!["install", "tod", "--force"]);
}
#[test]
fn test_get_update_command_args_from_source() {
let err = get_update_command_args(Some("source"))
.expect_err("Expected error when getting update command args for source");
assert!(err.contains("Automatic update is not supported"));
}
#[test]
fn test_get_update_command_args_unknown() {
let err = get_update_command_args(Some("unknown"))
.expect_err("Expected error when getting update command args for unknown");
assert!(err.contains("Automatic update is not supported"));
}
#[test]
fn test_get_install_method_override_whitespace_case() {
assert_eq!(get_install_method(Some(" CaRgO ")), InstallMethod::Cargo);
}
#[test]
fn test_get_install_method_override_random() {
assert_eq!(get_install_method(Some("foobar")), InstallMethod::Unknown);
}
#[test]
fn test_get_update_command_args_none() {
let result = get_update_command_args(None);
assert!(
result.is_ok()
|| result
.unwrap_err()
.contains("Automatic update is not supported")
);
}
}