use anyhow::{bail, Result};
use std::fmt;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct GoVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
pub const GO_VERSION_FILE: &str = ".go-version";
pub const TOOL_VERSIONS_FILE: &str = ".tool-versions";
pub fn parse_tool_versions_golang_line(content: &str) -> Option<&str> {
content.lines().find_map(|line| {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
return None;
}
let mut parts = line.split_whitespace();
if parts.next()? != "golang" {
return None;
}
parts.next()
})
}
impl GoVersion {
pub fn parse(input: &str) -> Result<Self> {
let s = input.trim().strip_prefix("go").unwrap_or(input.trim());
let parts: Vec<&str> = s.split('.').collect();
match parts.as_slice() {
[major, minor] => Ok(Self {
major: major.parse()?,
minor: minor.parse()?,
patch: 0,
}),
[major, minor, patch] => Ok(Self {
major: major.parse()?,
minor: minor.parse()?,
patch: patch.parse()?,
}),
_ => bail!(
"Invalid version '{}'. Use X.Y or X.Y.Z (e.g. 1.22 or 1.22.4)",
input
),
}
}
pub fn tag(&self) -> String {
if self.patch == 0 {
format!("go{}.{}", self.major, self.minor)
} else {
format!("go{}.{}.{}", self.major, self.minor, self.patch)
}
}
}
impl fmt::Display for GoVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.patch == 0 {
write!(f, "{}.{}", self.major, self.minor)
} else {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
}
impl PartialOrd for GoVersion {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for GoVersion {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.major
.cmp(&other.major)
.then(self.minor.cmp(&other.minor))
.then(self.patch.cmp(&other.patch))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_tool_versions_finds_the_golang_line() {
let content = "nodejs 20.11.0\ngolang 1.22.4\npython 3.12.1\n";
assert_eq!(parse_tool_versions_golang_line(content), Some("1.22.4"));
}
#[test]
fn parse_tool_versions_takes_only_the_first_fallback_version() {
let content = "golang 1.22.4 1.21.9\n";
assert_eq!(parse_tool_versions_golang_line(content), Some("1.22.4"));
}
#[test]
fn parse_tool_versions_ignores_blank_lines_and_comments() {
let content = "\n# managed by asdf\n\ngolang 1.22.4\n";
assert_eq!(parse_tool_versions_golang_line(content), Some("1.22.4"));
}
#[test]
fn parse_tool_versions_ignores_tools_with_golang_as_a_prefix() {
let content = "golangci-lint 1.55.2\n";
assert_eq!(parse_tool_versions_golang_line(content), None);
}
#[test]
fn parse_tool_versions_returns_none_when_absent() {
let content = "nodejs 20.11.0\npython 3.12.1\n";
assert_eq!(parse_tool_versions_golang_line(content), None);
}
#[test]
fn parse_tool_versions_returns_none_for_empty_content() {
assert_eq!(parse_tool_versions_golang_line(""), None);
}
}