mirrored from https://chromium.googlesource.com/infra/luci/luci-go
/
version.go
156 lines (142 loc) · 4.36 KB
/
version.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
// Copyright 2017 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package python
import (
"fmt"
"regexp"
"strconv"
"strings"
"go.chromium.org/luci/common/errors"
)
// canonicalVersionRE is a regular expression that can match canonical Python
// versions.
//
// This has been modified from the PEP440 canonical regular expression to
// exclude parts outside of the (major.minor.patch...) section.
var canonicalVersionRE = regexp.MustCompile(
`^([1-9]\d*!)?` +
`((0|[1-9]\d*)(\.(0|[1-9]\d*))*)` +
`(((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*))?)` +
`(\+.*)?$`)
// Version is a Python interpreter version.
//
// It is a simplified version of the Python interpreter version scheme defined
// in PEP 440: https://www.python.org/dev/peps/pep-0440/
//
// Notably, it extracts the major, minor, and patch values out of the version.
type Version struct {
Major int
Minor int
Patch int
}
// ParseVersion parses a Python version from a version string (e.g., "1.2.3").
func ParseVersion(s string) (Version, error) {
var v Version
if s == "" {
return v, nil
}
match := canonicalVersionRE.FindStringSubmatch(s)
if match == nil {
return v, errors.Reason("non-canonical Python version string: %q", s).Err()
}
parts := strings.Split(match[2], ".")
// Values are expected to parse, and will panic otherwise. This is safe
// because the value has already been determined to be canonical above.
mustParseVersion := func(value string) int {
version, err := strconv.Atoi(value)
if err != nil {
panic(fmt.Sprintf("invalid number value %q: %s", value, err))
}
return version
}
// Regexp match guarantees that "parts" will have at least one component, and
// that all components are well-formed numbers.
if len(parts) >= 3 {
v.Patch = mustParseVersion(parts[2])
}
if len(parts) >= 2 {
v.Minor = mustParseVersion(parts[1])
}
v.Major = mustParseVersion(parts[0])
if v.IsZero() {
return v, errors.Reason("version is incomplete").Err()
}
return v, nil
}
func (v Version) String() string {
if v.IsZero() {
return ""
}
return fmt.Sprintf("%d.%d.%d", v.Major, v.Minor, v.Patch)
}
// IsZero returns true if the Version is empty. This is true if the Major field,
// which must be set, is empty.
func (v *Version) IsZero() bool { return v.Major <= 0 }
// PythonBase returns the base Python interpreter name for this version.
func (v *Version) PythonBase() string {
switch {
case v.IsZero():
return "python"
case v.Minor > 0:
return fmt.Sprintf("python%d.%d", v.Major, v.Minor)
default:
return fmt.Sprintf("python%d", v.Major)
}
}
// IsSatisfiedBy returns true if "other" is a suitable match for this version. A
// suitable match:
//
// - MUST have a Major version.
// - If v is zero, other is automatically suitable.
// - If v is non-zero, other must have the same Major version as v, and a
// minor/patch version that is >= v's.
func (v *Version) IsSatisfiedBy(other Version) bool {
switch {
case other.Major <= 0:
// "other" must have a Major version.
return false
case v.IsZero():
// "v" is zero (anything), so "other" satisfies it.
return true
case v.Major != other.Major:
// "other" must match "v"'s Major version precisely.
return false
case v.Minor > other.Minor:
// "v" requires a Minor version that is greater than "other"'s.
return false
case v.Minor < other.Minor:
// "v" requires a Minor version that is less than "other"'s.
return true
case v.Patch > other.Patch:
// "v" requires a Patch version that is greater than "other"'s.
return false
default:
return true
}
}
// Less returns true if "v"'s Version semantically precedes "other".
func (v *Version) Less(other *Version) bool {
switch {
case v.Major < other.Major:
return true
case v.Major > other.Major:
return false
case v.Minor < other.Minor:
return true
case v.Minor > other.Minor:
return false
default:
return (v.Patch < other.Patch)
}
}