File: version.go

package info (click to toggle)
golang-github-cavaliergopher-rpm 1.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 704 kB
  • sloc: python: 84; makefile: 4
file content (140 lines) | stat: -rw-r--r-- 3,208 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package rpm

import (
	"math"
	"regexp"
	"strings"
	"unicode"
)

// alphanumPattern is a regular expression to match all sequences of numeric
// characters or alphanumeric characters.
var alphanumPattern = regexp.MustCompile("([a-zA-Z]+)|([0-9]+)|(~)")

// Version is an interface which holds version information for a package in EVR
// form.
type Version interface {
	Epoch() int
	Version() string
	Release() string
}

// Compare compares the version details of two packages. Versions are
// compared by Epoch, Version and Release (EVR) in descending order of
// precedence.
//
// If a is more recent than b, 1 is returned. If a is less recent than b, -1 is
// returned. If a and b are equal, 0 is returned.
//
// This function does not consider if the two packages have the same name or if
// either package has been made obsolete by the other.
func Compare(a, b Version) int {
	// compare nils
	if a == nil && b == nil {
		return 0
	} else if a == nil {
		return -1
	} else if b == nil {
		return 1
	}

	// compare epoch
	ae := a.Epoch()
	be := b.Epoch()
	if ae != be {
		if ae > be {
			return 1
		}
		return -1
	}

	// compare version
	if rc := CompareVersions(a.Version(), b.Version()); rc != 0 {
		return rc
	}

	// compare release
	return CompareVersions(a.Release(), b.Release())
}

// CompareVersion compares version strings. It does not consider package epochs
// or release numbers like Compare.
//
// If a is more recent than b, 1 is returned. If a is less recent than b, -1 is
// returned. If a and b are equal, 0 is returned.
func CompareVersions(a, b string) int {
	// For the original C implementation, see:
	// https://github.com/rpm-software-management/rpm/blob/master/lib/rpmvercmp.c#L16
	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
}