vendoring the semver library

master
Jordan Orelli 3 years ago
parent dcb2e52e7b
commit 85725345a0

@ -18,7 +18,8 @@ import (
"time"
"golang.org/x/crypto/bcrypt"
"golang.org/x/mod/semver"
"orel.li/mir/internal/semver"
)
// this is pretty janky, but I didn't want to import a routing library

@ -0,0 +1,27 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -0,0 +1,411 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package semver implements comparison of semantic version strings.
// In this package, semantic version strings must begin with a leading "v",
// as in "v1.0.0".
//
// The general form of a semantic version string accepted by this package is
//
// vMAJOR[.MINOR[.PATCH[-PRERELEASE][+BUILD]]]
//
// where square brackets indicate optional parts of the syntax;
// MAJOR, MINOR, and PATCH are decimal integers without extra leading zeros;
// PRERELEASE and BUILD are each a series of non-empty dot-separated identifiers
// using only alphanumeric characters and hyphens; and
// all-numeric PRERELEASE identifiers must not have leading zeros.
//
// This package follows Semantic Versioning 2.0.0 (see semver.org)
// with two exceptions. First, it requires the "v" prefix. Second, it recognizes
// vMAJOR and vMAJOR.MINOR (with no prerelease or build suffixes)
// as shorthands for vMAJOR.0.0 and vMAJOR.MINOR.0.
package semver
import "sort"
// parsed returns the parsed form of a semantic version string.
type parsed struct {
major string
minor string
patch string
short string
prerelease string
build string
err string
}
// IsValid reports whether v is a valid semantic version string.
func IsValid(v string) bool {
_, ok := parse(v)
return ok
}
// Canonical returns the canonical formatting of the semantic version v.
// It fills in any missing .MINOR or .PATCH and discards build metadata.
// Two semantic versions compare equal only if their canonical formattings
// are identical strings.
// The canonical invalid semantic version is the empty string.
func Canonical(v string) string {
p, ok := parse(v)
if !ok {
return ""
}
if p.build != "" {
return v[:len(v)-len(p.build)]
}
if p.short != "" {
return v + p.short
}
return v
}
// Major returns the major version prefix of the semantic version v.
// For example, Major("v2.1.0") == "v2".
// If v is an invalid semantic version string, Major returns the empty string.
func Major(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return v[:1+len(pv.major)]
}
// MajorMinor returns the major.minor version prefix of the semantic version v.
// For example, MajorMinor("v2.1.0") == "v2.1".
// If v is an invalid semantic version string, MajorMinor returns the empty string.
func MajorMinor(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
i := 1 + len(pv.major)
if j := i + 1 + len(pv.minor); j <= len(v) && v[i] == '.' && v[i+1:j] == pv.minor {
return v[:j]
}
return v[:i] + "." + pv.minor
}
// Prerelease returns the prerelease suffix of the semantic version v.
// For example, Prerelease("v2.1.0-pre+meta") == "-pre".
// If v is an invalid semantic version string, Prerelease returns the empty string.
func Prerelease(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.prerelease
}
// Build returns the build suffix of the semantic version v.
// For example, Build("v2.1.0+meta") == "+meta".
// If v is an invalid semantic version string, Build returns the empty string.
func Build(v string) string {
pv, ok := parse(v)
if !ok {
return ""
}
return pv.build
}
// Compare returns an integer comparing two versions according to
// semantic version precedence.
// The result will be 0 if v == w, -1 if v < w, or +1 if v > w.
//
// An invalid semantic version string is considered less than a valid one.
// All invalid semantic version strings compare equal to each other.
func Compare(v, w string) int {
pv, ok1 := parse(v)
pw, ok2 := parse(w)
if !ok1 && !ok2 {
return 0
}
if !ok1 {
return -1
}
if !ok2 {
return +1
}
if c := compareInt(pv.major, pw.major); c != 0 {
return c
}
if c := compareInt(pv.minor, pw.minor); c != 0 {
return c
}
if c := compareInt(pv.patch, pw.patch); c != 0 {
return c
}
return comparePrerelease(pv.prerelease, pw.prerelease)
}
// Max canonicalizes its arguments and then returns the version string
// that compares greater.
//
// Deprecated: use Compare instead. In most cases, returning a canonicalized
// version is not expected or desired.
func Max(v, w string) string {
v = Canonical(v)
w = Canonical(w)
if Compare(v, w) > 0 {
return v
}
return w
}
// ByVersion implements sort.Interface for sorting semantic version strings.
type ByVersion []string
func (vs ByVersion) Len() int { return len(vs) }
func (vs ByVersion) Swap(i, j int) { vs[i], vs[j] = vs[j], vs[i] }
func (vs ByVersion) Less(i, j int) bool {
cmp := Compare(vs[i], vs[j])
if cmp != 0 {
return cmp < 0
}
return vs[i] < vs[j]
}
// Sort sorts a list of semantic version strings using ByVersion.
func Sort(list []string) {
sort.Sort(ByVersion(list))
}
func parse(v string) (p parsed, ok bool) {
if v == "" || v[0] != 'v' {
p.err = "missing v prefix"
return
}
p.major, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad major version"
return
}
if v == "" {
p.minor = "0"
p.patch = "0"
p.short = ".0.0"
return
}
if v[0] != '.' {
p.err = "bad minor prefix"
ok = false
return
}
p.minor, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad minor version"
return
}
if v == "" {
p.patch = "0"
p.short = ".0"
return
}
if v[0] != '.' {
p.err = "bad patch prefix"
ok = false
return
}
p.patch, v, ok = parseInt(v[1:])
if !ok {
p.err = "bad patch version"
return
}
if len(v) > 0 && v[0] == '-' {
p.prerelease, v, ok = parsePrerelease(v)
if !ok {
p.err = "bad prerelease"
return
}
}
if len(v) > 0 && v[0] == '+' {
p.build, v, ok = parseBuild(v)
if !ok {
p.err = "bad build"
return
}
}
if v != "" {
p.err = "junk on end"
ok = false
return
}
ok = true
return
}
func parseInt(v string) (t, rest string, ok bool) {
if v == "" {
return
}
if v[0] < '0' || '9' < v[0] {
return
}
i := 1
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
if v[0] == '0' && i != 1 {
return
}
return v[:i], v[i:], true
}
func parsePrerelease(v string) (t, rest string, ok bool) {
// "A pre-release version MAY be denoted by appending a hyphen and
// a series of dot separated identifiers immediately following the patch version.
// Identifiers MUST comprise only ASCII alphanumerics and hyphen [0-9A-Za-z-].
// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes."
if v == "" || v[0] != '-' {
return
}
i := 1
start := 1
for i < len(v) && v[i] != '+' {
if !isIdentChar(v[i]) && v[i] != '.' {
return
}
if v[i] == '.' {
if start == i || isBadNum(v[start:i]) {
return
}
start = i + 1
}
i++
}
if start == i || isBadNum(v[start:i]) {
return
}
return v[:i], v[i:], true
}
func parseBuild(v string) (t, rest string, ok bool) {
if v == "" || v[0] != '+' {
return
}
i := 1
start := 1
for i < len(v) {
if !isIdentChar(v[i]) && v[i] != '.' {
return
}
if v[i] == '.' {
if start == i {
return
}
start = i + 1
}
i++
}
if start == i {
return
}
return v[:i], v[i:], true
}
func isIdentChar(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-'
}
func isBadNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v) && i > 1 && v[0] == '0'
}
func isNum(v string) bool {
i := 0
for i < len(v) && '0' <= v[i] && v[i] <= '9' {
i++
}
return i == len(v)
}
func compareInt(x, y string) int {
if x == y {
return 0
}
if len(x) < len(y) {
return -1
}
if len(x) > len(y) {
return +1
}
if x < y {
return -1
} else {
return +1
}
}
func comparePrerelease(x, y string) int {
// "When major, minor, and patch are equal, a pre-release version has
// lower precedence than a normal version.
// Example: 1.0.0-alpha < 1.0.0.
// Precedence for two pre-release versions with the same major, minor,
// and patch version MUST be determined by comparing each dot separated
// identifier from left to right until a difference is found as follows:
// identifiers consisting of only digits are compared numerically and
// identifiers with letters or hyphens are compared lexically in ASCII
// sort order. Numeric identifiers always have lower precedence than
// non-numeric identifiers. A larger set of pre-release fields has a
// higher precedence than a smaller set, if all of the preceding
// identifiers are equal.
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta <
// 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0."
if x == y {
return 0
}
if x == "" {
return +1
}
if y == "" {
return -1
}
for x != "" && y != "" {
x = x[1:] // skip - or .
y = y[1:] // skip - or .
var dx, dy string
dx, x = nextIdent(x)
dy, y = nextIdent(y)
if dx != dy {
ix := isNum(dx)
iy := isNum(dy)
if ix != iy {
if ix {
return -1
} else {
return +1
}
}
if ix {
if len(dx) < len(dy) {
return -1
}
if len(dx) > len(dy) {
return +1
}
}
if dx < dy {
return -1
} else {
return +1
}
}
}
if x == "" {
return -1
} else {
return +1
}
}
func nextIdent(x string) (dx, rest string) {
i := 0
for i < len(x) && x[i] != '.' {
i++
}
return x[:i], x[i:]
}

