Arch Linux helpers and parsers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

188 lines
3.8 KiB

/*
Package version implements version and dependency parsing
The version sorting algorithm is as implemented by the RedHat Package Manager
(RPM) algorithm.
*/
package version
import (
"fmt"
"math"
"regexp"
"strings"
"unicode"
)
// Version is an EVR (epoch, version, release) tuple
type Version struct {
Epoch string
Version string
Release string
}
func (v Version) String() string {
if v.Epoch != "" && v.Epoch != "0" {
return fmt.Sprintf("%s:%s-%s", v.Epoch, v.Version, v.Release)
}
if v.Release != "" {
return fmt.Sprintf("%s-%s", v.Version, v.Release)
}
return v.Version
}
// Parse parses an EVR (Epoch:Version-Release) version string.
func Parse(evr string) Version {
var epoch, version, release string
if i := strings.IndexByte(evr, ':'); i != -1 {
epoch, evr = evr[:i], evr[i+1:]
}
if epoch == "0" {
epoch = ""
}
if i := strings.IndexByte(evr, '-'); i != -1 {
version, release = evr[:i], evr[i+1:]
} else {
version = evr
}
return Version{epoch, version, release}
}
// LessThan returns true if this version is less than the other version.
func (v Version) LessThan(other Version) bool {
return Compare(v, other) < 0
}
// Versions is a sort.Sortable slice of Version
type Versions []Version
func (vs Versions) Len() int {
return len(vs)
}
func (vs Versions) Less(i, j int) bool {
return vs[i].LessThan(vs[j])
}
func (vs Versions) Swap(i, j int) {
vs[i], vs[j] = vs[j], vs[i]
}
var alphanumPattern = regexp.MustCompile("([a-zA-Z]+)|([0-9]+)|(~)")
// rpmvercmp implements https://github.com/rpm-software-management/rpm/blob/master/lib/rpmvercmp.c
func rpmvercmp(a, b string) int {
// shortcut for equality
if a == b {
return 0
}
// get alpha/numeric segements
segsa := alphanumPattern.FindAllString(a, -1)
segsb := alphanumPattern.FindAllString(b, -1)
segs := int(math.Min(float64(len(segsa)), float64(len(segsb))))
// compare each segment
for i := 0; i < segs; i++ {
a := segsa[i]
b := segsb[i]
// compare tildes
if []rune(a)[0] == '~' || []rune(b)[0] == '~' {
if []rune(a)[0] != '~' {
return 1
}
if []rune(b)[0] != '~' {
return -1
}
}
if unicode.IsNumber([]rune(a)[0]) {
// numbers are always greater than alphas
if !unicode.IsNumber([]rune(b)[0]) {
// a is numeric, b is alpha
return 1
}
// trim leading zeros
a = strings.TrimLeft(a, "0")
b = strings.TrimLeft(b, "0")
// longest string wins without further comparison
if len(a) > len(b) {
return 1
} else if len(b) > len(a) {
return -1
}
} else if unicode.IsNumber([]rune(b)[0]) {
// a is alpha, b is numeric
return -1
}
// string compare
if a < b {
return -1
} else if a > b {
return 1
}
}
// segments were all the same but separators must have been different
if len(segsa) == len(segsb) {
return 0
}
// If there is a tilde in a segment past the min number of segments, find it.
if len(segsa) > segs && []rune(segsa[segs])[0] == '~' {
return -1
} else if len(segsb) > segs && []rune(segsb[segs])[0] == '~' {
return 1
}
// whoever has the most segments wins
if len(segsa) > len(segsb) {
return 1
}
return -1
}
// Compare compares two versions
func Compare(a, b Version) int {
var r int
if r = rpmvercmp(a.Epoch, b.Epoch); r == 0 {
if r = rpmvercmp(a.Version, b.Version); r == 0 {
return rpmvercmp(a.Release, b.Release)
}
}
return r
}
// CompareString compares two version strings
func CompareString(a, b string) int {
if a == "" && b == "" || a == b {
return 0
} else if a == "" {
return -1
} else if b == "" {
return 1
}
av := Parse(a)
bv := Parse(b)
return Compare(av, bv)
}
func isalpha(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
}
func isdigit(b byte) bool {
return b >= '0' && b <= '9'
}
func isalnum(b byte) bool {
return isdigit(b) || isalpha(b)
}