github.com/quay/claircore@v1.5.28/pkg/pep440/version.go (about) 1 // Package pep440 implements types for working with versions as defined in 2 // PEP-440. 3 package pep440 4 5 import ( 6 "fmt" 7 "regexp" 8 "strconv" 9 "strings" 10 11 "github.com/quay/claircore" 12 ) 13 14 var pattern *regexp.Regexp 15 16 func init() { 17 // This is the regexp used in the "versioning" package, as noted in 18 // https://www.python.org/dev/peps/pep-0440/#id81 19 const r = `v?` + 20 `(?:` + 21 `(?:(?P<epoch>[0-9]+)!)?` + // epoch 22 `(?P<release>[0-9]+(?:\.[0-9]+)*)` + // release segment 23 `(?P<pre>[-_\.]?(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))[-_\.]?(?P<pre_n>[0-9]+)?)?` + // pre release 24 `(?P<post>(?:-(?P<post_n1>[0-9]+))|(?:[-_\.]?(?P<post_l>post|rev|r)[-_\.]?(?P<post_n2>[0-9]+)?))?` + // post release 25 `(?P<dev>[-_\.]?(?P<dev_l>dev)[-_\.]?(?P<dev_n>[0-9]+)?)?` + // dev release 26 `)` + 27 `(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?` // local version 28 pattern = regexp.MustCompile(r) 29 } 30 31 // Version repesents a canonical-ish represention of a PEP440 version. 32 // 33 // Local revisions are discarded. 34 type Version struct { 35 Epoch int 36 Release []int 37 Pre struct { 38 Label string 39 N int 40 } 41 Post int 42 Dev int 43 } 44 45 // Version returns a fixed-width slice of integers meant for allowing some 46 // amount of version comparision with no knowledge of the version scheme. 47 // 48 // In generating this slice, the following rules are applied: 49 // 50 // Release is normalized to five numbers. Missing numbers are set to "0" and 51 // additional numbers are dropped. 52 // 53 // The Dev revision is promoted earlier in the int slice if there's no Pre or 54 // Post revision, and sorts as earlier than a Pre revision. 55 func (v *Version) Version() (c claircore.Version) { 56 const ( 57 epoch = 0 58 rel = 1 59 preL = 6 60 preN = 7 61 post = 8 62 dev = 9 63 ) 64 // BUG(hank) The int-slice versioning method tries to accomdate arbitrary 65 // numbers, but may give odd results with sufficiently large revision 66 // numbers. One suggested workaround is to make fewer than 9 quintillion 67 // releases. 68 c.Kind = "pep440" 69 c.V[epoch] = int32(v.Epoch) 70 for i, n := range v.Release { 71 if i > 4 { 72 break 73 } 74 c.V[rel+i] = int32(n) 75 } 76 switch v.Pre.Label { 77 case "a": 78 c.V[preL] = -3 79 case "b": 80 c.V[preL] = -2 81 case "rc": 82 c.V[preL] = -1 83 } 84 c.V[preN] = int32(v.Pre.N) 85 c.V[post] = int32(v.Post) 86 if v.Dev != 0 { 87 if v.Post != 0 || c.V[preL] != 0 { 88 c.V[dev] = -int32(v.Dev) 89 } else { 90 const minInt = -int32((^uint32(0))>>1) - 1 91 c.V[preL] = minInt + int32(v.Dev) 92 } 93 } 94 95 return c 96 } 97 98 // String returns the canonicalized representation of the Version. 99 func (v *Version) String() string { 100 var b strings.Builder 101 if v.Epoch != 0 { 102 fmt.Fprintf(&b, "%d!", v.Epoch) 103 } 104 for i, n := range v.Release { 105 if i != 0 { 106 b.WriteByte('.') 107 } 108 b.WriteString(strconv.FormatInt(int64(n), 10)) 109 } 110 if v.Pre.Label != "" { 111 b.WriteString(v.Pre.Label) 112 b.WriteString(strconv.FormatInt(int64(v.Pre.N), 10)) 113 } 114 if v.Post != 0 { 115 fmt.Fprintf(&b, ".post%d", v.Post) 116 } 117 if v.Dev != 0 { 118 fmt.Fprintf(&b, ".dev%d", v.Dev) 119 } 120 return b.String() 121 } 122 123 // Compare returns an integer comparing two versions. The result will be 0 if 124 // a == b, -1 if a < b and +1 if a > b. 125 func (a *Version) Compare(b *Version) int { 126 av, bv := a.Version(), b.Version() 127 return av.Compare(&bv) 128 } 129 130 // Parse attempts to extract a PEP-440 version string from the provided string. 131 func Parse(s string) (v Version, err error) { 132 if !pattern.MatchString(s) { 133 return v, fmt.Errorf("invalid pep440 version: %q", s) 134 } 135 136 ms := pattern.FindStringSubmatch(s) 137 for i, n := range pattern.SubexpNames() { 138 if ms[i] == "" { 139 continue 140 } 141 142 switch n { 143 case "epoch": 144 v.Epoch, err = strconv.Atoi(ms[i]) 145 if err != nil { 146 return v, err 147 } 148 case "release": 149 ns := strings.Split(ms[i], ".") 150 v.Release = make([]int, len(ns)) 151 for i, n := range ns { 152 v.Release[i], err = strconv.Atoi(n) 153 if err != nil { 154 return v, err 155 } 156 } 157 case "pre_l": 158 switch l := ms[i]; l { 159 case "a", "alpha": 160 v.Pre.Label = "a" 161 case "b", "beta": 162 v.Pre.Label = "b" 163 case "rc", "c", "pre", "preview": 164 v.Pre.Label = "rc" 165 default: 166 return v, fmt.Errorf("unknown pre-release label %q", l) 167 } 168 case "pre_n": 169 v.Pre.N, err = strconv.Atoi(ms[i]) 170 if err != nil { 171 return v, err 172 } 173 case "post_n1", "post_n2": 174 v.Post, err = strconv.Atoi(ms[i]) 175 if err != nil { 176 return v, err 177 } 178 case "dev_n": 179 v.Dev, err = strconv.Atoi(ms[i]) 180 if err != nil { 181 return v, err 182 } 183 } 184 } 185 186 return v, nil 187 } 188 189 // Versions implements sort.Interface. 190 type Versions []Version 191 192 func (vs Versions) Len() int { 193 return len([]Version(vs)) 194 } 195 196 func (vs Versions) Less(i, j int) bool { 197 return vs[i].Compare(&vs[j]) == -1 198 } 199 200 func (vs Versions) Swap(i, j int) { 201 vs[i], vs[j] = vs[j], vs[i] 202 }