1 package semver
2
3 import (
4 "bytes"
5 "database/sql/driver"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "regexp"
10 "strconv"
11 "strings"
12 )
13
14
15
16 var versionRegex *regexp.Regexp
17
18 var (
19
20
21 ErrInvalidSemVer = errors.New("Invalid Semantic Version")
22
23
24 ErrEmptyString = errors.New("Version string empty")
25
26
27
28 ErrInvalidCharacters = errors.New("Invalid characters in version")
29
30
31
32 ErrSegmentStartsZero = errors.New("Version segment starts with 0")
33
34
35 ErrInvalidMetadata = errors.New("Invalid Metadata string")
36
37
38 ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
39 )
40
41
42 const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
43 `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
44 `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
45
46
47 type Version struct {
48 major, minor, patch uint64
49 pre string
50 metadata string
51 original string
52 }
53
54 func init() {
55 versionRegex = regexp.MustCompile("^" + semVerRegex + "$")
56 }
57
58 const (
59 num string = "0123456789"
60 allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num
61 )
62
63
64
65
66
67
68 func StrictNewVersion(v string) (*Version, error) {
69
70
71
72 if len(v) == 0 {
73 return nil, ErrEmptyString
74 }
75
76
77 parts := strings.SplitN(v, ".", 3)
78 if len(parts) != 3 {
79 return nil, ErrInvalidSemVer
80 }
81
82 sv := &Version{
83 original: v,
84 }
85
86
87 var extra []string
88 if strings.ContainsAny(parts[2], "-+") {
89
90 extra = strings.SplitN(parts[2], "+", 2)
91 if len(extra) > 1 {
92
93 sv.metadata = extra[1]
94 parts[2] = extra[0]
95 }
96
97 extra = strings.SplitN(parts[2], "-", 2)
98 if len(extra) > 1 {
99
100 sv.pre = extra[1]
101 parts[2] = extra[0]
102 }
103 }
104
105
106
107 for _, p := range parts {
108 if !containsOnly(p, num) {
109 return nil, ErrInvalidCharacters
110 }
111
112 if len(p) > 1 && p[0] == '0' {
113 return nil, ErrSegmentStartsZero
114 }
115 }
116
117
118 var err error
119 sv.major, err = strconv.ParseUint(parts[0], 10, 64)
120 if err != nil {
121 return nil, err
122 }
123
124 sv.minor, err = strconv.ParseUint(parts[1], 10, 64)
125 if err != nil {
126 return nil, err
127 }
128
129 sv.patch, err = strconv.ParseUint(parts[2], 10, 64)
130 if err != nil {
131 return nil, err
132 }
133
134
135 if sv.pre == "" && sv.metadata == "" {
136 return sv, nil
137 }
138
139 if sv.pre != "" {
140 if err = validatePrerelease(sv.pre); err != nil {
141 return nil, err
142 }
143 }
144
145 if sv.metadata != "" {
146 if err = validateMetadata(sv.metadata); err != nil {
147 return nil, err
148 }
149 }
150
151 return sv, nil
152 }
153
154
155
156
157
158 func NewVersion(v string) (*Version, error) {
159 m := versionRegex.FindStringSubmatch(v)
160 if m == nil {
161 return nil, ErrInvalidSemVer
162 }
163
164 sv := &Version{
165 metadata: m[8],
166 pre: m[5],
167 original: v,
168 }
169
170 var err error
171 sv.major, err = strconv.ParseUint(m[1], 10, 64)
172 if err != nil {
173 return nil, fmt.Errorf("Error parsing version segment: %s", err)
174 }
175
176 if m[2] != "" {
177 sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)
178 if err != nil {
179 return nil, fmt.Errorf("Error parsing version segment: %s", err)
180 }
181 } else {
182 sv.minor = 0
183 }
184
185 if m[3] != "" {
186 sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)
187 if err != nil {
188 return nil, fmt.Errorf("Error parsing version segment: %s", err)
189 }
190 } else {
191 sv.patch = 0
192 }
193
194
195
196
197 if sv.pre != "" {
198 if err = validatePrerelease(sv.pre); err != nil {
199 return nil, err
200 }
201 }
202
203 if sv.metadata != "" {
204 if err = validateMetadata(sv.metadata); err != nil {
205 return nil, err
206 }
207 }
208
209 return sv, nil
210 }
211
212
213
214 func New(major, minor, patch uint64, pre, metadata string) *Version {
215 v := Version{
216 major: major,
217 minor: minor,
218 patch: patch,
219 pre: pre,
220 metadata: metadata,
221 original: "",
222 }
223
224 v.original = v.String()
225
226 return &v
227 }
228
229
230 func MustParse(v string) *Version {
231 sv, err := NewVersion(v)
232 if err != nil {
233 panic(err)
234 }
235 return sv
236 }
237
238
239
240
241
242
243 func (v Version) String() string {
244 var buf bytes.Buffer
245
246 fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
247 if v.pre != "" {
248 fmt.Fprintf(&buf, "-%s", v.pre)
249 }
250 if v.metadata != "" {
251 fmt.Fprintf(&buf, "+%s", v.metadata)
252 }
253
254 return buf.String()
255 }
256
257
258 func (v *Version) Original() string {
259 return v.original
260 }
261
262
263 func (v Version) Major() uint64 {
264 return v.major
265 }
266
267
268 func (v Version) Minor() uint64 {
269 return v.minor
270 }
271
272
273 func (v Version) Patch() uint64 {
274 return v.patch
275 }
276
277
278 func (v Version) Prerelease() string {
279 return v.pre
280 }
281
282
283 func (v Version) Metadata() string {
284 return v.metadata
285 }
286
287
288 func (v Version) originalVPrefix() string {
289
290 if v.original != "" && v.original[:1] == "v" {
291 return v.original[:1]
292 }
293 return ""
294 }
295
296
297
298
299
300
301 func (v Version) IncPatch() Version {
302 vNext := v
303
304
305
306
307 if v.pre != "" {
308 vNext.metadata = ""
309 vNext.pre = ""
310 } else {
311 vNext.metadata = ""
312 vNext.pre = ""
313 vNext.patch = v.patch + 1
314 }
315 vNext.original = v.originalVPrefix() + "" + vNext.String()
316 return vNext
317 }
318
319
320
321
322
323
324 func (v Version) IncMinor() Version {
325 vNext := v
326 vNext.metadata = ""
327 vNext.pre = ""
328 vNext.patch = 0
329 vNext.minor = v.minor + 1
330 vNext.original = v.originalVPrefix() + "" + vNext.String()
331 return vNext
332 }
333
334
335
336
337
338
339
340 func (v Version) IncMajor() Version {
341 vNext := v
342 vNext.metadata = ""
343 vNext.pre = ""
344 vNext.patch = 0
345 vNext.minor = 0
346 vNext.major = v.major + 1
347 vNext.original = v.originalVPrefix() + "" + vNext.String()
348 return vNext
349 }
350
351
352
353 func (v Version) SetPrerelease(prerelease string) (Version, error) {
354 vNext := v
355 if len(prerelease) > 0 {
356 if err := validatePrerelease(prerelease); err != nil {
357 return vNext, err
358 }
359 }
360 vNext.pre = prerelease
361 vNext.original = v.originalVPrefix() + "" + vNext.String()
362 return vNext, nil
363 }
364
365
366
367 func (v Version) SetMetadata(metadata string) (Version, error) {
368 vNext := v
369 if len(metadata) > 0 {
370 if err := validateMetadata(metadata); err != nil {
371 return vNext, err
372 }
373 }
374 vNext.metadata = metadata
375 vNext.original = v.originalVPrefix() + "" + vNext.String()
376 return vNext, nil
377 }
378
379
380 func (v *Version) LessThan(o *Version) bool {
381 return v.Compare(o) < 0
382 }
383
384
385 func (v *Version) GreaterThan(o *Version) bool {
386 return v.Compare(o) > 0
387 }
388
389
390
391
392 func (v *Version) Equal(o *Version) bool {
393 return v.Compare(o) == 0
394 }
395
396
397
398
399
400
401
402
403 func (v *Version) Compare(o *Version) int {
404
405
406 if d := compareSegment(v.Major(), o.Major()); d != 0 {
407 return d
408 }
409 if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
410 return d
411 }
412 if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
413 return d
414 }
415
416
417 ps := v.pre
418 po := o.Prerelease()
419
420 if ps == "" && po == "" {
421 return 0
422 }
423 if ps == "" {
424 return 1
425 }
426 if po == "" {
427 return -1
428 }
429
430 return comparePrerelease(ps, po)
431 }
432
433
434 func (v *Version) UnmarshalJSON(b []byte) error {
435 var s string
436 if err := json.Unmarshal(b, &s); err != nil {
437 return err
438 }
439 temp, err := NewVersion(s)
440 if err != nil {
441 return err
442 }
443 v.major = temp.major
444 v.minor = temp.minor
445 v.patch = temp.patch
446 v.pre = temp.pre
447 v.metadata = temp.metadata
448 v.original = temp.original
449 return nil
450 }
451
452
453 func (v Version) MarshalJSON() ([]byte, error) {
454 return json.Marshal(v.String())
455 }
456
457
458 func (v *Version) UnmarshalText(text []byte) error {
459 temp, err := NewVersion(string(text))
460 if err != nil {
461 return err
462 }
463
464 *v = *temp
465
466 return nil
467 }
468
469
470 func (v Version) MarshalText() ([]byte, error) {
471 return []byte(v.String()), nil
472 }
473
474
475 func (v *Version) Scan(value interface{}) error {
476 var s string
477 s, _ = value.(string)
478 temp, err := NewVersion(s)
479 if err != nil {
480 return err
481 }
482 v.major = temp.major
483 v.minor = temp.minor
484 v.patch = temp.patch
485 v.pre = temp.pre
486 v.metadata = temp.metadata
487 v.original = temp.original
488 return nil
489 }
490
491
492 func (v Version) Value() (driver.Value, error) {
493 return v.String(), nil
494 }
495
496 func compareSegment(v, o uint64) int {
497 if v < o {
498 return -1
499 }
500 if v > o {
501 return 1
502 }
503
504 return 0
505 }
506
507 func comparePrerelease(v, o string) int {
508
509
510 sparts := strings.Split(v, ".")
511 oparts := strings.Split(o, ".")
512
513
514
515 slen := len(sparts)
516 olen := len(oparts)
517
518 l := slen
519 if olen > slen {
520 l = olen
521 }
522
523
524 for i := 0; i < l; i++ {
525
526
527 stemp := ""
528 if i < slen {
529 stemp = sparts[i]
530 }
531
532 otemp := ""
533 if i < olen {
534 otemp = oparts[i]
535 }
536
537 d := comparePrePart(stemp, otemp)
538 if d != 0 {
539 return d
540 }
541 }
542
543
544
545
546 return 0
547 }
548
549 func comparePrePart(s, o string) int {
550
551 if s == o {
552 return 0
553 }
554
555
556
557 if s == "" {
558 if o != "" {
559 return -1
560 }
561 return 1
562 }
563
564 if o == "" {
565 if s != "" {
566 return 1
567 }
568 return -1
569 }
570
571
572
573
574
575
576
577
578 oi, n1 := strconv.ParseUint(o, 10, 64)
579 si, n2 := strconv.ParseUint(s, 10, 64)
580
581
582 if n1 != nil && n2 != nil {
583 if s > o {
584 return 1
585 }
586 return -1
587 } else if n1 != nil {
588
589 return -1
590 } else if n2 != nil {
591
592 return 1
593 }
594
595 if si > oi {
596 return 1
597 }
598 return -1
599 }
600
601
602 func containsOnly(s string, comp string) bool {
603 return strings.IndexFunc(s, func(r rune) bool {
604 return !strings.ContainsRune(comp, r)
605 }) == -1
606 }
607
608
609
610
611
612 func validatePrerelease(p string) error {
613 eparts := strings.Split(p, ".")
614 for _, p := range eparts {
615 if containsOnly(p, num) {
616 if len(p) > 1 && p[0] == '0' {
617 return ErrSegmentStartsZero
618 }
619 } else if !containsOnly(p, allowed) {
620 return ErrInvalidPrerelease
621 }
622 }
623
624 return nil
625 }
626
627
628
629
630
631 func validateMetadata(m string) error {
632 eparts := strings.Split(m, ".")
633 for _, p := range eparts {
634 if !containsOnly(p, allowed) {
635 return ErrInvalidMetadata
636 }
637 }
638 return nil
639 }
640
View as plain text