semver.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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 Precendence
  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. // Equals checks if v is equal to o.
  54. func (v Version) Equals(o Version) bool {
  55. return (v.Compare(o) == 0)
  56. }
  57. // EQ checks if v is equal to o.
  58. func (v Version) EQ(o Version) bool {
  59. return (v.Compare(o) == 0)
  60. }
  61. // NE checks if v is not equal to o.
  62. func (v Version) NE(o Version) bool {
  63. return (v.Compare(o) != 0)
  64. }
  65. // GT checks if v is greater than o.
  66. func (v Version) GT(o Version) bool {
  67. return (v.Compare(o) == 1)
  68. }
  69. // GTE checks if v is greater than or equal to o.
  70. func (v Version) GTE(o Version) bool {
  71. return (v.Compare(o) >= 0)
  72. }
  73. // GE checks if v is greater than or equal to o.
  74. func (v Version) GE(o Version) bool {
  75. return (v.Compare(o) >= 0)
  76. }
  77. // LT checks if v is less than o.
  78. func (v Version) LT(o Version) bool {
  79. return (v.Compare(o) == -1)
  80. }
  81. // LTE checks if v is less than or equal to o.
  82. func (v Version) LTE(o Version) bool {
  83. return (v.Compare(o) <= 0)
  84. }
  85. // LE checks if v is less than or equal to o.
  86. func (v Version) LE(o Version) bool {
  87. return (v.Compare(o) <= 0)
  88. }
  89. // Compare compares Versions v to o:
  90. // -1 == v is less than o
  91. // 0 == v is equal to o
  92. // 1 == v is greater than o
  93. func (v Version) Compare(o Version) int {
  94. if v.Major != o.Major {
  95. if v.Major > o.Major {
  96. return 1
  97. }
  98. return -1
  99. }
  100. if v.Minor != o.Minor {
  101. if v.Minor > o.Minor {
  102. return 1
  103. }
  104. return -1
  105. }
  106. if v.Patch != o.Patch {
  107. if v.Patch > o.Patch {
  108. return 1
  109. }
  110. return -1
  111. }
  112. // Quick comparison if a version has no prerelease versions
  113. if len(v.Pre) == 0 && len(o.Pre) == 0 {
  114. return 0
  115. } else if len(v.Pre) == 0 && len(o.Pre) > 0 {
  116. return 1
  117. } else if len(v.Pre) > 0 && len(o.Pre) == 0 {
  118. return -1
  119. }
  120. i := 0
  121. for ; i < len(v.Pre) && i < len(o.Pre); i++ {
  122. if comp := v.Pre[i].Compare(o.Pre[i]); comp == 0 {
  123. continue
  124. } else if comp == 1 {
  125. return 1
  126. } else {
  127. return -1
  128. }
  129. }
  130. // If all pr versions are the equal but one has further prversion, this one greater
  131. if i == len(v.Pre) && i == len(o.Pre) {
  132. return 0
  133. } else if i == len(v.Pre) && i < len(o.Pre) {
  134. return -1
  135. } else {
  136. return 1
  137. }
  138. }
  139. // Validate validates v and returns error in case
  140. func (v Version) Validate() error {
  141. // Major, Minor, Patch already validated using uint64
  142. for _, pre := range v.Pre {
  143. if !pre.IsNum { //Numeric prerelease versions already uint64
  144. if len(pre.VersionStr) == 0 {
  145. return fmt.Errorf("Prerelease can not be empty %q", pre.VersionStr)
  146. }
  147. if !containsOnly(pre.VersionStr, alphanum) {
  148. return fmt.Errorf("Invalid character(s) found in prerelease %q", pre.VersionStr)
  149. }
  150. }
  151. }
  152. for _, build := range v.Build {
  153. if len(build) == 0 {
  154. return fmt.Errorf("Build meta data can not be empty %q", build)
  155. }
  156. if !containsOnly(build, alphanum) {
  157. return fmt.Errorf("Invalid character(s) found in build meta data %q", build)
  158. }
  159. }
  160. return nil
  161. }
  162. // New is an alias for Parse and returns a pointer, parses version string and returns a validated Version or error
  163. func New(s string) (vp *Version, err error) {
  164. v, err := Parse(s)
  165. vp = &v
  166. return
  167. }
  168. // Make is an alias for Parse, parses version string and returns a validated Version or error
  169. func Make(s string) (Version, error) {
  170. return Parse(s)
  171. }
  172. // Parse parses version string and returns a validated Version or error
  173. func Parse(s string) (Version, error) {
  174. if len(s) == 0 {
  175. return Version{}, errors.New("Version string empty")
  176. }
  177. // Split into major.minor.(patch+pr+meta)
  178. parts := strings.SplitN(s, ".", 3)
  179. if len(parts) != 3 {
  180. return Version{}, errors.New("No Major.Minor.Patch elements found")
  181. }
  182. // Major
  183. if !containsOnly(parts[0], numbers) {
  184. return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
  185. }
  186. if hasLeadingZeroes(parts[0]) {
  187. return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
  188. }
  189. major, err := strconv.ParseUint(parts[0], 10, 64)
  190. if err != nil {
  191. return Version{}, err
  192. }
  193. // Minor
  194. if !containsOnly(parts[1], numbers) {
  195. return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
  196. }
  197. if hasLeadingZeroes(parts[1]) {
  198. return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
  199. }
  200. minor, err := strconv.ParseUint(parts[1], 10, 64)
  201. if err != nil {
  202. return Version{}, err
  203. }
  204. v := Version{}
  205. v.Major = major
  206. v.Minor = minor
  207. var build, prerelease []string
  208. patchStr := parts[2]
  209. if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
  210. build = strings.Split(patchStr[buildIndex+1:], ".")
  211. patchStr = patchStr[:buildIndex]
  212. }
  213. if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
  214. prerelease = strings.Split(patchStr[preIndex+1:], ".")
  215. patchStr = patchStr[:preIndex]
  216. }
  217. if !containsOnly(patchStr, numbers) {
  218. return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
  219. }
  220. if hasLeadingZeroes(patchStr) {
  221. return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
  222. }
  223. patch, err := strconv.ParseUint(patchStr, 10, 64)
  224. if err != nil {
  225. return Version{}, err
  226. }
  227. v.Patch = patch
  228. // Prerelease
  229. for _, prstr := range prerelease {
  230. parsedPR, err := NewPRVersion(prstr)
  231. if err != nil {
  232. return Version{}, err
  233. }
  234. v.Pre = append(v.Pre, parsedPR)
  235. }
  236. // Build meta data
  237. for _, str := range build {
  238. if len(str) == 0 {
  239. return Version{}, errors.New("Build meta data is empty")
  240. }
  241. if !containsOnly(str, alphanum) {
  242. return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
  243. }
  244. v.Build = append(v.Build, str)
  245. }
  246. return v, nil
  247. }
  248. // MustParse is like Parse but panics if the version cannot be parsed.
  249. func MustParse(s string) Version {
  250. v, err := Parse(s)
  251. if err != nil {
  252. panic(`semver: Parse(` + s + `): ` + err.Error())
  253. }
  254. return v
  255. }
  256. // PRVersion represents a PreRelease Version
  257. type PRVersion struct {
  258. VersionStr string
  259. VersionNum uint64
  260. IsNum bool
  261. }
  262. // NewPRVersion creates a new valid prerelease version
  263. func NewPRVersion(s string) (PRVersion, error) {
  264. if len(s) == 0 {
  265. return PRVersion{}, errors.New("Prerelease is empty")
  266. }
  267. v := PRVersion{}
  268. if containsOnly(s, numbers) {
  269. if hasLeadingZeroes(s) {
  270. return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
  271. }
  272. num, err := strconv.ParseUint(s, 10, 64)
  273. // Might never be hit, but just in case
  274. if err != nil {
  275. return PRVersion{}, err
  276. }
  277. v.VersionNum = num
  278. v.IsNum = true
  279. } else if containsOnly(s, alphanum) {
  280. v.VersionStr = s
  281. v.IsNum = false
  282. } else {
  283. return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
  284. }
  285. return v, nil
  286. }
  287. // IsNumeric checks if prerelease-version is numeric
  288. func (v PRVersion) IsNumeric() bool {
  289. return v.IsNum
  290. }
  291. // Compare compares two PreRelease Versions v and o:
  292. // -1 == v is less than o
  293. // 0 == v is equal to o
  294. // 1 == v is greater than o
  295. func (v PRVersion) Compare(o PRVersion) int {
  296. if v.IsNum && !o.IsNum {
  297. return -1
  298. } else if !v.IsNum && o.IsNum {
  299. return 1
  300. } else if v.IsNum && o.IsNum {
  301. if v.VersionNum == o.VersionNum {
  302. return 0
  303. } else if v.VersionNum > o.VersionNum {
  304. return 1
  305. } else {
  306. return -1
  307. }
  308. } else { // both are Alphas
  309. if v.VersionStr == o.VersionStr {
  310. return 0
  311. } else if v.VersionStr > o.VersionStr {
  312. return 1
  313. } else {
  314. return -1
  315. }
  316. }
  317. }
  318. // PreRelease version to string
  319. func (v PRVersion) String() string {
  320. if v.IsNum {
  321. return strconv.FormatUint(v.VersionNum, 10)
  322. }
  323. return v.VersionStr
  324. }
  325. func containsOnly(s string, set string) bool {
  326. return strings.IndexFunc(s, func(r rune) bool {
  327. return !strings.ContainsRune(set, r)
  328. }) == -1
  329. }
  330. func hasLeadingZeroes(s string) bool {
  331. return len(s) > 1 && s[0] == '0'
  332. }
  333. // NewBuildVersion creates a new valid build version
  334. func NewBuildVersion(s string) (string, error) {
  335. if len(s) == 0 {
  336. return "", errors.New("Buildversion is empty")
  337. }
  338. if !containsOnly(s, alphanum) {
  339. return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
  340. }
  341. return s, nil
  342. }