package kiosk import ( "regexp" "strings" ) var ( validVersionRe = regexp.MustCompile(`^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`) describeRe = regexp.MustCompile(`^(v(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*))(?:-[0-9]+-g[0-9a-f]+)?$`) pseudoBaseRe = regexp.MustCompile(`^(v[0-9]+\.[0-9]+\.[0-9]+)-`) ) // ValidVersion reports whether v is a release version in the strict form // vX.Y.Z: no pre-release, no build metadata, no leading zeros. func ValidVersion(v string) bool { return validVersionRe.MatchString(v) } // CompareVersions compares two valid versions numerically per component // and returns -1, 0 or +1. It is only defined for versions ValidVersion // accepts; never pass a git describe string (use BaseVersion first). func CompareVersions(a, b string) int { pa := strings.Split(strings.TrimPrefix(a, "v"), ".") pb := strings.Split(strings.TrimPrefix(b, "v"), ".") for i := 0; i < len(pa) && i < len(pb); i++ { if c := compareNumeric(pa[i], pb[i]); c != 0 { return c } } switch { case len(pa) < len(pb): return -1 case len(pa) > len(pb): return 1 } return 0 } // compareNumeric compares two decimal strings without leading zeros, of // any length. func compareNumeric(a, b string) int { switch { case len(a) != len(b): if len(a) < len(b) { return -1 } return 1 case a < b: return -1 case a > b: return 1 } return 0 } // BaseVersion returns the vX.Y.Z tag a `git describe --tags` string starts // from: "v0.3.1" for "v0.3.1" and for "v0.3.1-4-gabc1234". Anything else // (a bare commit hash, an empty string, a pre-release tag) gives "". func BaseVersion(describe string) string { m := describeRe.FindStringSubmatch(describe) if m == nil { return "" } return m[1] } // Satisfies reports whether the release have meets the minimum min. An // empty minimum is always met. A pseudo-version minimum such as // vX.Y.Z-0.- sorts just below vX.Y.Z, so it is met by // vX.Y.Z and later. have must be a valid release; anything else meets // only the empty minimum. func Satisfies(have, min string) bool { if min == "" { return true } if !ValidVersion(have) { return false } if ValidVersion(min) { return CompareVersions(have, min) >= 0 } if isPseudoVersion(min) { base := pseudoBaseRe.FindStringSubmatch(min) return base != nil && ValidVersion(base[1]) && CompareVersions(have, base[1]) >= 0 } return false } // NewestCompatible returns the newest release in versions whose framework // minimum the given framework version satisfies, and false when there is // none. Entries whose Version is not a valid release are skipped. func NewestCompatible(versions []RegistryVersion, framework string) (RegistryVersion, bool) { var best RegistryVersion found := false for _, v := range versions { if !ValidVersion(v.Version) || !Satisfies(framework, v.Framework) { continue } if !found || CompareVersions(v.Version, best.Version) > 0 { best, found = v, true } } return best, found }