This repository was archived by the owner on Sep 19, 2024. It is now read-only.
forked from olliebun/go-semver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsemver.go
184 lines (167 loc) · 4.42 KB
/
semver.go
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
// package semver provides a type representing a semantic version, and
// facilities for parsing, serialisation and comparison.
//
// See http://semver.org for more information on semantic versioning.
//
// This package expands on the specification: a partial version string like
// "v2" or "v2.0" is considered valid, and expanded to "v2.0.0".
//
// To parse a version string:
//
// s := "v1.0.7-alpha"
// v, err := semver.Parse(s)
// if err != nil {
// panic(err)
// }
// fmt.Println(v)
//
// Visit godoc.org/github.com/DanielBryan/semver for the full package API.
package semver
import (
"bytes"
"errors"
"fmt"
"sort"
"strconv"
)
// A Version is a parsed semver version string.
//
// If only a partial version was specified, the missing parts will be -1.
type Version struct {
Major int
Minor int
Patch int
Prerelease string
}
// Returns the standard string representation of the Version value.
//
// For example, given this Version value:
//
// Version{Major: 1, Minor: 2, Patch: 3, Prerelease: "beta1"}
//
// The following string is produced:
//
// v1.2.3-beta1
func (v Version) String() string {
if len(v.Prerelease) > 0 {
return fmt.Sprintf("v%d.%d.%d-%s", v.Major, v.Minor, v.Patch, v.Prerelease)
} else {
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
}
}
// Returns true if v is a higher version than o.
func (v Version) GreaterThan(o Version) bool {
if v.Major != o.Major {
return v.Major > o.Major
} else if v.Minor != o.Minor {
return v.Minor > o.Minor
} else if v.Patch != o.Patch {
return v.Patch > o.Patch
} else if v.Prerelease == o.Prerelease {
return false
}
sl := []string{v.Prerelease, o.Prerelease}
sort.Strings(sl)
if sl[0] == v.Prerelease {
return false
}
return true
}
// Returns true if v is a lesser version than o.
func (v Version) LessThan(o Version) bool {
return !v.Equals(o) && !v.GreaterThan(o)
}
// Returns true if v and o are the same version.
func (v Version) Equals(o Version) bool {
return v.Major == o.Major && v.Minor == o.Minor && v.Patch == o.Patch && v.Prerelease == o.Prerelease
}
// States for parsing state machine.
// The states are ordered - parsing can only advance forwards.
// We can only advance forwards.
type parseState int
const (
atStart parseState = iota
foundV
foundMajor
foundMinor
foundPatch
foundPrerelease
)
var (
EmptyVersion = errors.New("Empty version string")
IllegalVersion = errors.New("Illegal version string")
)
// Parse a version value from its string representation.
//
// The error value will either be nil, EmptyVersion or IllegalVersion.
func Parse(s string) (Version, error) {
var (
v Version // Version object we'll return
state parseState = atStart // current parsing state
pos int // pointer into the string
buf = bytes.NewBuffer(nil) // container for temporary state while we loop
err error
)
if len(s) == 0 {
return v, EmptyVersion
}
// Loop until we find an error or we've finished parsing the string
for pos < len(s) {
switch state {
case atStart:
if s[pos] == 'v' {
pos = pos + 1
state = foundV
} else {
return v, IllegalVersion
}
case foundV:
var maj int
if maj, pos, err = readNextNum(s, pos, buf); err != nil {
return v, err
}
v.Major = maj
state = foundMajor
case foundMajor:
var minor int
if minor, pos, err = readNextNum(s, pos, buf); err != nil {
return v, err
}
v.Minor = minor
state = foundMinor
case foundMinor:
var patch int
if patch, pos, err = readNextNum(s, pos, buf); err != nil {
return v, err
}
v.Patch = patch
state = foundPatch
case foundPatch:
v.Prerelease = s[pos:]
pos = len(s)
state = foundPrerelease
}
}
if state < foundMajor {
// At minimum we need a major version
return v, IllegalVersion
}
return v, nil
}
// Read the next version number from this cursor in the string.
// buf should be an empty bytes.Buffer. The buffer will be automatically reset.
//
// Reads until a period, hyphen or the end of the string.
//
// Returns the version number, the new cursor point and any error.
func readNextNum(s string, curs int, buf *bytes.Buffer) (int, int, error) {
defer buf.Reset()
for ; curs < len(s) && s[curs] != '.' && s[curs] != '-'; curs += 1 {
buf.WriteByte(s[curs])
}
i, err := strconv.Atoi(buf.String())
if err != nil {
return -1, curs, IllegalVersion
}
return i, curs + 1, nil
}