github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/strutil/version.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2017 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package strutil
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  )
    26  
    27  // golang: seriously? that's sad!
    28  func max(a, b int) int {
    29  	if a < b {
    30  		return b
    31  	}
    32  	return a
    33  }
    34  
    35  //go:generate go run ./chrorder/main.go -package=strutil -output=chrorder.go
    36  
    37  func cmpString(as, bs string) int {
    38  	for i := 0; i < max(len(as), len(bs)); i++ {
    39  		var a uint8
    40  		var b uint8
    41  		if i < len(as) {
    42  			a = as[i]
    43  		}
    44  		if i < len(bs) {
    45  			b = bs[i]
    46  		}
    47  		if chOrder[a] < chOrder[b] {
    48  			return -1
    49  		}
    50  		if chOrder[a] > chOrder[b] {
    51  			return +1
    52  		}
    53  	}
    54  	return 0
    55  }
    56  
    57  func trimLeadingZeroes(a string) string {
    58  	for i := 0; i < len(a); i++ {
    59  		if a[i] != '0' {
    60  			return a[i:]
    61  		}
    62  	}
    63  	return ""
    64  }
    65  
    66  // a and b both match /[0-9]+/
    67  func cmpNumeric(a, b string) int {
    68  	a = trimLeadingZeroes(a)
    69  	b = trimLeadingZeroes(b)
    70  
    71  	switch d := len(a) - len(b); {
    72  	case d > 0:
    73  		return 1
    74  	case d < 0:
    75  		return -1
    76  	}
    77  	for i := 0; i < len(a); i++ {
    78  		switch {
    79  		case a[i] > b[i]:
    80  			return 1
    81  		case a[i] < b[i]:
    82  			return -1
    83  		}
    84  	}
    85  	return 0
    86  }
    87  
    88  func matchEpoch(a string) bool {
    89  	if len(a) == 0 {
    90  		return false
    91  	}
    92  	if a[0] < '0' || a[0] > '9' {
    93  		return false
    94  	}
    95  	var i int
    96  	for i = 1; i < len(a) && a[i] >= '0' && a[i] <= '9'; i++ {
    97  	}
    98  	return i < len(a) && a[i] == ':'
    99  }
   100  
   101  func atMostOneDash(a string) bool {
   102  	seen := false
   103  	for i := 0; i < len(a); i++ {
   104  		if a[i] == '-' {
   105  			if seen {
   106  				return false
   107  			}
   108  			seen = true
   109  		}
   110  	}
   111  	return true
   112  }
   113  
   114  // VersionIsValid returns true if the given string is a valid
   115  // version number according to the debian policy
   116  func VersionIsValid(a string) bool {
   117  	if matchEpoch(a) {
   118  		return false
   119  	}
   120  	return atMostOneDash(a)
   121  }
   122  
   123  func nextFrag(s string) (frag, rest string, numeric bool) {
   124  	if len(s) == 0 {
   125  		return "", "", false
   126  	}
   127  
   128  	var i int
   129  	if s[0] >= '0' && s[0] <= '9' {
   130  		// is digit
   131  		for i = 1; i < len(s) && s[i] >= '0' && s[i] <= '9'; i++ {
   132  		}
   133  		numeric = true
   134  	} else {
   135  		// not digit
   136  		for i = 1; i < len(s) && (s[i] < '0' || s[i] > '9'); i++ {
   137  		}
   138  	}
   139  	return s[:i], s[i:], numeric
   140  }
   141  
   142  func compareSubversion(va, vb string) int {
   143  	var a, b string
   144  	var anum, bnum bool
   145  	var res int
   146  	for res == 0 {
   147  		a, va, anum = nextFrag(va)
   148  		b, vb, bnum = nextFrag(vb)
   149  		if a == "" && b == "" {
   150  			break
   151  		}
   152  		if anum && bnum {
   153  			res = cmpNumeric(a, b)
   154  		} else {
   155  			res = cmpString(a, b)
   156  		}
   157  	}
   158  	return res
   159  }
   160  
   161  // VersionCompare compare two version strings that follow the debian
   162  // version policy and
   163  // Returns:
   164  //   -1 if a is smaller than b
   165  //    0 if a equals b
   166  //   +1 if a is bigger than b
   167  func VersionCompare(va, vb string) (res int, err error) {
   168  	// FIXME: return err here instead
   169  	if !VersionIsValid(va) {
   170  		return 0, fmt.Errorf("invalid version %q", va)
   171  	}
   172  	if !VersionIsValid(vb) {
   173  		return 0, fmt.Errorf("invalid version %q", vb)
   174  	}
   175  
   176  	var sa, sb string
   177  	if ia := strings.IndexByte(va, '-'); ia < 0 {
   178  		sa = "0"
   179  	} else {
   180  		va, sa = va[:ia], va[ia+1:]
   181  	}
   182  	if ib := strings.IndexByte(vb, '-'); ib < 0 {
   183  		sb = "0"
   184  	} else {
   185  		vb, sb = vb[:ib], vb[ib+1:]
   186  	}
   187  
   188  	// the main version number (before the "-")
   189  	res = compareSubversion(va, vb)
   190  	if res != 0 {
   191  		return res, nil
   192  	}
   193  
   194  	// the subversion revision behind the "-"
   195  	return compareSubversion(sa, sb), nil
   196  }