package update import ( "cmp" "strconv" "strings" ) type Version struct { numbers []int pre []string } func ParseVersion(raw string) (Version, error) { trimmed := strings.TrimPrefix(strings.TrimSpace(raw), "v") core, pre, hasPre := strings.Cut(trimmed, "-") if core == "" || (hasPre && pre == "") { return Version{}, ErrInvalidVersion } var version Version for _, segment := range strings.Split(core, ".") { number, err := strconv.Atoi(segment) if err != nil || number < 0 { return Version{}, ErrInvalidVersion } version.numbers = append(version.numbers, number) } if hasPre { version.pre = strings.Split(pre, ".") } return version, nil } func IsNewer(current, latest string) bool { currentVersion, err := ParseVersion(current) if err != nil { return false } latestVersion, err := ParseVersion(latest) if err != nil { return false } return compareVersions(latestVersion, currentVersion) > 0 } func compareVersions(a, b Version) int { if order := compareNumbers(a.numbers, b.numbers); order != 0 { return order } return comparePreRelease(a.pre, b.pre) } func compareNumbers(a, b []int) int { for i := range max(len(a), len(b)) { if order := cmp.Compare(segmentAt(a, i), segmentAt(b, i)); order != 0 { return order } } return 0 } func segmentAt(numbers []int, index int) int { if index < len(numbers) { return numbers[index] } return 0 } func comparePreRelease(a, b []string) int { if len(a) == 0 && len(b) == 0 { return 0 } if len(a) == 0 { return 1 } if len(b) == 0 { return -1 } length := min(len(a), len(b)) for i := 0; i < length; i++ { if order := compareIdentifiers(a[i], b[i]); order != 0 { return order } } return cmp.Compare(len(a), len(b)) } func compareIdentifiers(a, b string) int { numberA, errA := strconv.Atoi(a) numberB, errB := strconv.Atoi(b) if errA == nil && errB == nil { return cmp.Compare(numberA, numberB) } if errA == nil { return -1 } if errB == nil { return 1 } return strings.Compare(a, b) }