blob: 026e99f36216daa7129c86ffb2cb84b83dbf34d6 [file] [log] [blame]
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
// Package version implements version parsing.
package version
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
"strings"
"gopkg.in/mgo.v2/bson"
)
// Number represents a version number.
type Number struct {
Major int
Minor int
Tag string
Patch int
Build int
}
// Zero is occasionally convenient and readable.
// Please don't change its value.
var Zero = Number{}
// Binary specifies a binary version of juju.v
type Binary struct {
Number
Series string
Arch string
}
// String returns the string representation of the binary version.
func (b Binary) String() string {
return fmt.Sprintf("%v-%s-%s", b.Number, b.Series, b.Arch)
}
// GetBSON implements bson.Getter.
func (b Binary) GetBSON() (interface{}, error) {
return b.String(), nil
}
// SetBSON implements bson.Setter.
func (b *Binary) SetBSON(raw bson.Raw) error {
var s string
err := raw.Unmarshal(&s)
if err != nil {
return err
}
v, err := ParseBinary(s)
if err != nil {
return err
}
*b = v
return nil
}
// MarshalJSON implements json.Marshaler.
func (b Binary) MarshalJSON() ([]byte, error) {
return json.Marshal(b.String())
}
// UnmarshalJSON implements json.Unmarshaler.
func (b *Binary) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
v, err := ParseBinary(s)
if err != nil {
return err
}
*b = v
return nil
}
// MarshalYAML implements yaml.v2.Marshaller interface.
func (b Binary) MarshalYAML() (interface{}, error) {
return b.String(), nil
}
// UnmarshalYAML implements the yaml.Unmarshaller interface.
func (b *Binary) UnmarshalYAML(unmarshal func(interface{}) error) error {
var vstr string
err := unmarshal(&vstr)
if err != nil {
return err
}
v, err := ParseBinary(vstr)
if err != nil {
return err
}
*b = v
return nil
}
var (
binaryPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?-([^-]+)-([^-]+)$`)
numberPat = regexp.MustCompile(`^(\d{1,9})\.(\d{1,9})(?:\.|-([a-z]+))(\d{1,9})(\.\d{1,9})?$`)
)
// MustParse parses a version and panics if it does
// not parse correctly.
func MustParse(s string) Number {
v, err := Parse(s)
if err != nil {
panic(err)
}
return v
}
// MustParseBinary parses a binary version and panics if it does
// not parse correctly.
func MustParseBinary(s string) Binary {
b, err := ParseBinary(s)
if err != nil {
panic(err)
}
return b
}
// ParseBinary parses a binary version of the form "1.2.3-series-arch".
func ParseBinary(s string) (Binary, error) {
m := binaryPat.FindStringSubmatch(s)
if m == nil {
return Binary{}, fmt.Errorf("invalid binary version %q", s)
}
var b Binary
b.Major = atoi(m[1])
b.Minor = atoi(m[2])
b.Tag = m[3]
b.Patch = atoi(m[4])
if m[5] != "" {
b.Build = atoi(m[5][1:])
}
b.Series = m[6]
b.Arch = m[7]
return b, nil
}
// Parse parses the version, which is of the form 1.2.3
// giving the major, minor and release versions
// respectively.
func Parse(s string) (Number, error) {
m := numberPat.FindStringSubmatch(s)
if m == nil {
return Number{}, fmt.Errorf("invalid version %q", s)
}
var n Number
n.Major = atoi(m[1])
n.Minor = atoi(m[2])
n.Tag = m[3]
n.Patch = atoi(m[4])
if m[5] != "" {
n.Build = atoi(m[5][1:])
}
return n, nil
}
// atoi is the same as strconv.Atoi but assumes that
// the string has been verified to be a valid integer.
func atoi(s string) int {
n, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return n
}
// String returns the string representation of this Number.
func (n Number) String() string {
var s string
if n.Tag == "" {
s = fmt.Sprintf("%d.%d.%d", n.Major, n.Minor, n.Patch)
} else {
s = fmt.Sprintf("%d.%d-%s%d", n.Major, n.Minor, n.Tag, n.Patch)
}
if n.Build > 0 {
s += fmt.Sprintf(".%d", n.Build)
}
return s
}
// Compare returns -1, 0 or 1 depending on whether
// n is less than, equal to or greater than other.
// The comparison compares Major, then Minor, then Patch, then Build, using the first difference as
func (n Number) Compare(other Number) int {
if n == other {
return 0
}
less := false
switch {
case n.Major != other.Major:
less = n.Major < other.Major
case n.Minor != other.Minor:
less = n.Minor < other.Minor
case n.Tag != other.Tag:
switch {
case n.Tag == "":
less = false
case other.Tag == "":
less = true
default:
less = n.Tag < other.Tag
}
case n.Patch != other.Patch:
less = n.Patch < other.Patch
case n.Build != other.Build:
less = n.Build < other.Build
}
if less {
return -1
}
return 1
}
// GetBSON implements bson.Getter.
func (n Number) GetBSON() (interface{}, error) {
return n.String(), nil
}
// SetBSON implements bson.Setter.
func (n *Number) SetBSON(raw bson.Raw) error {
var s string
err := raw.Unmarshal(&s)
if err != nil {
return err
}
v, err := Parse(s)
if err != nil {
return err
}
*n = v
return nil
}
// MarshalJSON implements json.Marshaler.
func (n Number) MarshalJSON() ([]byte, error) {
return json.Marshal(n.String())
}
// UnmarshalJSON implements json.Unmarshaler.
func (n *Number) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
v, err := Parse(s)
if err != nil {
return err
}
*n = v
return nil
}
// MarshalYAML implements yaml.v2.Marshaller interface
func (n Number) MarshalYAML() (interface{}, error) {
return n.String(), nil
}
// UnmarshalYAML implements the yaml.Unmarshaller interface
func (n *Number) UnmarshalYAML(unmarshal func(interface{}) error) error {
var vstr string
err := unmarshal(&vstr)
if err != nil {
return err
}
v, err := Parse(vstr)
if err != nil {
return err
}
*n = v
return nil
}
// ParseMajorMinor takes an argument of the form "major.minor" and returns ints major and minor.
func ParseMajorMinor(vers string) (int, int, error) {
parts := strings.Split(vers, ".")
major, err := strconv.Atoi(parts[0])
minor := -1
if err != nil {
return -1, -1, fmt.Errorf("invalid major version number %s: %v", parts[0], err)
}
if len(parts) == 2 {
minor, err = strconv.Atoi(parts[1])
if err != nil {
return -1, -1, fmt.Errorf("invalid minor version number %s: %v", parts[1], err)
}
} else if len(parts) > 2 {
return -1, -1, fmt.Errorf("invalid major.minor version number %s", vers)
}
return major, minor, nil
}