semver.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package semver
  2. import (
  3. "errors"
  4. "fmt"
  5. "strconv"
  6. "strings"
  7. )
  8. const (
  9. numbers string = "0123456789"
  10. alphas = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
  11. alphanum = alphas + numbers
  12. )
  13. // SpecVersion is the latest fully supported spec version of semver
  14. var SpecVersion = Version{
  15. Major: 2,
  16. Minor: 0,
  17. Patch: 0,
  18. }
  19. // Version represents a semver compatible version
  20. type Version struct {
  21. Major uint64
  22. Minor uint64
  23. Patch uint64
  24. Pre []PRVersion
  25. Build []string //No Precedence
  26. }
  27. // Version to string
  28. func (v Version) String() string {
  29. b := make([]byte, 0, 5)
  30. b = strconv.AppendUint(b, v.Major, 10)
  31. b = append(b, '.')
  32. b = strconv.AppendUint(b, v.Minor, 10)
  33. b = append(b, '.')
  34. b = strconv.AppendUint(b, v.Patch, 10)
  35. if len(v.Pre) > 0 {
  36. b = append(b, '-')
  37. b = append(b, v.Pre[0].String()...)
  38. for _, pre := range v.Pre[1:] {
  39. b = append(b, '.')
  40. b = append(b, pre.String()...)
  41. }
  42. }
  43. if len(v.Build) > 0 {
  44. b = append(b, '+')
  45. b = append(b, v.Build[0]...)
  46. for _, build := range v.Build[1:] {
  47. b = append(b, '.')
  48. b = append(b, build...)
  49. }
  50. }
  51. return string(b)
  52. }
  53. // FinalizeVersion discards prerelease and build number and only returns
  54. // major, minor and patch number.
  55. func (v Version) FinalizeVersion() string {
  56. b := make([]byte, 0, 5)
  57. b = strconv.AppendUint(b, v.Major, 10)
  58. b = append(b, '.')
  59. b = strconv.AppendUint(b, v.Minor, 10)
  60. b = append(b, '.')
  61. b = strconv.AppendUint(b, v.Patch, 10)
  62. return string(b)
  63. }
  64. // Equals checks if v is equal to o.
  65. func (v Version) Equals(o Version) bool {
  66. return (v.Compare(o) == 0)
  67. }
  68. // EQ checks if v is equal to o.
  69. func (v Version) EQ(o Version) bool {
  70. return (v.Compare(o) == 0)
  71. }
  72. // NE checks if v is not equal to o.
  73. func (v Version) NE(o Version) bool {
  74. return (v.Compare(o) != 0)
  75. }
  76. // GT checks if v is greater than o.
  77. func (v Version) GT(o Version) bool {
  78. return (v.Compare(o) == 1)
  79. }
  80. // GTE checks if v is greater than or equal to o.
  81. func (v Version) GTE(o Version) bool {
  82. return (v.Compare(o) >= 0)
  83. }
  84. // GE checks if v is greater than or equal to o.
  85. func (v Version) GE(o Version) bool {
  86. return (v.Compare(o) >= 0)
  87. }
  88. // LT checks if v is less than o.
  89. func (v Version) LT(o Version) bool {
  90. return (v.Compare(o) == -1)
  91. }
  92. // LTE checks if v is less than or equal to o.
  93. func (v Version) LTE(o Version) bool {
  94. return (v.Compare(o) <= 0)
  95. }
  96. // LE checks if v is less than or equal to o.
  97. func (v Version) LE(o Version) bool {
  98. return (v.Compare(o) <= 0)
  99. }
  100. // Compare compares Versions v to o:
  101. // -1 == v is less than o
  102. // 0 == v is equal to o
  103. // 1 == v is greater than o
  104. func (v Version) Compare(o Version) int {
  105. if v.Major != o.Major {
  106. if v.Major > o.Major {
  107. return 1
  108. }
  109. return -1
  110. }
  111. if v.Minor != o.Minor {
  112. if v.Minor > o.Minor {
  113. return 1
  114. }
  115. return -1
  116. }
  117. if v.Patch != o.Patch {
  118. if v.Patch > o.Patch {
  119. return 1
  120. }
  121. return -1
  122. }
  123. // Quick comparison if a version has no prerelease versions
  124. if len(v.Pre) == 0 && len(o.Pre) == 0 {
  125. return 0
  126. } else if len(v.Pre) == 0 && len(o.Pre) > 0 {
  127. return 1
  128. } else if len(v.Pre) > 0 && len(o.Pre) == 0 {
  129. return -1
  130. }
  131. i := 0
  132. for ; i < len(v.Pre) && i < len(o.Pre); i++ {
  133. if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
  134. continue
  135. } else if comp == 1 {
  136. return 1
  137. } else {
  138. return -1
  139. }
  140. }
  141. // If all pr versions are the equal but one has further prversion, this one greater
  142. if i == len(v.Pre) && i == len(o.Pre) {
  143. return 0
  144. } else if i == len(v.Pre) && i < len(o.Pre) {
  145. return -1
  146. } else {
  147. return 1
  148. }
  149. }
  150. // IncrementPatch increments the patch version
  151. func (v *Version) IncrementPatch() error {
  152. v.Patch++
  153. return nil
  154. }
  155. // IncrementMinor increments the minor version
  156. func (v *Version) IncrementMinor() error {
  157. v.Minor++
  158. v.Patch = 0
  159. return nil
  160. }
  161. // IncrementMajor increments the major version
  162. func (v *Version) IncrementMajor() error {
  163. v.Major++
  164. v.Minor = 0
  165. v.Patch = 0
  166. return nil
  167. }
  168. // Validate validates v and returns error in case
  169. func (v Version) Validate() error {
  170. // Major, Minor, Patch already validated using uint64
  171. for _, pre := range v.Pre {
  172. if !pre.IsNum { //Numeric prerelease versions already uint64
  173. if len(pre.VersionStr) == 0 {
  174. return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
  175. }
  176. if !containsOnly(pre.VersionStr, alphanum) {
  177. return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
  178. }
  179. }
  180. }
  181. for _, build := range v.Build {
  182. if len(build) == 0 {
  183. return fmt.Errorf("Build meta data can not be empty %q", build)
  184. }
  185. if !containsOnly(build, alphanum) {
  186. return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
  187. }
  188. }
  189. return nil
  190. }
  191. // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
  192. func New(s string) (*Version, error) {
  193. v, err := Parse(s)
  194. vp := &v
  195. return vp, err
  196. }
  197. // Make is an alias for Parse, parses version string and returns a validated Version or error
  198. func Make(s string) (Version, error) {
  199. return Parse(s)
  200. }
  201. // ParseTolerant allows for certain version specifications that do not strictly adhere to semver
  202. // specs to be parsed by this library. It does so by normalizing versions before passing them to
  203. // Parse(). It currently trims spaces, removes a "v" prefix, adds a 0 patch number to versions
  204. // with only major and minor components specified, and removes leading 0s.
  205. func ParseTolerant(s string) (Version, error) {
  206. s = strings.TrimSpace(s)
  207. s = strings.TrimPrefix(s, "v")
  208. // Split into major.minor.(patch+pr+meta)
  209. parts := strings.SplitN(s, ".", 3)
  210. // Remove leading zeros.
  211. for i, p := range parts {
  212. if len(p) > 1 {
  213. p = strings.TrimLeft(p, "0")
  214. if len(p) == 0 || !strings.ContainsAny(p[0:1], "0123456789") {
  215. p = "0" + p
  216. }
  217. parts[i] = p
  218. }
  219. }
  220. // Fill up shortened versions.
  221. if len(parts) < 3 {
  222. if strings.ContainsAny(parts[len(parts)-1], "+-") {
  223. return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
  224. }
  225. for len(parts) < 3 {
  226. parts = append(parts, "0")
  227. }
  228. }
  229. s = strings.Join(parts, ".")
  230. return Parse(s)
  231. }
  232. // Parse parses version string and returns a validated Version or error
  233. func Parse(s string) (Version, error) {
  234. if len(s) == 0 {
  235. return Version{}, errors.New("Version string empty")
  236. }
  237. // Split into major.minor.(patch+pr+meta)
  238. parts := strings.SplitN(s, ".", 3)
  239. if len(parts) != 3 {
  240. return Version{}, errors.New("No Major.Minor.Patch elements found")
  241. }
  242. // Major
  243. if !containsOnly(parts[0], numbers) {
  244. return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
  245. }
  246. if hasLeadingZeroes(parts[0]) {
  247. return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
  248. }
  249. major, err := strconv.ParseUint(parts[0], 10, 64)
  250. if err != nil {
  251. return Version{}, err
  252. }
  253. // Minor
  254. if !containsOnly(parts[1], numbers) {
  255. return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
  256. }
  257. if hasLeadingZeroes(parts[1]) {
  258. return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
  259. }
  260. minor, err := strconv.ParseUint(parts[1], 10, 64)
  261. if err != nil {
  262. return Version{}, err
  263. }
  264. v := Version{}
  265. v.Major = major
  266. v.Minor = minor
  267. var build, prerelease []string
  268. patchStr := parts[2]
  269. if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
  270. build = strings.Split(patchStr[buildIndex+1:], ".")
  271. patchStr = patchStr[:buildIndex]
  272. }
  273. if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
  274. prerelease = strings.Split(patchStr[preIndex+1:], ".")
  275. patchStr = patchStr[:preIndex]
  276. }
  277. if !containsOnly(patchStr, numbers) {
  278. return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
  279. }
  280. if hasLeadingZeroes(patchStr) {
  281. return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
  282. }
  283. patch, err := strconv.ParseUint(patchStr, 10, 64)
  284. if err != nil {
  285. return Version{}, err
  286. }
  287. v.Patch = patch
  288. // Prerelease
  289. for _, prstr := range prerelease {
  290. parsedPR, err := NewPRVersion(prstr)
  291. if err != nil {
  292. return Version{}, err
  293. }
  294. v.Pre = append(v.Pre, parsedPR)
  295. }
  296. // Build meta data
  297. for _, str := range build {
  298. if len(str) == 0 {
  299. return Version{}, errors.New("Build meta data is empty")
  300. }
  301. if !containsOnly(str, alphanum) {
  302. return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
  303. }
  304. v.Build = append(v.Build, str)
  305. }
  306. return v, nil
  307. }
  308. // MustParse is like Parse but panics if the version cannot be parsed.
  309. func MustParse(s string) Version {
  310. v, err := Parse(s)
  311. if err != nil {
  312. panic(`semver: Parse(` + s + `): ` + err.Error())
  313. }
  314. return v
  315. }
  316. // PRVersion represents a PreRelease Version
  317. type PRVersion struct {
  318. VersionStr string
  319. VersionNum uint64
  320. IsNum bool
  321. }
  322. // NewPRVersion creates a new valid prerelease version
  323. func NewPRVersion(s string) (PRVersion, error) {
  324. if len(s) == 0 {
  325. return PRVersion{}, errors.New("Prerelease is empty")
  326. }
  327. v := PRVersion{}
  328. if containsOnly(s, numbers) {
  329. if hasLeadingZeroes(s) {
  330. return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
  331. }
  332. num, err := strconv.ParseUint(s, 10, 64)
  333. // Might never be hit, but just in case
  334. if err != nil {
  335. return PRVersion{}, err
  336. }
  337. v.VersionNum = num
  338. v.IsNum = true
  339. } else if containsOnly(s, alphanum) {
  340. v.VersionStr = s
  341. v.IsNum = false
  342. } else {
  343. return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
  344. }
  345. return v, nil
  346. }
  347. // IsNumeric checks if prerelease-version is numeric
  348. func (v PRVersion) IsNumeric() bool {
  349. return v.IsNum
  350. }
  351. // Compare compares two PreRelease Versions v and o:
  352. // -1 == v is less than o
  353. // 0 == v is equal to o
  354. // 1 == v is greater than o
  355. func (v PRVersion) Compare(o PRVersion) int {
  356. if v.IsNum && !o.IsNum {
  357. return -1
  358. } else if !v.IsNum && o.IsNum {
  359. return 1
  360. } else if v.IsNum && o.IsNum {
  361. if v.VersionNum == o.VersionNum {
  362. return 0
  363. } else if v.VersionNum > o.VersionNum {
  364. return 1
  365. } else {
  366. return -1
  367. }
  368. } else { // both are Alphas
  369. if v.VersionStr == o.VersionStr {
  370. return 0
  371. } else if v.VersionStr > o.VersionStr {
  372. return 1
  373. } else {
  374. return -1
  375. }
  376. }
  377. }
  378. // PreRelease version to string
  379. func (v PRVersion) String() string {
  380. if v.IsNum {
  381. return strconv.FormatUint(v.VersionNum, 10)
  382. }
  383. return v.VersionStr
  384. }
  385. func containsOnly(s string, set string) bool {
  386. return strings.IndexFunc(s, func(r rune) bool {
  387. return !strings.ContainsRune(set, r)
  388. }) == -1
  389. }
  390. func hasLeadingZeroes(s string) bool {
  391. return len(s) > 1 && s[0] == '0'
  392. }
  393. // NewBuildVersion creates a new valid build version
  394. func NewBuildVersion(s string) (string, error) {
  395. if len(s) == 0 {
  396. return "", errors.New("Buildversion is empty")
  397. }
  398. if !containsOnly(s, alphanum) {
  399. return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
  400. }
  401. return s, nil
  402. }
  403. // FinalizeVersion returns the major, minor and patch number only and discards
  404. // prerelease and build number.
  405. func FinalizeVersion(s string) (string, error) {
  406. v, err := Parse(s)
  407. if err != nil {
  408. return "", err
  409. }
  410. v.Pre = nil
  411. v.Build = nil
  412. finalVer := v.String()
  413. return finalVer, nil
  414. }