package selfupdate import ( "fmt" "strconv" "strings" ) // Version represents a semantic version (Major.Minor.Patch). type Version struct { Major int Minor int Patch int Raw string } // ParseVersion parses "X.Y.Z" or "vX.Y.Z". Returns error for "dev", "latest", or invalid formats. func ParseVersion(s string) (Version, error) { s = strings.TrimPrefix(s, "v") if s == "dev" || s == "latest" || s == "" { return Version{}, fmt.Errorf("invalid version: %q", s) } parts := strings.SplitN(s, ".", 3) if len(parts) != 3 { return Version{}, fmt.Errorf("invalid version format: %q (expected X.Y.Z)", s) } major, err := strconv.Atoi(parts[0]) if err != nil { return Version{}, fmt.Errorf("invalid major version: %w", err) } minor, err := strconv.Atoi(parts[1]) if err != nil { return Version{}, fmt.Errorf("invalid minor version: %w", err) } patch, err := strconv.Atoi(parts[2]) if err != nil { return Version{}, fmt.Errorf("invalid patch version: %w", err) } return Version{Major: major, Minor: minor, Patch: patch, Raw: s}, nil } // Compare returns -1 if a < b, 0 if a == b, 1 if a > b. func (a Version) Compare(b Version) int { if a.Major != b.Major { if a.Major < b.Major { return -1 } return 1 } if a.Minor != b.Minor { if a.Minor < b.Minor { return -1 } return 1 } if a.Patch != b.Patch { if a.Patch < b.Patch { return -1 } return 1 } return 0 } // String returns the version as "X.Y.Z". func (v Version) String() string { return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) }