github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/common/hugo/version.go (about)

     1  // Copyright 2018 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package hugo
    15  
    16  import (
    17  	"fmt"
    18  	"io"
    19  	"math"
    20  	"runtime"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"github.com/gohugoio/hugo/compare"
    25  	"github.com/spf13/cast"
    26  )
    27  
    28  // Version represents the Hugo build version.
    29  type Version struct {
    30  	Major int
    31  
    32  	Minor int
    33  
    34  	// Increment this for bug releases
    35  	PatchLevel int
    36  
    37  	// HugoVersionSuffix is the suffix used in the Hugo version string.
    38  	// It will be blank for release versions.
    39  	Suffix string
    40  }
    41  
    42  var (
    43  	_ compare.Eqer     = (*VersionString)(nil)
    44  	_ compare.Comparer = (*VersionString)(nil)
    45  )
    46  
    47  func (v Version) String() string {
    48  	return version(v.Major, v.Minor, v.PatchLevel, v.Suffix)
    49  }
    50  
    51  // Version returns the Hugo version.
    52  func (v Version) Version() VersionString {
    53  	return VersionString(v.String())
    54  }
    55  
    56  // Compare implements the compare.Comparer interface.
    57  func (h Version) Compare(other any) int {
    58  	return compareVersions(h, other)
    59  }
    60  
    61  // VersionString represents a Hugo version string.
    62  type VersionString string
    63  
    64  func (h VersionString) String() string {
    65  	return string(h)
    66  }
    67  
    68  // Compare implements the compare.Comparer interface.
    69  func (h VersionString) Compare(other any) int {
    70  	v := MustParseVersion(h.String())
    71  	return compareVersions(v, other)
    72  }
    73  
    74  // Eq implements the compare.Eqer interface.
    75  func (h VersionString) Eq(other any) bool {
    76  	s, err := cast.ToStringE(other)
    77  	if err != nil {
    78  		return false
    79  	}
    80  	return s == h.String()
    81  }
    82  
    83  var versionSuffixes = []string{"-test", "-DEV"}
    84  
    85  // ParseVersion parses a version string.
    86  func ParseVersion(s string) (Version, error) {
    87  	var vv Version
    88  	for _, suffix := range versionSuffixes {
    89  		if strings.HasSuffix(s, suffix) {
    90  			vv.Suffix = suffix
    91  			s = strings.TrimSuffix(s, suffix)
    92  		}
    93  	}
    94  
    95  	vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
    96  
    97  	return vv, nil
    98  }
    99  
   100  // MustParseVersion parses a version string
   101  // and panics if any error occurs.
   102  func MustParseVersion(s string) Version {
   103  	vv, err := ParseVersion(s)
   104  	if err != nil {
   105  		panic(err)
   106  	}
   107  	return vv
   108  }
   109  
   110  // ReleaseVersion represents the release version.
   111  func (v Version) ReleaseVersion() Version {
   112  	v.Suffix = ""
   113  	return v
   114  }
   115  
   116  // Next returns the next Hugo release version.
   117  func (v Version) Next() Version {
   118  	return Version{Major: v.Major, Minor: v.Minor + 1}
   119  }
   120  
   121  // Prev returns the previous Hugo release version.
   122  func (v Version) Prev() Version {
   123  	return Version{Major: v.Major, Minor: v.Minor - 1}
   124  }
   125  
   126  // NextPatchLevel returns the next patch/bugfix Hugo version.
   127  // This will be a patch increment on the previous Hugo version.
   128  func (v Version) NextPatchLevel(level int) Version {
   129  	prev := v.Prev()
   130  	prev.PatchLevel = level
   131  	return prev
   132  }
   133  
   134  // BuildVersionString creates a version string. This is what you see when
   135  // running "hugo version".
   136  func BuildVersionString() string {
   137  	// program := "Hugo Static Site Generator"
   138  	program := "hugo"
   139  
   140  	version := "v" + CurrentVersion.String()
   141  
   142  	bi := getBuildInfo()
   143  	if bi == nil {
   144  		return version
   145  	}
   146  	if bi.Revision != "" {
   147  		version += "-" + bi.Revision
   148  	}
   149  	if IsExtended {
   150  		version += "+extended"
   151  	}
   152  
   153  	osArch := bi.GoOS + "/" + bi.GoArch
   154  
   155  	date := bi.RevisionTime
   156  	if date == "" {
   157  		// Accept vendor-specified build date if .git/ is unavailable.
   158  		date = buildDate
   159  	}
   160  	if date == "" {
   161  		date = "unknown"
   162  	}
   163  
   164  	versionString := fmt.Sprintf("%s %s %s BuildDate=%s",
   165  		program, version, osArch, date)
   166  
   167  	if vendorInfo != "" {
   168  		versionString += " VendorInfo=" + vendorInfo
   169  	}
   170  
   171  	return versionString
   172  }
   173  
   174  func version(major, minor, patch int, suffix string) string {
   175  	if patch > 0 || minor > 53 {
   176  		return fmt.Sprintf("%d.%d.%d%s", major, minor, patch, suffix)
   177  	}
   178  	return fmt.Sprintf("%d.%d%s", major, minor, suffix)
   179  }
   180  
   181  // CompareVersion compares the given version string or number against the
   182  // running Hugo version.
   183  // It returns -1 if the given version is less than, 0 if equal and 1 if greater than
   184  // the running version.
   185  func CompareVersion(version any) int {
   186  	return compareVersions(CurrentVersion, version)
   187  }
   188  
   189  func compareVersions(inVersion Version, in any) int {
   190  	var c int
   191  	switch d := in.(type) {
   192  	case float64:
   193  		c = compareFloatWithVersion(d, inVersion)
   194  	case float32:
   195  		c = compareFloatWithVersion(float64(d), inVersion)
   196  	case int:
   197  		c = compareFloatWithVersion(float64(d), inVersion)
   198  	case int32:
   199  		c = compareFloatWithVersion(float64(d), inVersion)
   200  	case int64:
   201  		c = compareFloatWithVersion(float64(d), inVersion)
   202  	case Version:
   203  		if d.Major == inVersion.Major && d.Minor == inVersion.Minor && d.PatchLevel == inVersion.PatchLevel {
   204  			return strings.Compare(inVersion.Suffix, d.Suffix)
   205  		}
   206  		if d.Major > inVersion.Major {
   207  			return 1
   208  		} else if d.Major < inVersion.Major {
   209  			return -1
   210  		}
   211  		if d.Minor > inVersion.Minor {
   212  			return 1
   213  		} else if d.Minor < inVersion.Minor {
   214  			return -1
   215  		}
   216  		if d.PatchLevel > inVersion.PatchLevel {
   217  			return 1
   218  		} else if d.PatchLevel < inVersion.PatchLevel {
   219  			return -1
   220  		}
   221  	default:
   222  		s, err := cast.ToStringE(in)
   223  		if err != nil {
   224  			return -1
   225  		}
   226  
   227  		v, err := ParseVersion(s)
   228  		if err != nil {
   229  			return -1
   230  		}
   231  		return inVersion.Compare(v)
   232  
   233  	}
   234  
   235  	return c
   236  }
   237  
   238  func parseVersion(s string) (int, int, int) {
   239  	var major, minor, patch int
   240  	parts := strings.Split(s, ".")
   241  	if len(parts) > 0 {
   242  		major, _ = strconv.Atoi(parts[0])
   243  	}
   244  	if len(parts) > 1 {
   245  		minor, _ = strconv.Atoi(parts[1])
   246  	}
   247  	if len(parts) > 2 {
   248  		patch, _ = strconv.Atoi(parts[2])
   249  	}
   250  
   251  	return major, minor, patch
   252  }
   253  
   254  // compareFloatWithVersion compares v1 with v2.
   255  // It returns -1 if v1 is less than v2, 0 if v1 is equal to v2 and 1 if v1 is greater than v2.
   256  func compareFloatWithVersion(v1 float64, v2 Version) int {
   257  	mf, minf := math.Modf(v1)
   258  	v1maj := int(mf)
   259  	v1min := int(minf * 100)
   260  
   261  	if v2.Major == v1maj && v2.Minor == v1min {
   262  		return 0
   263  	}
   264  
   265  	if v1maj > v2.Major {
   266  		return 1
   267  
   268  	}
   269  
   270  	if v1maj < v2.Major {
   271  		return -1
   272  	}
   273  
   274  	if v1min > v2.Minor {
   275  		return 1
   276  	}
   277  
   278  	return -1
   279  
   280  }
   281  
   282  func GoMinorVersion() int {
   283  	return goMinorVersion(runtime.Version())
   284  }
   285  
   286  func goMinorVersion(version string) int {
   287  	if strings.HasPrefix(version, "devel") {
   288  		return 9999 // magic
   289  	}
   290  	var major, minor int
   291  	var trailing string
   292  	n, err := fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing)
   293  	if n == 2 && err == io.EOF {
   294  		// Means there were no trailing characters (i.e., not an alpha/beta)
   295  		err = nil
   296  	}
   297  	if err != nil {
   298  		return 0
   299  	}
   300  	return minor
   301  }