This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/semver.go
Jeffrey Smith 4da25350ac
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-sqlite (push) Successful in 2m41s
CI/CD / test-go-pg (push) Successful in 2m42s
CI/CD / build-and-deploy (push) Successful in 29s
Feat v0.5.4 package updates (#34)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-30 19:31:45 +00:00

98 lines
2.3 KiB
Go

package handlers
import (
"fmt"
"strconv"
"strings"
)
// Semver represents a parsed semantic version (major.minor.patch[-prerelease]).
type Semver struct {
Major int
Minor int
Patch int
Prerelease string
}
// ParseSemver parses a version string like "1.2.3" or "v1.2.3-beta.1".
// Leading "v" is stripped. Prerelease suffix after "-" is preserved.
func ParseSemver(s string) (Semver, error) {
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "v")
if s == "" {
return Semver{}, fmt.Errorf("empty version string")
}
// Split off prerelease suffix
var pre string
if idx := strings.IndexByte(s, '-'); idx >= 0 {
pre = s[idx+1:]
s = s[:idx]
}
parts := strings.Split(s, ".")
if len(parts) != 3 {
return Semver{}, fmt.Errorf("version %q must have exactly 3 parts (major.minor.patch)", s)
}
major, err := strconv.Atoi(parts[0])
if err != nil {
return Semver{}, fmt.Errorf("invalid major version %q: %w", parts[0], err)
}
minor, err := strconv.Atoi(parts[1])
if err != nil {
return Semver{}, fmt.Errorf("invalid minor version %q: %w", parts[1], err)
}
patch, err := strconv.Atoi(parts[2])
if err != nil {
return Semver{}, fmt.Errorf("invalid patch version %q: %w", parts[2], err)
}
return Semver{Major: major, Minor: minor, Patch: patch, Prerelease: pre}, nil
}
// Compare returns -1 if a < b, 0 if a == b, +1 if a > b.
// Prerelease versions sort lower than the same release version (per semver spec).
func (a Semver) Compare(b Semver) int {
if a.Major != b.Major {
return cmpInt(a.Major, b.Major)
}
if a.Minor != b.Minor {
return cmpInt(a.Minor, b.Minor)
}
if a.Patch != b.Patch {
return cmpInt(a.Patch, b.Patch)
}
// Same numeric version — prerelease < release
if a.Prerelease == b.Prerelease {
return 0
}
if a.Prerelease == "" {
return 1 // a is release, b has prerelease → a > b
}
if b.Prerelease == "" {
return -1 // a has prerelease, b is release → a < b
}
// Both have prerelease — compare lexicographically
if a.Prerelease < b.Prerelease {
return -1
}
return 1
}
// String returns the version in "major.minor.patch[-prerelease]" format.
func (v Semver) String() string {
s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.Prerelease != "" {
s += "-" + v.Prerelease
}
return s
}
func cmpInt(a, b int) int {
if a < b {
return -1
}
return 1
}