File: version.go

package info (click to toggle)
golang-github-apparentlymart-go-versions 1.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid
  • size: 292 kB
  • sloc: makefile: 2
file content (222 lines) | stat: -rw-r--r-- 5,840 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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package versions

import (
	"fmt"
	"strings"
)

// Version represents a single version.
type Version struct {
	Major      uint64
	Minor      uint64
	Patch      uint64
	Prerelease VersionExtra
	Metadata   VersionExtra
}

// Unspecified is the zero value of Version and represents the absense of a
// version number.
//
// Note that this is indistinguishable from the explicit version that
// results from parsing the string "0.0.0".
var Unspecified Version

// Same returns true if the receiver has the same precedence as the other
// given version. In other words, it has the same major, minor and patch
// version number and an identical prerelease portion. The Metadata, if
// any, is not considered.
func (v Version) Same(other Version) bool {
	return (v.Major == other.Major &&
		v.Minor == other.Minor &&
		v.Patch == other.Patch &&
		v.Prerelease == other.Prerelease)
}

// Comparable returns a version that is the same as the receiver but its
// metadata is the empty string. For Comparable versions, the standard
// equality operator == is equivalent to method Same.
func (v Version) Comparable() Version {
	v.Metadata = ""
	return v
}

// String is an implementation of fmt.Stringer that returns the receiver
// in the canonical "semver" format.
func (v Version) String() string {
	s := fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
	if v.Prerelease != "" {
		s = fmt.Sprintf("%s-%s", s, v.Prerelease)
	}
	if v.Metadata != "" {
		s = fmt.Sprintf("%s+%s", s, v.Metadata)
	}
	return s
}

func (v Version) GoString() string {
	return fmt.Sprintf("versions.MustParseVersion(%q)", v.String())
}

// LessThan returns true if the receiver has a lower precedence than the
// other given version, as defined by the semantic versioning specification.
func (v Version) LessThan(other Version) bool {
	switch {
	case v.Major != other.Major:
		return v.Major < other.Major
	case v.Minor != other.Minor:
		return v.Minor < other.Minor
	case v.Patch != other.Patch:
		return v.Patch < other.Patch
	case v.Prerelease != other.Prerelease:
		if v.Prerelease == "" {
			return false
		}
		if other.Prerelease == "" {
			return true
		}
		return v.Prerelease.LessThan(other.Prerelease)
	default:
		return false
	}
}

// GreaterThan returns true if the receiver has a higher precedence than the
// other given version, as defined by the semantic versioning specification.
func (v Version) GreaterThan(other Version) bool {
	switch {
	case v.Major != other.Major:
		return v.Major > other.Major
	case v.Minor != other.Minor:
		return v.Minor > other.Minor
	case v.Patch != other.Patch:
		return v.Patch > other.Patch
	case v.Prerelease != other.Prerelease:
		if v.Prerelease == "" {
			return true
		}
		if other.Prerelease == "" {
			return false
		}
		return !v.Prerelease.LessThan(other.Prerelease)
	default:
		return false
	}
}

// MarshalText is an implementation of encoding.TextMarshaler, allowing versions
// to be automatically marshalled for text-based serialization formats,
// including encoding/json.
//
// The format used is that returned by String, which can be parsed using
// ParseVersion.
func (v Version) MarshalText() (text []byte, err error) {
	return []byte(v.String()), nil
}

// UnmarshalText is an implementation of encoding.TextUnmarshaler, allowing
// versions to be automatically unmarshalled from strings in text-based
// serialization formats, including encoding/json.
//
// The format expected is what is accepted by ParseVersion. Any parser errors
// are passed on verbatim to the caller.
func (v *Version) UnmarshalText(text []byte) error {
	str := string(text)
	new, err := ParseVersion(str)
	if err != nil {
		return err
	}
	*v = new
	return nil
}

// VersionExtra represents a string containing dot-delimited tokens, as used
// in the pre-release and build metadata portions of a Semantic Versioning
// version expression.
type VersionExtra string

// Parts tokenizes the string into its separate parts by splitting on dots.
//
// The result is undefined if the receiver is not valid per the semver spec,
func (e VersionExtra) Parts() []string {
	return strings.Split(string(e), ".")
}

func (e VersionExtra) Raw() string {
	return string(e)
}

// LessThan returns true if the receiever has lower precedence than the
// other given VersionExtra string, per the rules defined in the semver
// spec for pre-release versions.
//
// Build metadata has no defined precedence rules, so it is not meaningful
// to call this method on a VersionExtra representing build metadata.
func (e VersionExtra) LessThan(other VersionExtra) bool {
	if e == other {
		// Easy path
		return false
	}

	s1 := string(e)
	s2 := string(other)
	for {
		d1 := strings.IndexByte(s1, '.')
		d2 := strings.IndexByte(s2, '.')

		switch {
		case d1 == -1 && d2 != -1:
			// s1 has fewer parts, so it precedes s2
			return true
		case d2 == -1 && d1 != -1:
			// s1 has more parts, so it succeeds s2
			return false
		case d1 == -1: // d2 must be -1 too, because of the above
			// this is our last portion to compare
			return lessThanStr(s1, s2)
		default:
			s1s := s1[:d1]
			s2s := s2[:d2]
			if s1s != s2s {
				return lessThanStr(s1s, s2s)
			}
			s1 = s1[d1+1:]
			s2 = s2[d2+1:]
		}
	}
}

func lessThanStr(s1, s2 string) bool {
	// How we compare here depends on whether the string is entirely consistent of digits
	s1Numeric := true
	s2Numeric := true
	for _, c := range s1 {
		if c < '0' || c > '9' {
			s1Numeric = false
			break
		}
	}
	for _, c := range s2 {
		if c < '0' || c > '9' {
			s2Numeric = false
			break
		}
	}

	switch {
	case s1Numeric && !s2Numeric:
		return true
	case s2Numeric && !s1Numeric:
		return false
	case s1Numeric: // s2Numeric must also be true
		switch {
		case len(s1) < len(s2):
			return true
		case len(s2) < len(s1):
			return false
		default:
			return s1 < s2
		}
	default:
		return s1 < s2
	}
}