podman
639 строк · 15.7 Кб
1package semver2
3import (4"bytes"5"database/sql/driver"6"encoding/json"7"errors"8"fmt"9"regexp"10"strconv"11"strings"12)
13
14// The compiled version of the regex created at init() is cached here so it
15// only needs to be created once.
16var versionRegex *regexp.Regexp17
18var (19// ErrInvalidSemVer is returned a version is found to be invalid when20// being parsed.21ErrInvalidSemVer = errors.New("Invalid Semantic Version")22
23// ErrEmptyString is returned when an empty string is passed in for parsing.24ErrEmptyString = errors.New("Version string empty")25
26// ErrInvalidCharacters is returned when invalid characters are found as27// part of a version28ErrInvalidCharacters = errors.New("Invalid characters in version")29
30// ErrSegmentStartsZero is returned when a version segment starts with 0.31// This is invalid in SemVer.32ErrSegmentStartsZero = errors.New("Version segment starts with 0")33
34// ErrInvalidMetadata is returned when the metadata is an invalid format35ErrInvalidMetadata = errors.New("Invalid Metadata string")36
37// ErrInvalidPrerelease is returned when the pre-release is an invalid format38ErrInvalidPrerelease = errors.New("Invalid Prerelease string")39)
40
41// semVerRegex is the regular expression used to parse a semantic version.
42const 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// Version represents a single semantic version.
47type Version struct {48major, minor, patch uint6449pre string50metadata string51original string52}
53
54func init() {55versionRegex = regexp.MustCompile("^" + semVerRegex + "$")56}
57
58const (59num string = "0123456789"60allowed string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-" + num61)
62
63// StrictNewVersion parses a given version and returns an instance of Version or
64// an error if unable to parse the version. Only parses valid semantic versions.
65// Performs checking that can find errors within the version.
66// If you want to coerce a version such as 1 or 1.2 and parse it as the 1.x
67// releases of semver did, use the NewVersion() function.
68func StrictNewVersion(v string) (*Version, error) {69// Parsing here does not use RegEx in order to increase performance and reduce70// allocations.71
72if len(v) == 0 {73return nil, ErrEmptyString74}75
76// Split the parts into [0]major, [1]minor, and [2]patch,prerelease,build77parts := strings.SplitN(v, ".", 3)78if len(parts) != 3 {79return nil, ErrInvalidSemVer80}81
82sv := &Version{83original: v,84}85
86// check for prerelease or build metadata87var extra []string88if strings.ContainsAny(parts[2], "-+") {89// Start with the build metadata first as it needs to be on the right90extra = strings.SplitN(parts[2], "+", 2)91if len(extra) > 1 {92// build metadata found93sv.metadata = extra[1]94parts[2] = extra[0]95}96
97extra = strings.SplitN(parts[2], "-", 2)98if len(extra) > 1 {99// prerelease found100sv.pre = extra[1]101parts[2] = extra[0]102}103}104
105// Validate the number segments are valid. This includes only having positive106// numbers and no leading 0's.107for _, p := range parts {108if !containsOnly(p, num) {109return nil, ErrInvalidCharacters110}111
112if len(p) > 1 && p[0] == '0' {113return nil, ErrSegmentStartsZero114}115}116
117// Extract the major, minor, and patch elements onto the returned Version118var err error119sv.major, err = strconv.ParseUint(parts[0], 10, 64)120if err != nil {121return nil, err122}123
124sv.minor, err = strconv.ParseUint(parts[1], 10, 64)125if err != nil {126return nil, err127}128
129sv.patch, err = strconv.ParseUint(parts[2], 10, 64)130if err != nil {131return nil, err132}133
134// No prerelease or build metadata found so returning now as a fastpath.135if sv.pre == "" && sv.metadata == "" {136return sv, nil137}138
139if sv.pre != "" {140if err = validatePrerelease(sv.pre); err != nil {141return nil, err142}143}144
145if sv.metadata != "" {146if err = validateMetadata(sv.metadata); err != nil {147return nil, err148}149}150
151return sv, nil152}
153
154// NewVersion parses a given version and returns an instance of Version or
155// an error if unable to parse the version. If the version is SemVer-ish it
156// attempts to convert it to SemVer. If you want to validate it was a strict
157// semantic version at parse time see StrictNewVersion().
158func NewVersion(v string) (*Version, error) {159m := versionRegex.FindStringSubmatch(v)160if m == nil {161return nil, ErrInvalidSemVer162}163
164sv := &Version{165metadata: m[8],166pre: m[5],167original: v,168}169
170var err error171sv.major, err = strconv.ParseUint(m[1], 10, 64)172if err != nil {173return nil, fmt.Errorf("Error parsing version segment: %s", err)174}175
176if m[2] != "" {177sv.minor, err = strconv.ParseUint(strings.TrimPrefix(m[2], "."), 10, 64)178if err != nil {179return nil, fmt.Errorf("Error parsing version segment: %s", err)180}181} else {182sv.minor = 0183}184
185if m[3] != "" {186sv.patch, err = strconv.ParseUint(strings.TrimPrefix(m[3], "."), 10, 64)187if err != nil {188return nil, fmt.Errorf("Error parsing version segment: %s", err)189}190} else {191sv.patch = 0192}193
194// Perform some basic due diligence on the extra parts to ensure they are195// valid.196
197if sv.pre != "" {198if err = validatePrerelease(sv.pre); err != nil {199return nil, err200}201}202
203if sv.metadata != "" {204if err = validateMetadata(sv.metadata); err != nil {205return nil, err206}207}208
209return sv, nil210}
211
212// New creates a new instance of Version with each of the parts passed in as
213// arguments instead of parsing a version string.
214func New(major, minor, patch uint64, pre, metadata string) *Version {215v := Version{216major: major,217minor: minor,218patch: patch,219pre: pre,220metadata: metadata,221original: "",222}223
224v.original = v.String()225
226return &v227}
228
229// MustParse parses a given version and panics on error.
230func MustParse(v string) *Version {231sv, err := NewVersion(v)232if err != nil {233panic(err)234}235return sv236}
237
238// String converts a Version object to a string.
239// Note, if the original version contained a leading v this version will not.
240// See the Original() method to retrieve the original value. Semantic Versions
241// don't contain a leading v per the spec. Instead it's optional on
242// implementation.
243func (v Version) String() string {244var buf bytes.Buffer245
246fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)247if v.pre != "" {248fmt.Fprintf(&buf, "-%s", v.pre)249}250if v.metadata != "" {251fmt.Fprintf(&buf, "+%s", v.metadata)252}253
254return buf.String()255}
256
257// Original returns the original value passed in to be parsed.
258func (v *Version) Original() string {259return v.original260}
261
262// Major returns the major version.
263func (v Version) Major() uint64 {264return v.major265}
266
267// Minor returns the minor version.
268func (v Version) Minor() uint64 {269return v.minor270}
271
272// Patch returns the patch version.
273func (v Version) Patch() uint64 {274return v.patch275}
276
277// Prerelease returns the pre-release version.
278func (v Version) Prerelease() string {279return v.pre280}
281
282// Metadata returns the metadata on the version.
283func (v Version) Metadata() string {284return v.metadata285}
286
287// originalVPrefix returns the original 'v' prefix if any.
288func (v Version) originalVPrefix() string {289// Note, only lowercase v is supported as a prefix by the parser.290if v.original != "" && v.original[:1] == "v" {291return v.original[:1]292}293return ""294}
295
296// IncPatch produces the next patch version.
297// If the current version does not have prerelease/metadata information,
298// it unsets metadata and prerelease values, increments patch number.
299// If the current version has any of prerelease or metadata information,
300// it unsets both values and keeps current patch value
301func (v Version) IncPatch() Version {302vNext := v303// according to http://semver.org/#spec-item-9304// Pre-release versions have a lower precedence than the associated normal version.305// according to http://semver.org/#spec-item-10306// Build metadata SHOULD be ignored when determining version precedence.307if v.pre != "" {308vNext.metadata = ""309vNext.pre = ""310} else {311vNext.metadata = ""312vNext.pre = ""313vNext.patch = v.patch + 1314}315vNext.original = v.originalVPrefix() + "" + vNext.String()316return vNext317}
318
319// IncMinor produces the next minor version.
320// Sets patch to 0.
321// Increments minor number.
322// Unsets metadata.
323// Unsets prerelease status.
324func (v Version) IncMinor() Version {325vNext := v326vNext.metadata = ""327vNext.pre = ""328vNext.patch = 0329vNext.minor = v.minor + 1330vNext.original = v.originalVPrefix() + "" + vNext.String()331return vNext332}
333
334// IncMajor produces the next major version.
335// Sets patch to 0.
336// Sets minor to 0.
337// Increments major number.
338// Unsets metadata.
339// Unsets prerelease status.
340func (v Version) IncMajor() Version {341vNext := v342vNext.metadata = ""343vNext.pre = ""344vNext.patch = 0345vNext.minor = 0346vNext.major = v.major + 1347vNext.original = v.originalVPrefix() + "" + vNext.String()348return vNext349}
350
351// SetPrerelease defines the prerelease value.
352// Value must not include the required 'hyphen' prefix.
353func (v Version) SetPrerelease(prerelease string) (Version, error) {354vNext := v355if len(prerelease) > 0 {356if err := validatePrerelease(prerelease); err != nil {357return vNext, err358}359}360vNext.pre = prerelease361vNext.original = v.originalVPrefix() + "" + vNext.String()362return vNext, nil363}
364
365// SetMetadata defines metadata value.
366// Value must not include the required 'plus' prefix.
367func (v Version) SetMetadata(metadata string) (Version, error) {368vNext := v369if len(metadata) > 0 {370if err := validateMetadata(metadata); err != nil {371return vNext, err372}373}374vNext.metadata = metadata375vNext.original = v.originalVPrefix() + "" + vNext.String()376return vNext, nil377}
378
379// LessThan tests if one version is less than another one.
380func (v *Version) LessThan(o *Version) bool {381return v.Compare(o) < 0382}
383
384// GreaterThan tests if one version is greater than another one.
385func (v *Version) GreaterThan(o *Version) bool {386return v.Compare(o) > 0387}
388
389// Equal tests if two versions are equal to each other.
390// Note, versions can be equal with different metadata since metadata
391// is not considered part of the comparable version.
392func (v *Version) Equal(o *Version) bool {393return v.Compare(o) == 0394}
395
396// Compare compares this version to another one. It returns -1, 0, or 1 if
397// the version smaller, equal, or larger than the other version.
398//
399// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
400// lower than the version without a prerelease. Compare always takes into account
401// prereleases. If you want to work with ranges using typical range syntaxes that
402// skip prereleases if the range is not looking for them use constraints.
403func (v *Version) Compare(o *Version) int {404// Compare the major, minor, and patch version for differences. If a405// difference is found return the comparison.406if d := compareSegment(v.Major(), o.Major()); d != 0 {407return d408}409if d := compareSegment(v.Minor(), o.Minor()); d != 0 {410return d411}412if d := compareSegment(v.Patch(), o.Patch()); d != 0 {413return d414}415
416// At this point the major, minor, and patch versions are the same.417ps := v.pre418po := o.Prerelease()419
420if ps == "" && po == "" {421return 0422}423if ps == "" {424return 1425}426if po == "" {427return -1428}429
430return comparePrerelease(ps, po)431}
432
433// UnmarshalJSON implements JSON.Unmarshaler interface.
434func (v *Version) UnmarshalJSON(b []byte) error {435var s string436if err := json.Unmarshal(b, &s); err != nil {437return err438}439temp, err := NewVersion(s)440if err != nil {441return err442}443v.major = temp.major444v.minor = temp.minor445v.patch = temp.patch446v.pre = temp.pre447v.metadata = temp.metadata448v.original = temp.original449return nil450}
451
452// MarshalJSON implements JSON.Marshaler interface.
453func (v Version) MarshalJSON() ([]byte, error) {454return json.Marshal(v.String())455}
456
457// UnmarshalText implements the encoding.TextUnmarshaler interface.
458func (v *Version) UnmarshalText(text []byte) error {459temp, err := NewVersion(string(text))460if err != nil {461return err462}463
464*v = *temp465
466return nil467}
468
469// MarshalText implements the encoding.TextMarshaler interface.
470func (v Version) MarshalText() ([]byte, error) {471return []byte(v.String()), nil472}
473
474// Scan implements the SQL.Scanner interface.
475func (v *Version) Scan(value interface{}) error {476var s string477s, _ = value.(string)478temp, err := NewVersion(s)479if err != nil {480return err481}482v.major = temp.major483v.minor = temp.minor484v.patch = temp.patch485v.pre = temp.pre486v.metadata = temp.metadata487v.original = temp.original488return nil489}
490
491// Value implements the Driver.Valuer interface.
492func (v Version) Value() (driver.Value, error) {493return v.String(), nil494}
495
496func compareSegment(v, o uint64) int {497if v < o {498return -1499}500if v > o {501return 1502}503
504return 0505}
506
507func comparePrerelease(v, o string) int {508// split the prelease versions by their part. The separator, per the spec,509// is a .510sparts := strings.Split(v, ".")511oparts := strings.Split(o, ".")512
513// Find the longer length of the parts to know how many loop iterations to514// go through.515slen := len(sparts)516olen := len(oparts)517
518l := slen519if olen > slen {520l = olen521}522
523// Iterate over each part of the prereleases to compare the differences.524for i := 0; i < l; i++ {525// Since the lentgh of the parts can be different we need to create526// a placeholder. This is to avoid out of bounds issues.527stemp := ""528if i < slen {529stemp = sparts[i]530}531
532otemp := ""533if i < olen {534otemp = oparts[i]535}536
537d := comparePrePart(stemp, otemp)538if d != 0 {539return d540}541}542
543// Reaching here means two versions are of equal value but have different544// metadata (the part following a +). They are not identical in string form545// but the version comparison finds them to be equal.546return 0547}
548
549func comparePrePart(s, o string) int {550// Fastpath if they are equal551if s == o {552return 0553}554
555// When s or o are empty we can use the other in an attempt to determine556// the response.557if s == "" {558if o != "" {559return -1560}561return 1562}563
564if o == "" {565if s != "" {566return 1567}568return -1569}570
571// When comparing strings "99" is greater than "103". To handle572// cases like this we need to detect numbers and compare them. According573// to the semver spec, numbers are always positive. If there is a - at the574// start like -99 this is to be evaluated as an alphanum. numbers always575// have precedence over alphanum. Parsing as Uints because negative numbers576// are ignored.577
578oi, n1 := strconv.ParseUint(o, 10, 64)579si, n2 := strconv.ParseUint(s, 10, 64)580
581// The case where both are strings compare the strings582if n1 != nil && n2 != nil {583if s > o {584return 1585}586return -1587} else if n1 != nil {588// o is a string and s is a number589return -1590} else if n2 != nil {591// s is a string and o is a number592return 1593}594// Both are numbers595if si > oi {596return 1597}598return -1599}
600
601// Like strings.ContainsAny but does an only instead of any.
602func containsOnly(s string, comp string) bool {603return strings.IndexFunc(s, func(r rune) bool {604return !strings.ContainsRune(comp, r)605}) == -1606}
607
608// From the spec, "Identifiers MUST comprise only
609// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty.
610// Numeric identifiers MUST NOT include leading zeroes.". These segments can
611// be dot separated.
612func validatePrerelease(p string) error {613eparts := strings.Split(p, ".")614for _, p := range eparts {615if containsOnly(p, num) {616if len(p) > 1 && p[0] == '0' {617return ErrSegmentStartsZero618}619} else if !containsOnly(p, allowed) {620return ErrInvalidPrerelease621}622}623
624return nil625}
626
627// From the spec, "Build metadata MAY be denoted by
628// appending a plus sign and a series of dot separated identifiers immediately
629// following the patch or pre-release version. Identifiers MUST comprise only
630// ASCII alphanumerics and hyphen [0-9A-Za-z-]. Identifiers MUST NOT be empty."
631func validateMetadata(m string) error {632eparts := strings.Split(m, ".")633for _, p := range eparts {634if !containsOnly(p, allowed) {635return ErrInvalidMetadata636}637}638return nil639}
640