pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/version/version.go (about)

     1  // Package version provides methods for working with semver version info
     2  package version
     3  
     4  // ////////////////////////////////////////////////////////////////////////////////// //
     5  //                                                                                    //
     6  //                         Copyright (c) 2022 ESSENTIAL KAOS                          //
     7  //      Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>     //
     8  //                                                                                    //
     9  // ////////////////////////////////////////////////////////////////////////////////// //
    10  
    11  import (
    12  	"errors"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  )
    17  
    18  // ////////////////////////////////////////////////////////////////////////////////// //
    19  
    20  // Version contains version data
    21  type Version struct {
    22  	raw        string
    23  	slice      [3]int
    24  	preRelease string
    25  	build      string
    26  	size       int
    27  }
    28  
    29  // ////////////////////////////////////////////////////////////////////////////////// //
    30  
    31  var (
    32  	// ErrEmpty is returned is given version is empty
    33  	ErrEmpty = errors.New("Version can't be empty")
    34  	// ErrEmptyBuild is returned if build number is empty
    35  	ErrEmptyBuild = errors.New("Build number is empty")
    36  	// ErrEmptyPrerelease is returned is prerelease number is empty
    37  	ErrEmptyPrerelease = errors.New("Prerelease number is empty")
    38  )
    39  
    40  // ////////////////////////////////////////////////////////////////////////////////// //
    41  
    42  var preRegExp = regexp.MustCompile(`([a-zA-Z-.]{1,})([0-9]{0,})`)
    43  
    44  // ////////////////////////////////////////////////////////////////////////////////// //
    45  
    46  // Parse parses version string and return version struct
    47  func Parse(v string) (Version, error) {
    48  	if v == "" {
    49  		return Version{}, ErrEmpty
    50  	}
    51  
    52  	var slice = [3]int{0, 0, 0}
    53  	var raw = v
    54  
    55  	var (
    56  		preRelease string
    57  		build      string
    58  		size       int
    59  	)
    60  
    61  	if strings.Contains(v, "+") {
    62  		bs := strings.Split(v, "+")
    63  
    64  		if bs[1] == "" {
    65  			return Version{}, ErrEmptyBuild
    66  		}
    67  
    68  		v = bs[0]
    69  		build = bs[1]
    70  	}
    71  
    72  	if strings.Contains(v, "-") {
    73  		ps := strings.Split(v, "-")
    74  
    75  		if ps[1] == "" {
    76  			return Version{}, ErrEmptyPrerelease
    77  		}
    78  
    79  		v = ps[0]
    80  		preRelease = ps[1]
    81  	}
    82  
    83  	for index, version := range strings.Split(v, ".") {
    84  		if index < 3 {
    85  			iv, err := strconv.Atoi(version)
    86  
    87  			if err != nil {
    88  				return Version{}, err
    89  			}
    90  
    91  			slice[index] = iv
    92  			size = index + 1
    93  		}
    94  	}
    95  
    96  	return Version{
    97  		raw:        raw,
    98  		slice:      slice,
    99  		preRelease: preRelease,
   100  		build:      build,
   101  		size:       size,
   102  	}, nil
   103  }
   104  
   105  // ////////////////////////////////////////////////////////////////////////////////// //
   106  
   107  // Major returns major version
   108  func (v Version) Major() int {
   109  	if v.raw == "" || len(v.slice) == 0 {
   110  		return -1
   111  	}
   112  
   113  	return v.slice[0]
   114  }
   115  
   116  // Minor returns minor version
   117  func (v Version) Minor() int {
   118  	if v.raw == "" || len(v.slice) == 0 {
   119  		return -1
   120  	}
   121  
   122  	return v.slice[1]
   123  }
   124  
   125  // Patch returns patch version
   126  func (v Version) Patch() int {
   127  	if v.raw == "" || len(v.slice) == 0 {
   128  		return -1
   129  	}
   130  
   131  	return v.slice[2]
   132  }
   133  
   134  // PreRelease returns prerelease version
   135  func (v Version) PreRelease() string {
   136  	if v.raw == "" {
   137  		return ""
   138  	}
   139  
   140  	return v.preRelease
   141  }
   142  
   143  // Build returns build string
   144  func (v Version) Build() string {
   145  	if v.raw == "" {
   146  		return ""
   147  	}
   148  
   149  	return v.build
   150  }
   151  
   152  // Simple returns simple version (without prerelease and build info,
   153  // with major, minor and patch)
   154  func (v Version) Simple() string {
   155  	if v.raw == "" || len(v.slice) == 0 {
   156  		return "0.0.0"
   157  	}
   158  
   159  	return strconv.Itoa(v.slice[0]) + "." + strconv.Itoa(v.slice[1]) + "." + strconv.Itoa(v.slice[2])
   160  }
   161  
   162  // Equal return true if version are equal to given
   163  func (v Version) Equal(version Version) bool {
   164  	if v.Major() != version.Major() {
   165  		return false
   166  	}
   167  
   168  	if v.Minor() != version.Minor() {
   169  		return false
   170  	}
   171  
   172  	if v.Patch() != version.Patch() {
   173  		return false
   174  	}
   175  
   176  	if v.PreRelease() != version.PreRelease() {
   177  		return false
   178  	}
   179  
   180  	if v.Build() != version.Build() {
   181  		return false
   182  	}
   183  
   184  	return true
   185  }
   186  
   187  // Less returns true if given version is greater
   188  func (v Version) Less(version Version) bool {
   189  	if v.Int() < version.Int() {
   190  		return true
   191  	}
   192  
   193  	pr1, pr2 := v.PreRelease(), version.PreRelease()
   194  
   195  	if pr1 != pr2 {
   196  		return prereleaseLess(pr1, pr2)
   197  	}
   198  
   199  	return false
   200  }
   201  
   202  // Greater returns true if given version is less
   203  func (v Version) Greater(version Version) bool {
   204  	if v.Int() > version.Int() {
   205  		return true
   206  	}
   207  
   208  	pr1, pr2 := v.PreRelease(), version.PreRelease()
   209  
   210  	if pr1 != pr2 {
   211  		return !prereleaseLess(pr1, pr2)
   212  	}
   213  
   214  	return false
   215  }
   216  
   217  // Contains checks is current version contains given version
   218  func (v Version) Contains(version Version) bool {
   219  	if v.Major() != version.Major() {
   220  		return false
   221  	}
   222  
   223  	if v.size == 1 {
   224  		return true
   225  	}
   226  
   227  	if v.Minor() != version.Minor() {
   228  		return false
   229  	}
   230  
   231  	if v.size == 2 {
   232  		return true
   233  	}
   234  
   235  	if v.Patch() != version.Patch() {
   236  		return false
   237  	}
   238  
   239  	return false
   240  }
   241  
   242  // Int returns version as integer
   243  func (v Version) Int() int {
   244  	result := v.Major() * 1000000
   245  	result += v.Minor() * 1000
   246  	result += v.Patch()
   247  
   248  	return result
   249  }
   250  
   251  // IsZero returns if version is zero (0.0.0)
   252  func (v Version) IsZero() bool {
   253  	return v.raw == ""
   254  }
   255  
   256  // String returns version as string
   257  func (v Version) String() string {
   258  	return v.raw
   259  }
   260  
   261  // ////////////////////////////////////////////////////////////////////////////////// //
   262  
   263  func prereleaseLess(pr1, pr2 string) bool {
   264  	// Current version is release and given is prerelease
   265  	if pr1 == "" && pr2 != "" {
   266  		return false
   267  	}
   268  
   269  	// Current version is prerelease and given is release
   270  	if pr1 != "" && pr2 == "" {
   271  		return true
   272  	}
   273  
   274  	// Parse prerelease
   275  	pr1Re := preRegExp.FindStringSubmatch(pr1)
   276  	pr2Re := preRegExp.FindStringSubmatch(pr2)
   277  
   278  	pr1Name := pr1Re[1]
   279  	pr2Name := pr2Re[1]
   280  
   281  	if pr1Name > pr2Name {
   282  		return false
   283  	}
   284  
   285  	if pr1Name < pr2Name {
   286  		return true
   287  	}
   288  
   289  	// Errors not important, because if subver is empty
   290  	// Atoi return 0
   291  	pr1Ver, _ := strconv.Atoi(pr1Re[2])
   292  	pr2Ver, _ := strconv.Atoi(pr2Re[2])
   293  
   294  	return pr1Ver < pr2Ver
   295  }