blob: 026e99f36216daa7129c86ffb2cb84b83dbf34d6 [file] [log] [blame]
David K. Bainbridge528b3182017-01-23 08:51:59 -08001// Copyright 2012, 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4// Package version implements version parsing.
5package version
6
7import (
8 "encoding/json"
9 "fmt"
10 "regexp"
11 "strconv"
12 "strings"
13
14 "gopkg.in/mgo.v2/bson"
15)
16
17// Number represents a version number.
18type Number struct {
19 Major int
20 Minor int
21 Tag string
22 Patch int
23 Build int
24}
25
26// Zero is occasionally convenient and readable.
27// Please don't change its value.
28var Zero = Number{}
29
30// Binary specifies a binary version of juju.v
31type Binary struct {
32 Number
33 Series string
34 Arch string
35}
36
37// String returns the string representation of the binary version.
38func (b Binary) String() string {
39 return fmt.Sprintf("%v-%s-%s", b.Number, b.Series, b.Arch)
40}
41
42// GetBSON implements bson.Getter.
43func (b Binary) GetBSON() (interface{}, error) {
44 return b.String(), nil
45}
46
47// SetBSON implements bson.Setter.
48func (b *Binary) SetBSON(raw bson.Raw) error {
49 var s string
50 err := raw.Unmarshal(&s)
51 if err != nil {
52 return err
53 }
54 v, err := ParseBinary(s)
55 if err != nil {
56 return err
57 }
58 *b = v
59 return nil
60}
61
62// MarshalJSON implements json.Marshaler.
63func (b Binary) MarshalJSON() ([]byte, error) {
64 return json.Marshal(b.String())
65}
66
67// UnmarshalJSON implements json.Unmarshaler.
68func (b *Binary) UnmarshalJSON(data []byte) error {
69 var s string
70 if err := json.Unmarshal(data, &s); err != nil {
71 return err
72 }
73 v, err := ParseBinary(s)
74 if err != nil {
75 return err
76 }
77 *b = v
78 return nil
79}
80
81// MarshalYAML implements yaml.v2.Marshaller interface.
82func (b Binary) MarshalYAML() (interface{}, error) {
83 return b.String(), nil
84}
85
86// UnmarshalYAML implements the yaml.Unmarshaller interface.
87func (b *Binary) UnmarshalYAML(unmarshal func(interface{}) error) error {
88 var vstr string
89 err := unmarshal(&vstr)
90 if err != nil {
91 return err
92 }
93 v, err := ParseBinary(vstr)
94 if err != nil {
95 return err
96 }
97 *b = v
98 return nil
99}
100
101var (
102 binaryPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$`)
103 numberPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?$`)
104)
105
106// MustParse parses a version and panics if it does
107// not parse correctly.
108func MustParse(s string) Number {
109 v, err := Parse(s)
110 if err != nil {
111 panic(err)
112 }
113 return v
114}
115
116// MustParseBinary parses a binary version and panics if it does
117// not parse correctly.
118func MustParseBinary(s string) Binary {
119 b, err := ParseBinary(s)
120 if err != nil {
121 panic(err)
122 }
123 return b
124}
125
126// ParseBinary parses a binary version of the form "1.2.3-series-arch".
127func ParseBinary(s string) (Binary, error) {
128 m := binaryPat.FindStringSubmatch(s)
129 if m == nil {
130 return Binary{}, fmt.Errorf("invalid binary version %q", s)
131 }
132 var b Binary
133 b.Major = atoi(m[1])
134 b.Minor = atoi(m[2])
135 b.Tag = m[3]
136 b.Patch = atoi(m[4])
137 if m[5] != "" {
138 b.Build = atoi(m[5][1:])
139 }
140 b.Series = m[6]
141 b.Arch = m[7]
142 return b, nil
143}
144
145// Parse parses the version, which is of the form 1.2.3
146// giving the major, minor and release versions
147// respectively.
148func Parse(s string) (Number, error) {
149 m := numberPat.FindStringSubmatch(s)
150 if m == nil {
151 return Number{}, fmt.Errorf("invalid version %q", s)
152 }
153 var n Number
154 n.Major = atoi(m[1])
155 n.Minor = atoi(m[2])
156 n.Tag = m[3]
157 n.Patch = atoi(m[4])
158 if m[5] != "" {
159 n.Build = atoi(m[5][1:])
160 }
161 return n, nil
162}
163
164// atoi is the same as strconv.Atoi but assumes that
165// the string has been verified to be a valid integer.
166func atoi(s string) int {
167 n, err := strconv.Atoi(s)
168 if err != nil {
169 panic(err)
170 }
171 return n
172}
173
174// String returns the string representation of this Number.
175func (n Number) String() string {
176 var s string
177 if n.Tag == "" {
178 s = fmt.Sprintf("%d.%d.%d", n.Major, n.Minor, n.Patch)
179 } else {
180 s = fmt.Sprintf("%d.%d-%s%d", n.Major, n.Minor, n.Tag, n.Patch)
181 }
182 if n.Build > 0 {
183 s += fmt.Sprintf(".%d", n.Build)
184 }
185 return s
186}
187
188// Compare returns -1, 0 or 1 depending on whether
189// n is less than, equal to or greater than other.
190// The comparison compares Major, then Minor, then Patch, then Build, using the first difference as
191func (n Number) Compare(other Number) int {
192 if n == other {
193 return 0
194 }
195 less := false
196 switch {
197 case n.Major != other.Major:
198 less = n.Major < other.Major
199 case n.Minor != other.Minor:
200 less = n.Minor < other.Minor
201 case n.Tag != other.Tag:
202 switch {
203 case n.Tag == "":
204 less = false
205 case other.Tag == "":
206 less = true
207 default:
208 less = n.Tag < other.Tag
209 }
210 case n.Patch != other.Patch:
211 less = n.Patch < other.Patch
212 case n.Build != other.Build:
213 less = n.Build < other.Build
214 }
215 if less {
216 return -1
217 }
218 return 1
219}
220
221// GetBSON implements bson.Getter.
222func (n Number) GetBSON() (interface{}, error) {
223 return n.String(), nil
224}
225
226// SetBSON implements bson.Setter.
227func (n *Number) SetBSON(raw bson.Raw) error {
228 var s string
229 err := raw.Unmarshal(&s)
230 if err != nil {
231 return err
232 }
233 v, err := Parse(s)
234 if err != nil {
235 return err
236 }
237 *n = v
238 return nil
239}
240
241// MarshalJSON implements json.Marshaler.
242func (n Number) MarshalJSON() ([]byte, error) {
243 return json.Marshal(n.String())
244}
245
246// UnmarshalJSON implements json.Unmarshaler.
247func (n *Number) UnmarshalJSON(data []byte) error {
248 var s string
249 if err := json.Unmarshal(data, &s); err != nil {
250 return err
251 }
252 v, err := Parse(s)
253 if err != nil {
254 return err
255 }
256 *n = v
257 return nil
258}
259
260// MarshalYAML implements yaml.v2.Marshaller interface
261func (n Number) MarshalYAML() (interface{}, error) {
262 return n.String(), nil
263}
264
265// UnmarshalYAML implements the yaml.Unmarshaller interface
266func (n *Number) UnmarshalYAML(unmarshal func(interface{}) error) error {
267 var vstr string
268 err := unmarshal(&vstr)
269 if err != nil {
270 return err
271 }
272 v, err := Parse(vstr)
273 if err != nil {
274 return err
275 }
276 *n = v
277 return nil
278}
279
280// ParseMajorMinor takes an argument of the form "major.minor" and returns ints major and minor.
281func ParseMajorMinor(vers string) (int, int, error) {
282 parts := strings.Split(vers, ".")
283 major, err := strconv.Atoi(parts[0])
284 minor := -1
285 if err != nil {
286 return -1, -1, fmt.Errorf("invalid major version number %s: %v", parts[0], err)
287 }
288 if len(parts) == 2 {
289 minor, err = strconv.Atoi(parts[1])
290 if err != nil {
291 return -1, -1, fmt.Errorf("invalid minor version number %s: %v", parts[1], err)
292 }
293 } else if len(parts) > 2 {
294 return -1, -1, fmt.Errorf("invalid major.minor version number %s", vers)
295 }
296 return major, minor, nil
297}