Files
summercms/modules/kiosk/version.go
Jakub Zych 4916ee3d43 feat(kiosk): registry v1 wire types and version rules
- RegistryAPIPath, RegistryPlugin, RegistryPluginDetail, RegistryVersion, envelopes, errors, ingest types
- ValidVersion, CompareVersions, BaseVersion, Satisfies, NewestCompatible
- docs: Registry API v1 reference in plugins/marketplace.md
2026-10-29 17:05:41 +01:00

106 lines
3.0 KiB
Go

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.<timestamp>-<hash> 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
}