decred.org/dcrdex@v1.0.5/dex/version/version.go (about) 1 // Copyright (c) 2015-2022 The Decred developers 2 // Use of this source code is governed by an ISC license 3 // that can be found at https://github.com/decred/dcrd/blob/master/LICENSE. 4 5 package version 6 7 import ( 8 "fmt" 9 "regexp" 10 "strconv" 11 "strings" 12 ) 13 14 const ( 15 // semanticAlphabet defines the allowed characters for the pre-release and 16 // build metadata portions of a semantic version string. 17 semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-." 18 ) 19 20 // semverRE is a regular expression used to parse a semantic version string into 21 // its constituent parts. 22 var semverRE = regexp.MustCompile(`^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)` + 23 `(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*` + 24 `[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`) 25 26 // parseUint32 converts the passed string to an unsigned integer or returns an 27 // error if it is invalid. 28 func parseUint32(s string, fieldName string) (uint32, error) { 29 val, err := strconv.ParseUint(s, 10, 32) 30 if err != nil { 31 return 0, fmt.Errorf("malformed semver %s: %w", fieldName, err) 32 } 33 return uint32(val), err 34 } 35 36 // checkSemString returns an error if the passed string contains characters that 37 // are not in the provided alphabet. 38 func checkSemString(s, alphabet, fieldName string) error { 39 for _, r := range s { 40 if !strings.ContainsRune(alphabet, r) { 41 return fmt.Errorf("malformed semver %s: %q invalid", fieldName, r) 42 } 43 } 44 return nil 45 } 46 47 // ParseSemVer parses various semver components from the provided string. 48 func ParseSemVer(s string) (major, minor, patch uint32, preRel, build string, err error) { 49 // Parse the various semver component from the version string via a regular 50 // expression. 51 m := semverRE.FindStringSubmatch(s) 52 if m == nil { 53 err := fmt.Errorf("malformed version string %q: does not conform to "+ 54 "semver specification", s) 55 return 0, 0, 0, "", "", err 56 } 57 58 major, err = parseUint32(m[1], "major") 59 if err != nil { 60 return 0, 0, 0, "", "", err 61 } 62 63 minor, err = parseUint32(m[2], "minor") 64 if err != nil { 65 return 0, 0, 0, "", "", err 66 } 67 68 patch, err = parseUint32(m[3], "patch") 69 if err != nil { 70 return 0, 0, 0, "", "", err 71 } 72 73 preRel = m[4] 74 err = checkSemString(preRel, semanticAlphabet, "pre-release") 75 if err != nil { 76 return 0, 0, 0, s, s, err 77 } 78 79 build = m[5] 80 err = checkSemString(build, semanticAlphabet, "buildmetadata") 81 if err != nil { 82 return 0, 0, 0, s, s, err 83 } 84 85 return major, minor, patch, preRel, build, nil 86 } 87 88 // Parse returns the application version as a properly formed string per the 89 // semantic versioning 2.0.0 spec (https://semver.org/). 90 func Parse(version string) string { 91 var err error 92 Major, Minor, Patch, PreRelease, BuildMetadata, err := ParseSemVer(version) 93 if err != nil { 94 panic(err) 95 } 96 if BuildMetadata == "" { 97 BuildMetadata = vcsCommitID() 98 if BuildMetadata != "" { 99 version = fmt.Sprintf("%d.%d.%d", Major, Minor, Patch) 100 if PreRelease != "" { 101 version += "-" + PreRelease 102 } 103 version += "+" + BuildMetadata 104 } 105 } 106 107 return version 108 } 109 110 // NormalizeString returns the passed string stripped of all characters which 111 // are not valid according to the semantic versioning guidelines for pre-release 112 // and build metadata strings. In particular they MUST only contain characters 113 // in semanticAlphabet. 114 func NormalizeString(str string) string { 115 var result strings.Builder 116 for _, r := range str { 117 if strings.ContainsRune(semanticAlphabet, r) { 118 result.WriteRune(r) 119 } 120 } 121 return result.String() 122 }