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  }