-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathversion.go
More file actions
189 lines (151 loc) · 4.96 KB
/
Copy pathversion.go
File metadata and controls
189 lines (151 loc) · 4.96 KB
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
package cage
import (
"errors"
"fmt"
"regexp"
"slices"
"strings"
"golang.org/x/mod/semver"
)
// ================
// ==== Semver ====
// ================
// Semver is a type definition for a version that follows semantic versioning
type Semver string
// NewSemver creates a new [Semver] and checks if it's valid
func NewSemver(version string) (Semver, error) {
fmtVersion := version
if strings.HasPrefix(version, "V") {
fmtVersion = fmt.Sprintf("v%s", version[1:])
} else if !strings.HasPrefix(version, "v") {
fmtVersion = fmt.Sprintf("v%s", version)
}
if !semver.IsValid(fmtVersion) {
return "", fmt.Errorf("\"%s\" is not a valid version", fmtVersion)
}
return Semver(fmtVersion), nil
}
// IsValid checks if the [Semver] is valid
func (s Semver) IsValid() bool {
return semver.IsValid(string(s))
}
// Before checks if the passed [Semver] is smaller than the original [Semver]
func (s Semver) Before(sv Semver) bool {
return semver.Compare(string(s), string(sv)) == -1
}
// Before checks if the passed [Semver] is bigger than the original [Semver]
func (s Semver) After(sv Semver) bool {
return semver.Compare(string(s), string(sv)) == 1
}
// Equals checks if the passed [Semver] is equal to the original [Semver]
func (s Semver) Equals(sv Semver) bool {
return semver.Compare(string(s), string(sv)) == 0
}
// ======================
// ==== VersionRange ====
// ======================
// operators is a slice of all the supported operators
var operators = []string{">", ">=", "<", "<=", "=", "==", ""}
// VersionRange is used to store ranges of versions. If `start` is empty, the
// range will be `< end`. If `end` is empty, then it means `== start`.
type VersionRange struct {
Start Semver `json:"start"`
End Semver `json:"end"`
IncludeLeft bool `json:"left"`
IncludeRight bool `json:"right"`
}
// NewVersionRange creates a new [VersionRanges] and checks if it's valid
func NewVersionRange(start, end Semver, left, right bool) (*VersionRange, error) {
if start == "" && end == "" {
return nil, errors.New("at least one semver needs not to be empty")
}
if (start == "" && !end.IsValid()) || (!start.IsValid() && end == "") {
return nil, errors.New("if one semver is empty, the other needs to be valid")
}
if start != "" && end != "" {
if !start.IsValid() || !end.IsValid() {
return nil, errors.New("if not empty, both semvers need to be valid")
}
if semver.Compare(string(start), string(end)) == 1 {
return nil, errors.New("start is greater to end")
}
}
if start == "" {
start, _ = NewSemver("v0.0.0")
}
if start.Equals(end) && (!left || !right) {
return nil, errors.New("if start == end, then both left and right must be true")
}
return &VersionRange{
Start: start, End: end, IncludeLeft: left, IncludeRight: right,
}, nil
}
// NewVersionRangeString creates a range given a range string (e.g. `>= v1.0`)
func NewVersionRangeString(stringRange string) (*VersionRange, error) {
if stringRange == "" {
return nil, errors.New("stringRange cannot be null")
}
regex := regexp.MustCompile(`^([<>]=?|==?)?\s*([0-9A-z.+\-]+)$`)
matches := regex.FindStringSubmatch(stringRange)
if len(matches) != 3 {
return nil, errors.New("the passed string does not contain a range")
}
operator := matches[1]
version, err := NewSemver(matches[2])
if err != nil {
return nil, err
}
if !slices.Contains(operators, operator) {
return nil, fmt.Errorf("the operator \"%s\" is not valid", operator)
}
var finalRange *VersionRange
switch operator {
case "=", "==":
finalRange, _ = NewVersionRange(version, version, true, true)
case ">=":
finalRange, _ = NewVersionRange(version, "", true, false)
case ">":
finalRange, _ = NewVersionRange(version, "", false, false)
case "<=":
v0, _ := NewSemver("0.0.0")
finalRange, _ = NewVersionRange(v0, version, true, true)
case "<":
v0, _ := NewSemver("0.0.0")
finalRange, _ = NewVersionRange(v0, version, true, false)
default:
finalRange, _ = NewVersionRange(version, version, true, true)
}
return finalRange, nil
}
// Equals checks if two [VersionRange] are equal (i.e. same start and end)
func (v *VersionRange) Equals(vr VersionRange) bool {
return v.Start.Equals(vr.Start) && v.End.Equals(vr.End) &&
v.IncludeLeft == vr.IncludeLeft && v.IncludeRight == vr.IncludeRight
}
// Contains checks if a [Semver] is contained in a [VersionRange] struct
func (v *VersionRange) Contains(s Semver) bool {
afterLeft := false
beforeRight := false
// Check if >= than start
if v.IncludeLeft && (s.Equals(v.Start) || s.After(v.Start)) {
afterLeft = true
}
// Check if > than start
if !v.IncludeLeft && !s.Equals(v.Start) && s.After(v.Start) {
afterLeft = true
}
// Open-ended range
if v.End == "" {
beforeRight = true
} else {
// Check if <= than end
if v.IncludeRight && (s.Equals(v.End) || s.Before(v.End)) {
beforeRight = true
}
// Check if < than end
if !v.IncludeRight && !s.Equals(v.End) && s.Before(v.End) {
beforeRight = true
}
}
return afterLeft && beforeRight
}