зеркало из https://github.com/mozilla/scribe.git
310 строки
6.1 KiB
Go
310 строки
6.1 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Contributor:
|
|
// - Aaron Meihm ameihm@mozilla.com
|
|
|
|
package scribe
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// This is a go version of the librpm rpmvercmp() function, that compares
|
|
// two version strings to determine which is newer. It has undergone a
|
|
// series of tests but there are likely to be some edge cases and certain
|
|
// scenarios it does not handle.
|
|
|
|
// EVR operation constants
|
|
const (
|
|
_ = iota
|
|
EvropLessThan
|
|
EvropGreaterThan
|
|
EvropEquals
|
|
EvropUnknown
|
|
)
|
|
|
|
type evr struct {
|
|
epoch string
|
|
version string
|
|
release string
|
|
}
|
|
|
|
func evrLookupOperation(s string) int {
|
|
switch s {
|
|
case "<":
|
|
return EvropLessThan
|
|
case ">":
|
|
return EvropGreaterThan
|
|
case "=":
|
|
return EvropEquals
|
|
}
|
|
return EvropUnknown
|
|
}
|
|
|
|
func evrOperationStr(val int) string {
|
|
switch val {
|
|
case EvropLessThan:
|
|
return "<"
|
|
case EvropEquals:
|
|
return "="
|
|
default:
|
|
return "?"
|
|
}
|
|
}
|
|
|
|
func evrIsDigit(c rune) bool {
|
|
return unicode.IsDigit(c)
|
|
}
|
|
|
|
func evrIsNumber(s string) bool {
|
|
_, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func evrExtract(s string) (evr, error) {
|
|
var ret evr
|
|
var idx int
|
|
|
|
for _, c := range s {
|
|
if !evrIsDigit(c) {
|
|
break
|
|
}
|
|
idx++
|
|
}
|
|
|
|
if idx >= len(s) {
|
|
// The entire version string is a digit; in this case just set the
|
|
// version value to s
|
|
ret.epoch = "0"
|
|
ret.version = s
|
|
ret.release = ""
|
|
return ret, nil
|
|
}
|
|
|
|
if s[idx] == ':' {
|
|
ret.epoch = s[:idx]
|
|
idx++
|
|
} else {
|
|
ret.epoch = "0"
|
|
idx = 0
|
|
}
|
|
|
|
if idx >= len(s) {
|
|
return ret, fmt.Errorf("evrExtract: only epoch")
|
|
}
|
|
remain := s[idx:]
|
|
|
|
rp0 := strings.LastIndex(remain, "-")
|
|
if rp0 != -1 {
|
|
ret.version = remain[:rp0]
|
|
rp0++
|
|
if rp0 >= len(remain) {
|
|
return ret, fmt.Errorf("evrExtract: ends in dash")
|
|
}
|
|
ret.release = remain[rp0:]
|
|
} else {
|
|
ret.version = remain
|
|
ret.release = ""
|
|
}
|
|
|
|
debugPrint("evrExtract(): epoch=%v, version=%v, revision=%v\n", ret.epoch, ret.version, ret.release)
|
|
return ret, nil
|
|
}
|
|
|
|
func evrRpmTokenizer(s string) []string {
|
|
re := regexp.MustCompile("[A-Za-z0-9]+")
|
|
buf := re.FindAllString(s, -1)
|
|
ret := make([]string, 0)
|
|
var isnum bool
|
|
var cmp string
|
|
for _, x := range buf {
|
|
cmp = ""
|
|
for _, c := range x {
|
|
if len(cmp) == 0 {
|
|
if evrIsDigit(c) {
|
|
isnum = true
|
|
} else {
|
|
isnum = false
|
|
}
|
|
cmp += string(c)
|
|
} else {
|
|
if isnum {
|
|
if !evrIsDigit(c) {
|
|
ret = append(ret, cmp)
|
|
cmp = string(c)
|
|
isnum = false
|
|
} else {
|
|
cmp += string(c)
|
|
}
|
|
} else {
|
|
if evrIsDigit(c) {
|
|
ret = append(ret, cmp)
|
|
cmp = string(c)
|
|
isnum = true
|
|
} else {
|
|
cmp += string(c)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ret = append(ret, cmp)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func evrTrimZeros(s string) string {
|
|
if len(s) == 1 {
|
|
return s
|
|
}
|
|
_, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return s
|
|
}
|
|
return strings.TrimLeft(s, "0")
|
|
}
|
|
|
|
func evrRpmVerCmp(actual string, check string) int {
|
|
if actual == check {
|
|
return 0
|
|
}
|
|
|
|
acttokens := evrRpmTokenizer(actual)
|
|
chktokens := evrRpmTokenizer(check)
|
|
|
|
for i := range chktokens {
|
|
if i >= len(acttokens) {
|
|
// There are more tokens in the check value, the
|
|
// check wins.
|
|
return 1
|
|
}
|
|
|
|
// If the values are pure numbers, trim any leading 0's.
|
|
acttest := evrTrimZeros(acttokens[i])
|
|
chktest := evrTrimZeros(chktokens[i])
|
|
|
|
// Numeric component will always win out over alpha.
|
|
if evrIsDigit(rune(acttest[0])) && !evrIsDigit(rune(chktest[0])) {
|
|
return -1
|
|
}
|
|
if evrIsDigit(rune(chktest[0])) && !evrIsDigit(rune(acttest[0])) {
|
|
return 1
|
|
}
|
|
|
|
// If both values are pure numeric values, convert and check here
|
|
if evrIsNumber(acttest) && evrIsNumber(chktest) {
|
|
na, err := strconv.Atoi(acttest)
|
|
if err != nil {
|
|
panic("IsNumber and failed actual conversion")
|
|
}
|
|
nc, err := strconv.Atoi(chktest)
|
|
if err != nil {
|
|
panic("IsNumber and failed check conversion")
|
|
}
|
|
if nc > na {
|
|
return 1
|
|
} else if nc < na {
|
|
return -1
|
|
} else {
|
|
continue
|
|
}
|
|
}
|
|
|
|
// Do a lexical string comparison here, this should work
|
|
// even with pure integer values.
|
|
if chktest > acttest {
|
|
return 1
|
|
} else if chktest < acttest {
|
|
return -1
|
|
}
|
|
}
|
|
|
|
// If we get this far, see if the actual value still has more tokens
|
|
// for comparison, if so actual wins.
|
|
if len(acttokens) > len(chktokens) {
|
|
return -1
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func evrRpmCompare(actual evr, check evr) (int, error) {
|
|
aepoch, err := strconv.Atoi(actual.epoch)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("evrRpmCompare: bad actual epoch")
|
|
}
|
|
cepoch, err := strconv.Atoi(check.epoch)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("evrRpmCompare: bad check epoch")
|
|
}
|
|
if cepoch > aepoch {
|
|
return 1, nil
|
|
} else if cepoch < aepoch {
|
|
return -1, nil
|
|
}
|
|
|
|
ret := evrRpmVerCmp(actual.version, check.version)
|
|
if ret != 0 {
|
|
return ret, nil
|
|
}
|
|
|
|
ret = evrRpmVerCmp(actual.release, check.release)
|
|
if ret != 0 {
|
|
return ret, nil
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func evrCompare(op int, actual string, check string) (bool, error) {
|
|
debugPrint("evrCompare(): %v %v %v\n", actual, evrOperationStr(op), check)
|
|
|
|
evract, err := evrExtract(actual)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
evrchk, err := evrExtract(check)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
ret, err := evrRpmCompare(evract, evrchk)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
switch op {
|
|
case EvropEquals:
|
|
if ret != 0 {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
case EvropLessThan:
|
|
if ret == 1 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
case EvropGreaterThan:
|
|
if ret == -1 {
|
|
return true, nil
|
|
}
|
|
return false, nil
|
|
}
|
|
return false, fmt.Errorf("evrCompare: unknown operator")
|
|
}
|
|
|
|
// TestEvrCompare is an exported version of the EVR comparison operation. op is
|
|
// used to specify an EVR comparison operation (e.g., EvropLessThan). actual and
|
|
// check are the version strings to test. Returns status of test evaluation, or an error
|
|
// if an error occurs.
|
|
func TestEvrCompare(op int, actual string, check string) (bool, error) {
|
|
return evrCompare(op, actual, check)
|
|
}
|