1package version
2
3import (
4	"errors"
5	"fmt"
6	"regexp"
7)
8
9var (
10	versionRegexp = matchingRegexp{regexp.MustCompile(`\A(?P<release>[0-9A-Za-z_\.]+)(\-(?P<pre_release>[0-9A-Za-z_\-\.]+))?(\+(?P<post_release>[0-9A-Za-z_\-\.]+))?\z`)}
11)
12
13type matchingRegexp struct {
14	*regexp.Regexp
15}
16
17type Version struct {
18	Release, PreRelease, PostRelease VersionSegment
19
20	Segments []VersionSegment
21}
22
23func MustNewVersionFromString(v string) Version {
24	ver, err := NewVersionFromString(v)
25	if err != nil {
26		panic(fmt.Sprintf("Invalid version '%s': %s", v, err))
27	}
28
29	return ver
30}
31
32func NewVersionFromString(v string) (Version, error) {
33	var err error
34
35	if len(v) == 0 {
36		return Version{}, errors.New("Expected version to be non-empty string")
37	}
38
39	captures := versionRegexp.FindStringSubmatchMap(v)
40	if len(captures) == 0 {
41		errMsg := fmt.Sprintf("Expected version '%s' to match version format", v)
42		return Version{}, errors.New(errMsg)
43	}
44
45	release := VersionSegment{}
46	preRelease := VersionSegment{}
47	postRelease := VersionSegment{}
48
49	if releaseStr, ok := captures["release"]; ok {
50		release, err = NewVersionSegmentFromString(releaseStr)
51		if err != nil {
52			return Version{}, err
53		}
54	}
55
56	if preReleaseStr, ok := captures["pre_release"]; ok {
57		preRelease, err = NewVersionSegmentFromString(preReleaseStr)
58		if err != nil {
59			return Version{}, err
60		}
61	}
62
63	if postReleaseStr, ok := captures["post_release"]; ok {
64		postRelease, err = NewVersionSegmentFromString(postReleaseStr)
65		if err != nil {
66			return Version{}, err
67		}
68	}
69
70	return NewVersion(release, preRelease, postRelease)
71}
72
73func NewVersion(release, preRelease, postRelease VersionSegment) (Version, error) {
74	if release.Empty() {
75		return Version{}, errors.New("Expected to non-empty release segment for constructing version")
76	}
77
78	version := Version{
79		Release:     release,
80		PreRelease:  preRelease,
81		PostRelease: postRelease,
82		Segments:    []VersionSegment{release, preRelease, postRelease},
83	}
84
85	return version, nil
86}
87
88func (v Version) IncrementRelease() (Version, error) {
89	incRelease, err := v.Release.Increment()
90	if err != nil {
91		return Version{}, err
92	}
93
94	return NewVersion(incRelease, VersionSegment{}, VersionSegment{})
95}
96
97func (v Version) IncrementPostRelease(defaultPostRelease VersionSegment) (Version, error) {
98	var newPostRelease VersionSegment
99	var err error
100
101	if defaultPostRelease.Empty() {
102		return Version{}, errors.New("Expected default post relase to be non-empty")
103	}
104
105	if v.PostRelease.Empty() {
106		newPostRelease = defaultPostRelease.Copy()
107	} else {
108		newPostRelease, err = v.PostRelease.Increment()
109		if err != nil {
110			return Version{}, err
111		}
112	}
113
114	return NewVersion(v.Release.Copy(), v.PreRelease.Copy(), newPostRelease)
115}
116
117func (v Version) Empty() bool { return len(v.Segments) == 0 }
118
119func (v Version) String() string { return v.AsString() }
120
121func (v Version) AsString() string {
122	result := v.Release.AsString()
123
124	if !v.PreRelease.Empty() {
125		result += "-" + v.PreRelease.AsString()
126	}
127
128	if !v.PostRelease.Empty() {
129		result += "+" + v.PostRelease.AsString()
130	}
131
132	return result
133}
134
135func (v Version) Compare(other Version) int {
136	result := v.Release.Compare(other.Release)
137	if result != 0 {
138		return result
139	}
140
141	if !v.PreRelease.Empty() || !other.PreRelease.Empty() {
142		if v.PreRelease.Empty() {
143			return 1
144		}
145		if other.PreRelease.Empty() {
146			return -1
147		}
148		result = v.PreRelease.Compare(other.PreRelease)
149		if result != 0 {
150			return result
151		}
152	}
153
154	if !v.PostRelease.Empty() || !other.PostRelease.Empty() {
155		if v.PostRelease.Empty() {
156			return -1
157		}
158		if other.PostRelease.Empty() {
159			return 1
160		}
161		result = v.PostRelease.Compare(other.PostRelease)
162		if result != 0 {
163			return result
164		}
165	}
166
167	return 0
168}
169
170func (v Version) IsEq(other Version) bool { return v.Compare(other) == 0 }
171func (v Version) IsGt(other Version) bool { return v.Compare(other) == 1 }
172func (v Version) IsLt(other Version) bool { return v.Compare(other) == -1 }
173
174func (r *matchingRegexp) FindStringSubmatchMap(s string) map[string]string {
175	captures := map[string]string{}
176
177	match := r.FindStringSubmatch(s)
178	if match == nil {
179		return captures
180	}
181
182	for i, name := range r.SubexpNames() {
183		// 0 is a whole regex
184		if i == 0 || name == "" || match[i] == "" {
185			continue
186		}
187
188		captures[name] = match[i]
189	}
190
191	return captures
192}
193