diff options
| -rw-r--r-- | debVersion.go | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/debVersion.go b/debVersion.go new file mode 100644 index 0000000..c10fc81 --- /dev/null +++ b/debVersion.go @@ -0,0 +1,287 @@ +package fhelp + +// the origin repo was broken. pulled in from: +// MIT License +// Copyright (c) 2017 Teppei Fukuda + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "unicode" +) + +type defaultNumSlice []int + +// get function returns 0, if the slice does not have the specified index. +func (n defaultNumSlice) get(i int) int { + if len(n) > i { + return n[i] + } + return 0 +} + +type defaultStringSlice []string + +// get function returns "", if the slice does not have the specified index. +func (s defaultStringSlice) get(i int) string { + if len(s) > i { + return s[i] + } + return "" +} + +// Version represents a package version (http://man.he.net/man5/deb-version). +type Version struct { + epoch int + upstreamVersion string + debianRevision string +} + +var ( + digitRegexp = regexp.MustCompile(`[0-9]+`) + nonDigitRegexp = regexp.MustCompile(`[^0-9]+`) +) + +// NewDebVersion returns a parsed version +func NewDebVersion(ver string) (version Version, err error) { + // Trim space + ver = strings.TrimSpace(ver) + + // Parse epoch + splitted := strings.SplitN(ver, ":", 2) + if len(splitted) == 1 { + version.epoch = 0 + ver = splitted[0] + } else { + version.epoch, err = strconv.Atoi(splitted[0]) + if err != nil { + return Version{}, fmt.Errorf("epoch parse error: %v", err) + } + + if version.epoch < 0 { + return Version{}, errors.New("epoch is negative") + } + ver = splitted[1] + } + + // Parse upstream_version and debian_revision + index := strings.LastIndex(ver, "-") + if index >= 0 { + version.upstreamVersion = ver[:index] + version.debianRevision = ver[index+1:] + + } else { + version.upstreamVersion = ver + } + + // Verify upstream_version is valid + err = verifyUpstreamVersion(version.upstreamVersion) + if err != nil { + return Version{}, err + } + + // Verify debian_revision is valid + err = verifyDebianRevision(version.debianRevision) + if err != nil { + return Version{}, err + } + + return version, nil +} + +func verifyUpstreamVersion(str string) error { + if len(str) == 0 { + return errors.New("upstream_version is empty") + } + + // The upstream-version should start with a digit + if !unicode.IsDigit(rune(str[0])) { + return errors.New("upstream_version must start with digit") + } + + // The upstream-version may contain only alphanumerics("A-Za-z0-9") and the characters .+-:~ + allowedSymbols := ".-+~:_" + for _, s := range str { + if !unicode.IsDigit(s) && !unicode.IsLetter(s) && !strings.ContainsRune(allowedSymbols, s) { + return errors.New("upstream_version includes invalid character") + } + } + return nil +} + +func verifyDebianRevision(str string) error { + // The debian-revision may contain only alphanumerics and the characters +.~ + allowedSymbols := "+.~_" + for _, s := range str { + if !unicode.IsDigit(s) && !unicode.IsLetter(s) && !strings.ContainsRune(allowedSymbols, s) { + return errors.New("debian_revision includes invalid character") + } + } + return nil +} + +// Valid validates the version +func Valid(ver string) bool { + _, err := NewDebVersion(ver) + return err == nil +} + +// Equal returns whether this version is equal with another version. +func (v1 *Version) Equal(v2 Version) bool { + return v1.Compare(v2) == 0 +} + +// GreaterThan returns whether this version is greater than another version. +func (v1 *Version) GreaterThan(v2 Version) bool { + return v1.Compare(v2) > 0 +} + +// LessThan returns whether this version is less than another version. +func (v1 *Version) LessThan(v2 Version) bool { + return v1.Compare(v2) < 0 +} + +// Compare returns an integer comparing two version according to deb-version. +// The result will be 0 if v1==v2, -1 if v1 < v2, and +1 if v1 > v2. +func (v1 *Version) Compare(v2 Version) int { + // Equal + if reflect.DeepEqual(v1, v2) { + return 0 + } + + // Compare epochs + if v1.epoch > v2.epoch { + return 1 + } else if v1.epoch < v2.epoch { + return -1 + } + + // Compare version + ret := compare(v1.upstreamVersion, v2.upstreamVersion) + if ret != 0 { + return ret + } + + //Compare debian_revision + return compare(v1.debianRevision, v2.debianRevision) +} + +// String returns the full version string +func (v1 *Version) String() string { + version := "" + if v1.epoch > 0 { + version += fmt.Sprintf("%d:", v1.epoch) + } + version += v1.upstreamVersion + + if v1.debianRevision != "" { + version += fmt.Sprintf("-%s", v1.debianRevision) + + } + return version +} + +func (v1 *Version) Epoch() int { + return v1.epoch +} + +func (v1 *Version) Version() string { + return v1.upstreamVersion +} + +func (v1 *Version) Revision() string { + return v1.debianRevision +} + +func compare(v1, v2 string) int { + // Equal + if v1 == v2 { + return 0 + } + + // Extract digit strings and non-digit strings + numbers1, strings1 := extract(v1) + numbers2, strings2 := extract(v2) + + if len(v1) > 0 && unicode.IsDigit(rune(v1[0])) { + strings1 = append([]string{""}, strings1...) + } + if len(v2) > 0 && unicode.IsDigit(rune(v2[0])) { + strings2 = append([]string{""}, strings2...) + } + + index := max(len(numbers1), len(numbers2), len(strings1), len(strings2)) + for i := 0; i < index; i++ { + // Compare non-digit strings + diff := compareString(strings1.get(i), strings2.get(i)) + if diff != 0 { + return diff + } + + // Compare digit strings + diff = numbers1.get(i) - numbers2.get(i) + if diff != 0 { + return diff + } + } + + return 0 +} + +func compareString(s1, s2 string) int { + if s1 == s2 { + return 0 + } + + index := max(len(s1), len(s2)) + for i := 0; i < index; i++ { + a := 0 + if i < len(s1) { + a = order(rune(s1[i])) + } + + b := 0 + if i < len(s2) { + b = order(rune(s2[i])) + } + + if a != b { + return a - b + } + } + return 0 +} + +// order function returns the number corresponding to rune +func order(r rune) int { + // all the letters sort earlier than all the non-letters + if unicode.IsLetter(r) { + return int(r) + } + + // a tilde sorts before anything + if r == '~' { + return -1 + } + + return int(r) + 256 +} + +func extract(version string) (defaultNumSlice, defaultStringSlice) { + numbers := digitRegexp.FindAllString(version, -1) + + var dnum defaultNumSlice + for _, number := range numbers { + n, _ := strconv.Atoi(number) + dnum = append(dnum, n) + } + + s := nonDigitRegexp.FindAllString(version, -1) + + return dnum, defaultStringSlice(s) + +} |
