-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathsemver.go
173 lines (145 loc) · 4.33 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
// SPDX-FileCopyrightText: 2016-2024 caixw
//
// SPDX-License-Identifier: MIT
package version
import (
"strconv"
"strings"
"github.com/issue9/errwrap"
)
// SemVersion 是 [semver] 的定义
//
// [semver]: https://semver.org/lang/zh-CN/
type SemVersion struct {
Major int `version:"0,.1"`
Minor int `version:"1,.2"`
Patch int `version:"2,+4,-3"`
PreRelease string `version:"3,+4"`
Build string `version:"4"`
}
// Compare 比较两个版本号
//
// 若相同返回 0,若 v 比较大返回正整数,否则返回负数。
func (v *SemVersion) Compare(v2 *SemVersion) int {
switch {
case v.Major != v2.Major:
return v.Major - v2.Major
case v.Minor != v2.Minor:
return v.Minor - v2.Minor
case v.Patch != v2.Patch:
return v.Patch - v2.Patch
case len(v.PreRelease) == 0:
return len(v2.PreRelease) // v2.PreRelease 只要有内容,就比 v 的版本号小
case len(v2.PreRelease) == 0:
return -len(v.PreRelease) // v.PreRelease 只要有内容,就比 v2 的版本号小
}
// pre-release 的比较,按照 semver 的规则:
// 透过由左到右的每个被句点分隔的标识符号来比较,直到找到一个差异值后决定:
// 只有数字的标识符号以数值高低比较,有字母或连接号时则逐字以 ASCII 的排序来比较。
// 数字的标识符号比非数字的标识符号优先层级低。若开头的标识符号都相同时,
// 栏位比较多的先行版本号优先层级比较高。范例:1.0.0-alpha < 1.0.0-alpha.1
// < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0- rc.1 < 1.0.0。
vReleases := strings.Split(v.PreRelease, ".")
v2Releases := strings.Split(v2.PreRelease, ".")
l := len(vReleases)
if len(v2Releases) < l {
l = len(v2Releases)
}
for i := 0; i < l; i++ {
val := strings.Compare(vReleases[i], v2Releases[i])
if val == 0 { // 当前的所有字符相同,继续比较下个字符串
continue
}
// 尝试数值转换,若其中一个不能转换成数值,则直接返回 val
v1, err := strconv.Atoi(vReleases[i])
if err != nil {
return val
}
v2, err := strconv.Atoi(v2Releases[i])
if err != nil {
return val
}
// 到这里,v1 不可能等于 v2,只要判断两者的差值即可。
return v1 - v2
}
return 0
}
// CompareString 将当前对象与一个版本号字符串相比较。其返回值的功能与 Compare 相同
func (v *SemVersion) CompareString(ver string) (int, error) {
v2, err := SemVer(ver)
if err != nil {
return 0, err
}
return v.Compare(v2), nil
}
// Compatible 当前对象与 v2 是否兼容
//
// semver 规定主版本号相同的,在 API 层面必须兼容。
func (v *SemVersion) Compatible(v2 *SemVersion) bool {
return v.Major == v2.Major
}
// CompatibleString 当前对象与版本号字符串是否兼容
//
// semver 规定主版本号相同的,在 API 层面必须兼容。
func (v *SemVersion) CompatibleString(ver string) (bool, error) {
v2, err := SemVer(ver)
if err != nil {
return false, err
}
return v.Compatible(v2), nil
}
// String 转换成版本号字符串
func (v *SemVersion) String() string {
var buf errwrap.StringBuilder
buf.WString(strconv.Itoa(v.Major)).
WByte('.').
WString(strconv.Itoa(v.Minor)).
WByte('.').
WString(strconv.Itoa(v.Patch))
if len(v.PreRelease) > 0 {
buf.WByte('-').WString(v.PreRelease)
}
if len(v.Build) > 0 {
buf.WByte('+').WString(v.Build)
}
return buf.String()
}
// SemVer 将一个版本号字符串解析成 SemVersion 对象
func SemVer(ver string) (*SemVersion, error) {
semver := &SemVersion{}
if err := Parse(semver, ver); err != nil {
return nil, err
}
return semver, nil
}
// SemVerCompare 比较两个 semver 版本号字符串
func SemVerCompare(ver1, ver2 string) (int, error) {
v1, err := SemVer(ver1)
if err != nil {
return 0, err
}
return v1.CompareString(ver2)
}
// SemVerCompatible 两个 semver 版本号是否兼容
func SemVerCompatible(ver1, ver2 string) (bool, error) {
v1, err := SemVer(ver1)
if err != nil {
return false, err
}
return v1.CompatibleString(ver2)
}
// SemVerValid 验证 semver 版本号是否符合 semver 规范
func SemVerValid(ver string) bool {
v := &SemVersion{
Major: -1,
Minor: -1,
Patch: -1,
}
if err := Parse(v, ver); err != nil {
return false
}
if v.Major == -1 || v.Minor == -1 || v.Patch == -1 {
return false
}
return true
}