-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added package `update` and `semver` that can be used for selfupdate. Most of the code was copied from [go-selfupdate](https://github.com/creativeprojects/go-selfupdate), [go-update](https://github.com/inconshreveable/go-update) and [semver](https://github.com/Masterminds/semver). Most of the unneeded code has been removed, so there is no need to add new dependencies. In addition, users with the `mips` arch will not be able to selfupdate, as there is no way to get the float of the MIPS arch. Tested OS and arch: - [x] linux/amd64 - [x] linux/arm64 - [x] windows/amd64
- Loading branch information
1 parent
57b3aa6
commit 352e6b8
Showing
15 changed files
with
1,000 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Based on https://github.com/Masterminds/semver/blob/v3.2.1/version.go | ||
|
||
package semver | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
// 在 init() 中创建的正则表达式的编译版本被缓存在这里,这样 | ||
// 它只需要被创建一次。 | ||
var versionRegex *regexp.Regexp | ||
|
||
// semVerRegex 是用于解析语义化版本的正则表达式。 | ||
const semVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + | ||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + | ||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` | ||
|
||
// Version 表示单独的语义化版本。 | ||
type Version struct { | ||
major, minor, patch uint64 | ||
} | ||
|
||
func init() { | ||
versionRegex = regexp.MustCompile("^" + semVerRegex + "$") | ||
} | ||
|
||
// NewVersion 解析给定的版本并返回 Version 实例,如果 | ||
// 无法解析该版本则返回错误。如果版本是类似于 SemVer 的版本,则会 | ||
// 尝试将其转换为 SemVer。 | ||
func NewVersion(v string) (*Version, error) { | ||
m := versionRegex.FindStringSubmatch(v) | ||
if m == nil { | ||
return nil, fmt.Errorf("%s 不是有效的语义化版本", v) | ||
} | ||
|
||
sv := &Version{} | ||
|
||
var err error | ||
sv.major, err = strconv.ParseUint(m[1], 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("解析版本号时出错:%s", err) | ||
} | ||
|
||
if m[2] != "" { | ||
sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("解析版本号时出错:%s", err) | ||
} | ||
} else { | ||
sv.minor = 0 | ||
} | ||
|
||
if m[3] != "" { | ||
sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("解析版本号时出错:%s", err) | ||
} | ||
} else { | ||
sv.patch = 0 | ||
} | ||
|
||
return sv, nil | ||
} | ||
|
||
// String 将 Version 对象转换为字符串。 | ||
// 注意,如果原始版本包含前缀 v,则转换后的版本将不包含 v。 | ||
// 根据规范,语义版本不包含前缀 v,而在实现上则是可选的。 | ||
func (v Version) String() string { | ||
var buf bytes.Buffer | ||
|
||
fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) | ||
|
||
return buf.String() | ||
} | ||
|
||
// GreaterThan 测试一个版本是否大于另一个版本。 | ||
func (v *Version) GreaterThan(o *Version) bool { | ||
return v.compare(o) > 0 | ||
} | ||
|
||
// GreaterThanOrEqual 测试一个版本是否大于或等于另一个版本。 | ||
func (v *Version) GreaterThanOrEqual(o *Version) bool { | ||
return v.compare(o) >= 0 | ||
} | ||
|
||
// compare 比较当前版本与另一个版本。如果当前版本小于另一个版本则返回 -1;如果两个版本相等则返回 0;如果当前版本大于另一个版本,则返回 1。 | ||
// | ||
// 版本比较是基于 X.Y.Z 格式进行的。 | ||
func (v *Version) compare(o *Version) int { | ||
// 比较主版本号、次版本号和修订号。如果 | ||
// 发现差异则返回比较结果。 | ||
if d := compareSegment(v.major, o.major); d != 0 { | ||
return d | ||
} | ||
if d := compareSegment(v.minor, o.minor); d != 0 { | ||
return d | ||
} | ||
if d := compareSegment(v.patch, o.patch); d != 0 { | ||
return d | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
func compareSegment(v, o uint64) int { | ||
if v < o { | ||
return -1 | ||
} | ||
if v > o { | ||
return 1 | ||
} | ||
|
||
return 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
// Based on https://github.com/Masterminds/semver/blob/v3.2.1/version_test.go | ||
|
||
package semver | ||
|
||
import "testing" | ||
|
||
func TestNewVersion(t *testing.T) { | ||
tests := []struct { | ||
version string | ||
err bool | ||
}{ | ||
{"1.2.3", false}, | ||
{"1.2.3+test.01", false}, | ||
{"1.2.3-alpha.-1", false}, | ||
{"v1.2.3", false}, | ||
{"1.0", false}, | ||
{"v1.0", false}, | ||
{"1", false}, | ||
{"v1", false}, | ||
{"1.2.beta", true}, | ||
{"v1.2.beta", true}, | ||
{"foo", true}, | ||
{"1.2-5", false}, | ||
{"v1.2-5", false}, | ||
{"1.2-beta.5", false}, | ||
{"v1.2-beta.5", false}, | ||
{"\n1.2", true}, | ||
{"\nv1.2", true}, | ||
{"1.2.0-x.Y.0+metadata", false}, | ||
{"v1.2.0-x.Y.0+metadata", false}, | ||
{"1.2.0-x.Y.0+metadata-width-hyphen", false}, | ||
{"v1.2.0-x.Y.0+metadata-width-hyphen", false}, | ||
{"1.2.3-rc1-with-hyphen", false}, | ||
{"v1.2.3-rc1-with-hyphen", false}, | ||
{"1.2.3.4", true}, | ||
{"v1.2.3.4", true}, | ||
{"1.2.2147483648", false}, | ||
{"1.2147483648.3", false}, | ||
{"2147483648.3.0", false}, | ||
|
||
// Due to having 4 parts these should produce an error. See | ||
// https://github.com/Masterminds/semver/issues/185 for the reason for | ||
// these tests. | ||
{"12.3.4.1234", true}, | ||
{"12.23.4.1234", true}, | ||
{"12.3.34.1234", true}, | ||
|
||
// The SemVer spec in a pre-release expects to allow [0-9A-Za-z-]. | ||
{"20221209-update-renovatejson-v4", false}, | ||
} | ||
|
||
for _, tc := range tests { | ||
_, err := NewVersion(tc.version) | ||
if tc.err && err == nil { | ||
t.Fatalf("expected error for version: %s", tc.version) | ||
} else if !tc.err && err != nil { | ||
t.Fatalf("error for version %s: %s", tc.version, err) | ||
} | ||
} | ||
} | ||
|
||
func TestParts(t *testing.T) { | ||
v, err := NewVersion("1.2.3") | ||
if err != nil { | ||
t.Error("Error parsing version 1.2.3") | ||
} | ||
|
||
if v.major != 1 { | ||
t.Error("major returning wrong value") | ||
} | ||
if v.minor != 2 { | ||
t.Error("minor returning wrong value") | ||
} | ||
if v.patch != 3 { | ||
t.Error("patch returning wrong value") | ||
} | ||
} | ||
|
||
func TestCoerceString(t *testing.T) { | ||
tests := []struct { | ||
version string | ||
expected string | ||
}{ | ||
{"1.2.3", "1.2.3"}, | ||
{"v1.2.3", "1.2.3"}, | ||
{"1.0", "1.0.0"}, | ||
{"v1.0", "1.0.0"}, | ||
{"1", "1.0.0"}, | ||
{"v1", "1.0.0"}, | ||
} | ||
|
||
for _, tc := range tests { | ||
v, err := NewVersion(tc.version) | ||
if err != nil { | ||
t.Errorf("Error parsing version %s", tc) | ||
} | ||
|
||
s := v.String() | ||
if s != tc.expected { | ||
t.Errorf("Error generating string. Expected '%s' but got '%s'", tc.expected, s) | ||
} | ||
} | ||
} | ||
|
||
func TestCompare(t *testing.T) { | ||
tests := []struct { | ||
v1 string | ||
v2 string | ||
expected int | ||
}{ | ||
{"1.2.3", "1.5.1", -1}, | ||
{"2.2.3", "1.5.1", 1}, | ||
{"2.2.3", "2.2.2", 1}, | ||
} | ||
|
||
for _, tc := range tests { | ||
v1, err := NewVersion(tc.v1) | ||
if err != nil { | ||
t.Errorf("Error parsing version: %s", err) | ||
} | ||
|
||
v2, err := NewVersion(tc.v2) | ||
if err != nil { | ||
t.Errorf("Error parsing version: %s", err) | ||
} | ||
|
||
a := v1.compare(v2) | ||
e := tc.expected | ||
if a != e { | ||
t.Errorf( | ||
"Comparison of '%s' and '%s' failed. Expected '%d', got '%d'", | ||
tc.v1, tc.v2, e, a, | ||
) | ||
} | ||
} | ||
} | ||
|
||
func TestGreaterThan(t *testing.T) { | ||
tests := []struct { | ||
v1 string | ||
v2 string | ||
expected bool | ||
}{ | ||
{"1.2.3", "1.5.1", false}, | ||
{"2.2.3", "1.5.1", true}, | ||
{"3.2-beta", "3.2-beta", false}, | ||
{"3.2.0-beta.1", "3.2.0-beta.5", false}, | ||
{"7.43.0-SNAPSHOT.99", "7.43.0-SNAPSHOT.103", false}, | ||
{"7.43.0-SNAPSHOT.99", "7.43.0-SNAPSHOT.BAR", false}, | ||
} | ||
|
||
for _, tc := range tests { | ||
v1, err := NewVersion(tc.v1) | ||
if err != nil { | ||
t.Errorf("Error parsing version: %s", err) | ||
} | ||
|
||
v2, err := NewVersion(tc.v2) | ||
if err != nil { | ||
t.Errorf("Error parsing version: %s", err) | ||
} | ||
|
||
a := v1.GreaterThan(v2) | ||
e := tc.expected | ||
if a != e { | ||
t.Errorf( | ||
"Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", | ||
tc.v1, tc.v2, e, a, | ||
) | ||
} | ||
} | ||
} | ||
|
||
func TestGreaterThanOrEqual(t *testing.T) { | ||
tests := []struct { | ||
v1 string | ||
v2 string | ||
expected bool | ||
}{ | ||
{"1.2.3", "1.5.1", false}, | ||
{"2.2.3", "1.5.1", true}, | ||
{"3.2-beta", "3.2-beta", true}, | ||
{"3.2-beta.4", "3.2-beta.2", true}, | ||
{"7.43.0-SNAPSHOT.FOO", "7.43.0-SNAPSHOT.103", true}, | ||
} | ||
|
||
for _, tc := range tests { | ||
v1, err := NewVersion(tc.v1) | ||
if err != nil { | ||
t.Errorf("Error parsing version: %s", err) | ||
} | ||
|
||
v2, err := NewVersion(tc.v2) | ||
if err != nil { | ||
t.Errorf("Error parsing version: %s", err) | ||
} | ||
|
||
a := v1.GreaterThanOrEqual(v2) | ||
e := tc.expected | ||
if a != e { | ||
t.Errorf( | ||
"Comparison of '%s' and '%s' failed. Expected '%t', got '%t'", | ||
tc.v1, tc.v2, e, a, | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.