use std::{
env,
path::{Path, PathBuf},
process::Command,
};
use crate::{
find::{Bump, Find},
pm::{
core::{
types::{Categorizable, Category, PmInfo, Tool, ToolLister},
updater::update_cmd,
upstream::Upstream,
version::VersionExt,
},
find_all_pms_with_args,
},
};
pub struct Go;
impl Go {
const NAME: &'static str = "go";
fn get_tool_version(binary_path: &Path) -> Option<String> {
let output = Command::new("go")
.args(["version", "-m", binary_path.to_str()?])
.output()
.ok()?;
if !output.status.success() {
return None;
}
String::from_utf8_lossy(&output.stdout)
.lines()
.find(|line| line.trim_start().starts_with("mod\t"))
.and_then(|line| {
let parts: Vec<&str> = line.trim_start().split('\t').collect();
parts.get(2).map(|v| v.to_string())
})
}
}
impl Find for Go {
type Output = PmInfo;
fn name(&self) -> &'static str {
Self::NAME
}
fn search_paths(&self) -> &'static [&'static str] {
&[
"/usr/local/go/bin/go",
"/opt/homebrew/opt/go/libexec/bin/go",
"/usr/local/opt/go/libexec/bin/go",
"/home/linuxbrew/.linuxbrew/opt/go/libexec/bin/go",
]
}
fn find(&self) -> Vec<PmInfo> {
find_all_pms_with_args(Self::NAME, &["version"])
.into_iter()
.map(|mut pm_info| {
if let Some(version_info) = pm_info.version.split_whitespace().nth(2) {
let version = match version_info.strip_prefix("go") {
Some(rest) if rest.starts_with(|c: char| c.is_ascii_digit()) => rest,
_ => version_info,
};
pm_info.version = version.to_string();
}
pm_info
})
.collect()
}
fn check_bump(&self, pm_info: &PmInfo) -> Option<Bump> {
let http = ureq::agent();
let latest = Upstream::GitHub {
owner: "golang",
repo: "go",
}
.latest(&http)
.ok()?;
let cmd = update_cmd(Self::NAME, &pm_info.install_method);
Some(Bump { latest, cmd })
}
}
impl Categorizable for Go {
fn category(&self) -> Category {
Category::Go
}
}
impl ToolLister for Go {
fn name(&self) -> &'static str {
Self::NAME
}
fn is_available(&self) -> bool {
Command::new("go").arg("version").output().is_ok()
}
fn list(&self) -> Vec<Tool> {
let gopath = match Command::new("go").args(["env", "GOPATH"]).output() {
Ok(output) if output.status.success() => {
String::from_utf8_lossy(&output.stdout).trim().to_string()
},
_ => env::var("GOPATH").unwrap_or_else(|_| {
env::var("HOME")
.map(|home| format!("{}/go", home))
.unwrap_or_else(|_| "/usr/local/go".to_string())
}),
};
let bin_dir = PathBuf::from(gopath).join("bin");
if !bin_dir.exists() {
return Vec::new();
}
let entries = match std::fs::read_dir(&bin_dir) {
Ok(entries) => entries,
Err(_) => return Vec::new(),
};
entries
.filter_map(Result::ok)
.filter(|entry| {
entry
.file_type()
.map(|ft| ft.is_file() || ft.is_symlink())
.unwrap_or(false)
})
.filter_map(|entry| {
let path = entry.path();
let name = path.file_name()?.to_str()?.to_string();
let version = Self::get_tool_version(&path)
.as_deref()
.version_or_unknown();
Some(Tool {
name,
version,
path: Some(path.to_string_lossy().to_string()),
manager: Self::NAME.to_string(),
})
})
.collect()
}
fn owns(&self, tool_name: &str) -> Option<Tool> {
self.list().into_iter().find(|t| t.name == tool_name)
}
}