yay/vendor/github.com/mikkeloscar/gopkgbuild/version.go

291 lines
6.0 KiB
Go
Raw Normal View History

2017-07-14 19:03:54 +02:00
package pkgbuild
import (
"fmt"
"strconv"
"strings"
)
// Version string
type Version string
type CompleteVersion struct {
Version Version
Epoch uint8
2017-10-30 13:19:05 +01:00
Pkgrel Version
2017-07-14 19:03:54 +02:00
}
func (c *CompleteVersion) String() string {
2018-02-16 14:44:11 +01:00
if c.Epoch > 0 {
return fmt.Sprintf("%d:%s-%s", c.Epoch, c.Version, c.Pkgrel)
}
return fmt.Sprintf("%s-%s", c.Version, c.Pkgrel)
2017-07-14 19:03:54 +02:00
}
// NewCompleteVersion creates a CompleteVersion including basic version, epoch
// and rel from string
func NewCompleteVersion(s string) (*CompleteVersion, error) {
var err error
epoch := 0
2017-10-30 13:19:05 +01:00
rel := Version("")
2017-07-14 19:03:54 +02:00
// handle possible epoch
versions := strings.Split(s, ":")
if len(versions) > 2 {
return nil, fmt.Errorf("invalid version format: %s", s)
}
if len(versions) > 1 {
epoch, err = strconv.Atoi(versions[0])
if err != nil {
return nil, err
}
}
// handle possible rel
versions = strings.Split(versions[len(versions)-1], "-")
if len(versions) > 2 {
return nil, fmt.Errorf("invalid version format: %s", s)
}
if len(versions) > 1 {
2017-10-30 13:19:05 +01:00
rel = Version(versions[1])
2017-07-14 19:03:54 +02:00
}
// finally check that the actual version is valid
if validPkgver(versions[0]) {
return &CompleteVersion{
Version: Version(versions[0]),
Epoch: uint8(epoch),
2017-10-30 13:19:05 +01:00
Pkgrel: rel,
2017-07-14 19:03:54 +02:00
}, nil
}
return nil, fmt.Errorf("invalid version format: %s", s)
}
// Older returns true if a is older than the argument version string
func (a *CompleteVersion) Older(v string) bool {
b, err := NewCompleteVersion(v)
if err != nil {
return false
}
return a.cmp(b) == -1
}
// Newer returns true if a is newer than the argument version string
func (a *CompleteVersion) Newer(v string) bool {
b, err := NewCompleteVersion(v)
if err != nil {
return false
}
return a.cmp(b) == 1
}
// Equal returns true if a is equal to the argument version string
func (a *CompleteVersion) Equal(v string) bool {
b, err := NewCompleteVersion(v)
if err != nil {
return false
}
return a.cmp(b) == 0
}
// Compare a to b:
// return 1: a is newer than b
// 0: a and b are the same version
// -1: b is newer than a
func (a *CompleteVersion) cmp(b *CompleteVersion) int8 {
if a.Epoch > b.Epoch {
return 1
}
if a.Epoch < b.Epoch {
return -1
}
if a.Version.bigger(b.Version) {
return 1
}
if b.Version.bigger(a.Version) {
return -1
}
2017-10-30 13:19:05 +01:00
if a.Pkgrel.bigger(b.Pkgrel) {
2017-07-14 19:03:54 +02:00
return 1
}
2017-10-30 13:19:05 +01:00
if b.Pkgrel.bigger(a.Pkgrel) {
2017-07-14 19:03:54 +02:00
return -1
}
return 0
}
// Compare alpha and numeric segments of two versions.
// return 1: a is newer than b
// 0: a and b are the same version
// -1: b is newer than a
//
// This is based on the rpmvercmp function used in libalpm
// https://projects.archlinux.org/pacman.git/tree/lib/libalpm/version.c
func rpmvercmp(a, b Version) int {
if a == b {
return 0
}
var one, two, ptr1, ptr2 int
var isNum bool
one, two, ptr1, ptr2 = 0, 0, 0, 0
// loop through each version segment of a and b and compare them
for len(a) > one && len(b) > two {
for len(a) > one && !isAlphaNumeric(a[one]) {
one++
}
for len(b) > two && !isAlphaNumeric(b[two]) {
two++
}
// if we ran to the end of either, we are finished with the loop
if !(len(a) > one && len(b) > two) {
break
}
// if the seperator lengths were different, we are also finished
if one-ptr1 != two-ptr2 {
if one-ptr1 < two-ptr2 {
return -1
}
return 1
}
ptr1 = one
ptr2 = two
// grab first completely alpha or completely numeric segment
// leave one and two pointing to the start of the alpha or numeric
// segment and walk ptr1 and ptr2 to end of segment
if isDigit(a[ptr1]) {
for len(a) > ptr1 && isDigit(a[ptr1]) {
ptr1++
}
for len(b) > ptr2 && isDigit(b[ptr2]) {
ptr2++
}
isNum = true
} else {
for len(a) > ptr1 && isAlpha(a[ptr1]) {
ptr1++
}
for len(b) > ptr2 && isAlpha(b[ptr2]) {
ptr2++
}
isNum = false
}
// take care of the case where the two version segments are
// different types: one numeric, the other alpha (i.e. empty)
// numeric segments are always newer than alpha segments
if two == ptr2 {
if isNum {
return 1
}
return -1
}
if isNum {
// we know this part of the strings only contains digits
// so we can ignore the error value since it should
// always be nil
as, _ := strconv.ParseInt(string(a[one:ptr1]), 10, 0)
bs, _ := strconv.ParseInt(string(b[two:ptr2]), 10, 0)
// whichever number has more digits wins
if as > bs {
return 1
}
if as < bs {
return -1
}
} else {
cmp := alphaCompare(a[one:ptr1], b[two:ptr2])
if cmp < 0 {
return -1
}
if cmp > 0 {
return 1
}
}
// advance one and two to next segment
one = ptr1
two = ptr2
}
// this catches the case where all numeric and alpha segments have
// compared identically but the segment separating characters were
// different
if len(a) <= one && len(b) <= two {
return 0
}
// the final showdown. we never want a remaining alpha string to
// beat an empty string. the logic is a bit weird, but:
// - if one is empty and two is not an alpha, two is newer.
// - if one is an alpha, two is newer.
// - otherwise one is newer.
if (len(a) <= one && !isAlpha(b[two])) || len(a) > one && isAlpha(a[one]) {
return -1
}
return 1
}
// alphaCompare compares two alpha version segments and will return a positive
// value if a is bigger than b and a negative if b is bigger than a else 0
func alphaCompare(a, b Version) int8 {
if a == b {
return 0
}
i := 0
for len(a) > i && len(b) > i && a[i] == b[i] {
i++
}
if len(a) == i && len(b) > i {
return -1
}
if len(b) == i {
return 1
}
return int8(a[i]) - int8(b[i])
}
// check if version number v is bigger than v2
func (v Version) bigger(v2 Version) bool {
return rpmvercmp(v, v2) == 1
}
// isAlphaNumeric reports whether c is an alpha character or digit
func isAlphaNumeric(c uint8) bool {
return isDigit(c) || isAlpha(c)
}
// isAlpha reports whether c is an alpha character
func isAlpha(c uint8) bool {
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
}
// isDigit reports whether d is an ASCII digit
func isDigit(d uint8) bool {
return '0' <= d && d <= '9'
}