package detail
import (
"regexp"
"strconv"
"strings"
)
type Version struct {
Org string `json:"org"`
Nums []int `json:"nums,omitempty"`
Suffix string `json:"suffix,omitempty"`
}
type token struct {
link bool
num int
str string
isnum bool
}
var (
suffixs = map[string]int{"alpha": 1, "beta": 2, "milestone": 3, "rc": 4, "cr": 4, "snapshot": 5, "release": 6, "final": 6, "ga": 6, "sp": 7}
numStrReg = regexp.MustCompile(`((\d+)|([a-zA-Z]+))`)
)
func (t token) compare(t2 token) int {
if t.isnum && !t2.isnum {
return 1
} else if !t.isnum && t2.isnum {
return -1
} else if t.isnum && t2.isnum {
if t.num == t2.num {
if !t.link && t2.link {
return 1
} else if t.link && !t2.link {
return -1
} else {
return 0
}
} else {
return t.num - t2.num
}
}
if t.str != t2.str {
w, ok := suffixs[strings.ToLower(t.str)]
w2, ok2 := suffixs[strings.ToLower(t2.str)]
if ok && ok2 {
return w - w2
} else if ok && !ok2 {
return -1
} else if !ok && ok2 {
return 1
}
if t.str > t2.str {
return 1
} else {
return -1
}
}
if t.link != t2.link {
if t.num != 0 {
if !t.link {
return 1
} else {
return -1
}
}
if t.str != "" {
if t.link {
return 1
} else {
return -1
}
}
}
return 0
}
func compareToken(a, b []token) int {
var min int
if len(a) > len(b) {
if a[len(b)].str != "" {
b = append(b, token{link: true, str: "ga"})
}
min = len(b)
} else if len(a) < len(b) {
if b[len(a)].str != "" {
a = append(a, token{link: true, str: "ga"})
}
min = len(a)
} else {
min = len(a)
}
for i := 0; i < min; i++ {
r := a[i].compare(b[i])
if r != 0 {
return r
}
}
return len(a) - len(b)
}
func parseToken(ver string) (tokens []token) {
ver = strings.ToLower(strings.TrimLeft(ver, "vV"))
tokens = []token{}
t := token{isnum: true}
for len(ver) > 0 {
index := strings.IndexAny(ver, `.-`)
for index == 0 {
next := strings.IndexAny(ver[1:], `.-`)
if next == -1 {
index = len(ver)
} else {
index = next + 1
}
}
if index == -1 {
index = len(ver)
}
word := ver[:index]
ver = ver[index:]
if word[0] == '.' || word[0] == '-' {
tokens = append(tokens, t)
t = token{link: word[0] == '-', isnum: word[0] == '.'}
word = word[1:]
}
if n, err := strconv.Atoi(word); err == nil {
t.num = n
t.isnum = true
} else if !strings.ContainsAny(word, `1234567890`) {
t.str = word
} else {
link := false
matchs := numStrReg.FindAllString(word, -1)
for i, match := range matchs {
if n, err := strconv.Atoi(match); err == nil {
t.num = n
t.isnum = true
} else {
if len(match) == 1 && i+1 < len(matchs) {
if match == "a" {
match = "alpha"
} else if match == "b" {
match = "beta"
} else if match == "m" {
match = "milestone"
}
}
t.str = match
}
tokens = append(tokens, t)
t = token{link: true}
link = true
}
if link {
t.link = false
}
}
}
tokens = append(tokens, t)
for i := range tokens {
if tokens[i].str != "" {
s := tokens[i].str
if s == "final" || s == "ga" {
s = ""
}
tokens[i].str = s
tokens[i].isnum = false
}
}
isZero := true
for i := len(tokens) - 1; i >= 0; i-- {
t := tokens[i]
if t.num == 0 {
if t.str == "" {
if isZero || !t.isnum {
tokens = append(tokens[:i], tokens[i+1:]...)
}
} else if t.str != "" {
isZero = true
}
} else {
isZero = false
}
}
return
}
func newVersion(verStr string) *Version {
verStr = strings.TrimSpace(verStr)
ver := &Version{Nums: []int{}, Org: verStr}
verStr = strings.TrimLeft(verStr, "vV^~=<>")
index := strings.Index(verStr, "-")
if index != -1 {
ver.Suffix = verStr[index+1:]
verStr = verStr[:index]
}
tags := strings.Split(verStr, ".")
for i, numStr := range tags {
if num, err := strconv.Atoi(numStr); err == nil {
ver.Nums = append(ver.Nums, num)
} else {
ver.Suffix = strings.Join(tags[i:], ".")
break
}
}
for len(ver.Nums) > 1 {
length := len(ver.Nums)
if ver.Nums[length-1] == 0 {
ver.Nums = ver.Nums[:length-1]
} else {
break
}
}
return ver
}
func (ver *Version) Less(other *Version) bool {
va := strings.TrimLeft(ver.Org, "vV^<>=~!, ")
vb := strings.TrimLeft(other.Org, "vV^<>=~!, ")
ta := parseToken(va)
tb := parseToken(vb)
return compareToken(ta, tb) < 0
}
func (ver *Version) Equal(other *Version) bool {
if len(ver.Nums) != len(other.Nums) {
return false
}
va := strings.TrimLeft(ver.Org, "vV^<>=~!, ")
vb := strings.TrimLeft(other.Org, "vV^<>=~!, ")
ta := parseToken(va)
tb := parseToken(vb)
return compareToken(ta, tb) == 0
}
func (v *Version) Ok() bool {
return !strings.Contains(v.Org, "$") && len(v.Nums) > 0
}
func inRangeInterval(ver *Version, interval string) bool {
for interval := range strings.SplitSeq(interval, "||") {
if len(interval) < 2 {
continue
}
if interval[0] == '{' && interval[len(interval)-1] == '}' {
for v := range strings.SplitSeq(strings.Trim(interval, "{}"), ",") {
if newVersion(v).Equal(ver) {
return true
}
}
}
if interval[0] == '[' && interval[len(interval)-1] == ']' && !strings.Contains(interval, ",") {
if newVersion(strings.Trim(interval, "[]")).Equal(ver) {
return true
}
}
left := interval[0] == '['
right := interval[len(interval)-1] == ']'
index := strings.Index(interval, ",")
if index == -1 {
return false
}
leftValue := newVersion(interval[1:index])
rightValue := newVersion(interval[index+1 : len(interval)-1])
if (left && ver.Equal(leftValue)) || (right && ver.Equal(rightValue)) {
return true
}
if (len(leftValue.Nums) == 0 || leftValue.Less(ver)) && (len(rightValue.Nums) == 0 || ver.Less(rightValue)) {
return true
}
}
return false
}