@ -0,0 +1,197 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package semver
import (
"math/rand"
"sort"
"strings"
"testing"
)
var tests = []struct {
in string
out string
}{
{"bad", ""},
{"v1-alpha.beta.gamma", ""},
{"v1-pre", ""},
{"v1+meta", ""},
{"v1-pre+meta", ""},
{"v1.2-pre", ""},
{"v1.2+meta", ""},
{"v1.2-pre+meta", ""},
{"v1.0.0-alpha", "v1.0.0-alpha"},
{"v1.0.0-alpha.1", "v1.0.0-alpha.1"},
{"v1.0.0-alpha.beta", "v1.0.0-alpha.beta"},
{"v1.0.0-beta", "v1.0.0-beta"},
{"v1.0.0-beta.2", "v1.0.0-beta.2"},
{"v1.0.0-beta.11", "v1.0.0-beta.11"},
{"v1.0.0-rc.1", "v1.0.0-rc.1"},
{"v1", "v1.0.0"},
{"v1.0", "v1.0.0"},
{"v1.0.0", "v1.0.0"},
{"v1.2", "v1.2.0"},
{"v1.2.0", "v1.2.0"},
{"v1.2.3-456", "v1.2.3-456"},
{"v1.2.3-456.789", "v1.2.3-456.789"},
{"v1.2.3-456-789", "v1.2.3-456-789"},
{"v1.2.3-456a", "v1.2.3-456a"},
{"v1.2.3-pre", "v1.2.3-pre"},
{"v1.2.3-pre+meta", "v1.2.3-pre"},
{"v1.2.3-pre.1", "v1.2.3-pre.1"},
{"v1.2.3-zzz", "v1.2.3-zzz"},
{"v1.2.3", "v1.2.3"},
{"v1.2.3+meta", "v1.2.3"},
{"v1.2.3+meta-pre", "v1.2.3"},
{"v1.2.3+meta-pre.sha.256a", "v1.2.3"},
}
func TestIsValid(t *testing.T) {
for _, tt := range tests {
ok := IsValid(tt.in)
if ok != (tt.out != "") {
t.Errorf("IsValid(%q) = %v, want %v", tt.in, ok, !ok)
}
}
}
func TestCanonical(t *testing.T) {
for _, tt := range tests {
out := Canonical(tt.in)
if out != tt.out {
t.Errorf("Canonical(%q) = %q, want %q", tt.in, out, tt.out)
}
}
}
func TestMajor(t *testing.T) {
for _, tt := range tests {
out := Major(tt.in)
want := ""
if i := strings.Index(tt.out, "."); i >= 0 {
want = tt.out[:i]
}
if out != want {
t.Errorf("Major(%q) = %q, want %q", tt.in, out, want)
}
}
}
func TestMajorMinor(t *testing.T) {
for _, tt := range tests {
out := MajorMinor(tt.in)
var want string
if tt.out != "" {
want = tt.in
if i := strings.Index(want, "+"); i >= 0 {
want = want[:i]
}
if i := strings.Index(want, "-"); i >= 0 {
want = want[:i]
}
switch strings.Count(want, ".") {
case 0:
want += ".0"
case 1:
// ok
case 2:
want = want[:strings.LastIndex(want, ".")]
}
}
if out != want {
t.Errorf("MajorMinor(%q) = %q, want %q", tt.in, out, want)
}
}
}
func TestPrerelease(t *testing.T) {
for _, tt := range tests {
pre := Prerelease(tt.in)
var want string
if tt.out != "" {
if i := strings.Index(tt.out, "-"); i >= 0 {
want = tt.out[i:]
}
}
if pre != want {
t.Errorf("Prerelease(%q) = %q, want %q", tt.in, pre, want)
}
}
}
func TestBuild(t *testing.T) {
for _, tt := range tests {
build := Build(tt.in)
var want string
if tt.out != "" {
if i := strings.Index(tt.in, "+"); i >= 0 {
want = tt.in[i:]
}
}
if build != want {
t.Errorf("Build(%q) = %q, want %q", tt.in, build, want)
}
}
}
func TestCompare(t *testing.T) {
for i, ti := range tests {
for j, tj := range tests {
cmp := Compare(ti.in, tj.in)
var want int
if ti.out == tj.out {
want = 0
} else if i < j {
want = -1
} else {
want = +1
}
if cmp != want {
t.Errorf("Compare(%q, %q) = %d, want %d", ti.in, tj.in, cmp, want)
}
}
}
}
func TestSort(t *testing.T) {
versions := make([]string, len(tests))
for i, test := range tests {
versions[i] = test.in
}
rand.Shuffle(len(versions), func(i, j int) { versions[i], versions[j] = versions[j], versions[i] })
Sort(versions)
if !sort.IsSorted(ByVersion(versions)) {
t.Errorf("list is not sorted:\n%s", strings.Join(versions, "\n"))
}
}
func TestMax(t *testing.T) {
for i, ti := range tests {
for j, tj := range tests {
max := Max(ti.in, tj.in)
want := Canonical(ti.in)
if i < j {
want = Canonical(tj.in)
}
if max != want {
t.Errorf("Max(%q, %q) = %q, want %q", ti.in, tj.in, max, want)
}
}
}
}
var (
v1 = "v1.0.0+metadata-dash"
v2 = "v1.0.0+metadata-dash1"
)
func BenchmarkCompare(b *testing.B) {
for i := 0; i < b.N; i++ {
if Compare(v1, v2) != 0 {
b.Fatalf("bad compare")
}
}
}

@ -13,8 +13,9 @@ import (
"strings"
"golang.org/x/mod/modfile"
"golang.org/x/mod/semver"
"golang.org/x/net/html"
"orel.li/mir/internal/semver"
)
func nextcmd(args []string) {
@ -208,3 +209,10 @@ func parseVersionLines(r io.Reader) ([]string, error) {
}
}
}
// nextMinor takes a sorted list of version strings and returns a string
// representing the next minor version
// func nextMinor(versions []string) string {
// last := "v0.0.0"
// semver.MajorMinor
// }

@ -0,0 +1,15 @@
package main
import (
"testing"
)
func TestIncrMinor(t *testing.T) {
var tests = []struct {
in string
out string
}{
{"v0.0.0", "v0.1.0"},
}
t.Logf("%v", tests)
}
Loading…
Cancel
Save