semver.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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. // ParseTolerant allows for certain version specifications that do not strictly adhere to semver
  173. // specs to be parsed by this library. It does so by normalizing versions before passing them to
  174. // Parse(). It currently trims spaces, removes a "v" prefix, and adds a 0 patch number to versions
  175. // with only major and minor components specified
  176. func ParseTolerant(s string) (Version, error) {
  177. s = strings.TrimSpace(s)
  178. s = strings.TrimPrefix(s, "v")
  179. // Split into major.minor.(patch+pr+meta)
  180. parts := strings.SplitN(s, ".", 3)
  181. if len(parts) < 3 {
  182. if strings.ContainsAny(parts[len(parts)-1], "+-") {
  183. return Version{}, errors.New("Short version cannot contain PreRelease/Build meta data")
  184. }
  185. for len(parts) < 3 {
  186. parts = append(parts, "0")
  187. }
  188. s = strings.Join(parts, ".")
  189. }
  190. return Parse(s)
  191. }
  192. // Parse parses version string and returns a validated Version or error
  193. func Parse(s string) (Version, error) {
  194. if len(s) == 0 {
  195. return Version{}, errors.New("Version string empty")
  196. }
  197. // Split into major.minor.(patch+pr+meta)
  198. parts := strings.SplitN(s, ".", 3)
  199. if len(parts) != 3 {
  200. return Version{}, errors.New("No Major.Minor.Patch elements found")
  201. }
  202. // Major
  203. if !containsOnly(parts[0], numbers) {
  204. return Version{}, fmt.Errorf("Invalid character(s) found in major number %q", parts[0])
  205. }
  206. if hasLeadingZeroes(parts[0]) {
  207. return Version{}, fmt.Errorf("Major number must not contain leading zeroes %q", parts[0])
  208. }
  209. major, err := strconv.ParseUint(parts[0], 10, 64)
  210. if err != nil {
  211. return Version{}, err
  212. }
  213. // Minor
  214. if !containsOnly(parts[1], numbers) {
  215. return Version{}, fmt.Errorf("Invalid character(s) found in minor number %q", parts[1])
  216. }
  217. if hasLeadingZeroes(parts[1]) {
  218. return Version{}, fmt.Errorf("Minor number must not contain leading zeroes %q", parts[1])
  219. }
  220. minor, err := strconv.ParseUint(parts[1], 10, 64)
  221. if err != nil {
  222. return Version{}, err
  223. }
  224. v := Version{}
  225. v.Major = major
  226. v.Minor = minor
  227. var build, prerelease []string
  228. patchStr := parts[2]
  229. if buildIndex := strings.IndexRune(patchStr, '+'); buildIndex != -1 {
  230. build = strings.Split(patchStr[buildIndex+1:], ".")
  231. patchStr = patchStr[:buildIndex]
  232. }
  233. if preIndex := strings.IndexRune(patchStr, '-'); preIndex != -1 {
  234. prerelease = strings.Split(patchStr[preIndex+1:], ".")
  235. patchStr = patchStr[:preIndex]
  236. }
  237. if !containsOnly(patchStr, numbers) {
  238. return Version{}, fmt.Errorf("Invalid character(s) found in patch number %q", patchStr)
  239. }
  240. if hasLeadingZeroes(patchStr) {
  241. return Version{}, fmt.Errorf("Patch number must not contain leading zeroes %q", patchStr)
  242. }
  243. patch, err := strconv.ParseUint(patchStr, 10, 64)
  244. if err != nil {
  245. return Version{}, err
  246. }
  247. v.Patch = patch
  248. // Prerelease
  249. for _, prstr := range prerelease {
  250. parsedPR, err := NewPRVersion(prstr)
  251. if err != nil {
  252. return Version{}, err
  253. }
  254. v.Pre = append(v.Pre, parsedPR)
  255. }
  256. // Build meta data
  257. for _, str := range build {
  258. if len(str) == 0 {
  259. return Version{}, errors.New("Build meta data is empty")
  260. }
  261. if !containsOnly(str, alphanum) {
  262. return Version{}, fmt.Errorf("Invalid character(s) found in build meta data %q", str)
  263. }
  264. v.Build = append(v.Build, str)
  265. }
  266. return v, nil
  267. }
  268. // MustParse is like Parse but panics if the version cannot be parsed.
  269. func MustParse(s string) Version {
  270. v, err := Parse(s)
  271. if err != nil {
  272. panic(`semver: Parse(` + s + `): ` + err.Error())
  273. }
  274. return v
  275. }
  276. // PRVersion represents a PreRelease Version
  277. type PRVersion struct {
  278. VersionStr string
  279. VersionNum uint64
  280. IsNum bool
  281. }
  282. // NewPRVersion creates a new valid prerelease version
  283. func NewPRVersion(s string) (PRVersion, error) {
  284. if len(s) == 0 {
  285. return PRVersion{}, errors.New("Prerelease is empty")
  286. }
  287. v := PRVersion{}
  288. if containsOnly(s, numbers) {
  289. if hasLeadingZeroes(s) {
  290. return PRVersion{}, fmt.Errorf("Numeric PreRelease version must not contain leading zeroes %q", s)
  291. }
  292. num, err := strconv.ParseUint(s, 10, 64)
  293. // Might never be hit, but just in case
  294. if err != nil {
  295. return PRVersion{}, err
  296. }
  297. v.VersionNum = num
  298. v.IsNum = true
  299. } else if containsOnly(s, alphanum) {
  300. v.VersionStr = s
  301. v.IsNum = false
  302. } else {
  303. return PRVersion{}, fmt.Errorf("Invalid character(s) found in prerelease %q", s)
  304. }
  305. return v, nil
  306. }
  307. // IsNumeric checks if prerelease-version is numeric
  308. func (v PRVersion) IsNumeric() bool {
  309. return v.IsNum
  310. }
  311. // Compare compares two PreRelease Versions v and o:
  312. // -1 == v is less than o
  313. // 0 == v is equal to o
  314. // 1 == v is greater than o
  315. func (v PRVersion) Compare(o PRVersion) int {
  316. if v.IsNum && !o.IsNum {
  317. return -1
  318. } else if !v.IsNum && o.IsNum {
  319. return 1
  320. } else if v.IsNum && o.IsNum {
  321. if v.VersionNum == o.VersionNum {
  322. return 0
  323. } else if v.VersionNum > o.VersionNum {
  324. return 1
  325. } else {
  326. return -1
  327. }
  328. } else { // both are Alphas
  329. if v.VersionStr == o.VersionStr {
  330. return 0
  331. } else if v.VersionStr > o.VersionStr {
  332. return 1
  333. } else {
  334. return -1
  335. }
  336. }
  337. }
  338. // PreRelease version to string
  339. func (v PRVersion) String() string {
  340. if v.IsNum {
  341. return strconv.FormatUint(v.VersionNum, 10)
  342. }
  343. return v.VersionStr
  344. }
  345. func containsOnly(s string, set string) bool {
  346. return strings.IndexFunc(s, func(r rune) bool {
  347. return !strings.ContainsRune(set, r)
  348. }) == -1
  349. }
  350. func hasLeadingZeroes(s string) bool {
  351. return len(s) > 1 && s[0] == '0'
  352. }
  353. // NewBuildVersion creates a new valid build version
  354. func NewBuildVersion(s string) (string, error) {
  355. if len(s) == 0 {
  356. return "", errors.New("Buildversion is empty")
  357. }
  358. if !containsOnly(s, alphanum) {
  359. return "", fmt.Errorf("Invalid character(s) found in build meta data %q", s)
  360. }
  361. return s, nil
  362. }