package updater import ( "fmt" "strconv" "strings" ) // Version is a parsed semantic version. Only what a release-tag comparison // needs is modelled: major.minor.patch plus an optional pre-release, which // sorts before the same version without one. type Version struct { Major, Minor, Patch int Pre string } // ParseVersion accepts "v1.2.0", "1.2.0", "1.2", "1" and "1.2.0-rc1". Build // metadata after "+" is ignored, as semver says it must be for ordering. func ParseVersion(s string) (Version, error) { s = strings.TrimSpace(s) s = strings.TrimPrefix(s, "v") if s == "" { return Version{}, fmt.Errorf("empty version") } if i := strings.IndexByte(s, '+'); i >= 0 { s = s[:i] } var v Version if i := strings.IndexByte(s, '-'); i >= 0 { v.Pre = s[i+1:] s = s[:i] } parts := strings.Split(s, ".") if len(parts) > 3 { return Version{}, fmt.Errorf("%q has too many components", s) } dst := []*int{&v.Major, &v.Minor, &v.Patch} for i, p := range parts { n, err := strconv.Atoi(p) if err != nil || n < 0 { return Version{}, fmt.Errorf("%q is not a version number", s) } *dst[i] = n } return v, nil } // Compare returns -1, 0 or 1 as v sorts before, equal to, or after other. func (v Version) Compare(other Version) int { for _, pair := range [][2]int{ {v.Major, other.Major}, {v.Minor, other.Minor}, {v.Patch, other.Patch}, } { if pair[0] != pair[1] { if pair[0] < pair[1] { return -1 } return 1 } } // A pre-release sorts before the release it leads up to. switch { case v.Pre == "" && other.Pre == "": return 0 case v.Pre == "": return 1 case other.Pre == "": return -1 case v.Pre < other.Pre: return -1 case v.Pre > other.Pre: return 1 } return 0 } func (v Version) String() string { s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch) if v.Pre != "" { s += "-" + v.Pre } return s } // IsNewer reports whether candidate is a strictly later version than current. // An unparseable version on either side means "no update", because offering a // download on the strength of a tag we could not read is worse than silence. func IsNewer(candidate, current string) bool { c, err := ParseVersion(candidate) if err != nil { return false } cur, err := ParseVersion(current) if err != nil { return false } return c.Compare(cur) > 0 }