go_version/version.go

242 lines
6.6 KiB
Go

package version
import (
"encoding/xml"
"errors"
"fmt"
"strconv"
"strings"
)
// Version defines a structure describing the version of a program or module.
type Version struct {
Major uint `json:"major"`
Minor uint `json:"minor"`
Revision uint `json:"revision"`
Build uint `json:"build"`
}
// String converts a Version into its generic string representation
func (version Version) String() string {
return version.SpecificString(0)
}
// SpecificString converts a Version into its string representation with either the specified number of components ([1,4]) or its generic string representation
func (version Version) SpecificString(numberOfComponents int) string {
switch numberOfComponents {
case 4:
return fmt.Sprintf("%d.%d.%d.%d", version.Major, version.Minor, version.Revision, version.Build)
case 3:
return fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.Revision)
case 2:
return fmt.Sprintf("%d.%d", version.Major, version.Minor)
case 1:
return fmt.Sprintf("%d", version.Major)
default:
if 0 < version.Build {
return fmt.Sprintf("%d.%d.%d.%d", version.Major, version.Minor, version.Revision, version.Build)
} else if 0 < version.Revision {
return fmt.Sprintf("%d.%d.%d", version.Major, version.Minor, version.Revision)
} else if 0 < version.Minor {
return fmt.Sprintf("%d.%d", version.Major, version.Minor)
}
return fmt.Sprintf("%d", version.Major)
}
}
// Equal verifies, whether two Versions are the same
func (version Version) Equal(comparand Version) bool {
return version.Major == comparand.Major && version.Minor == comparand.Minor && version.Revision == comparand.Revision && version.Build == comparand.Build
}
// Smaller verifies, whether the Version is smaller than the comparand
func (version Version) Smaller(comparand Version) bool {
if version.Major < comparand.Major {
return true
}
if version.Major == comparand.Major {
if version.Minor < comparand.Minor {
return true
}
if version.Minor == comparand.Minor {
if version.Revision < comparand.Revision {
return true
}
if version.Revision == comparand.Revision {
if version.Build < comparand.Build {
return true
}
}
}
}
return false
}
// Larger verifies, whether the Version is larger than the comparand
func (version Version) Larger(comparand Version) bool {
if version.Equal(comparand) {
return false
}
return !version.Smaller(comparand)
}
func parseError(part, value string) error {
return fmt.Errorf("the '%s' component of the version could not be parsed ('%s')", part, value)
}
// ParseVersion tries to read all available parts of a version string
func ParseVersion(text string) (Version, error) {
result := Version{}
err := result.Parse(text)
return result, err
}
// Parse tries to read all available parts of a version string to the internal variables
func (version *Version) Parse(text string) error {
if nil == version {
return fmt.Errorf("cannot parse to nil")
}
parts := strings.Split(text, ".")
if 0 < len(parts) {
var err error
major, err := strconv.Atoi(strings.Trim(parts[0], " \t\r\n"))
if nil != err || major < 0 {
return parseError("Major", parts[0])
}
version.Major = uint(major)
if 1 < len(parts) {
minor, err := strconv.Atoi(strings.Trim(parts[1], " \t\r\n"))
if nil != err || minor < 0 {
return parseError("Minor", parts[1])
}
version.Minor = uint(minor)
if 2 < len(parts) {
revision, err := strconv.Atoi(strings.Trim(parts[2], " \t\r\n"))
if nil != err || revision < 0 {
return parseError("Revision", parts[2])
}
version.Revision = uint(revision)
if 3 < len(parts) {
build, err := strconv.Atoi(strings.Trim(parts[3], " \t\r\n"))
if nil != err || build < 0 {
return parseError("Build", parts[3])
}
version.Build = uint(build)
}
}
}
}
return nil
}
// MarshalXML satisfies the xml.Marshaler interface
func (version Version) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
if err = e.EncodeToken(start); err == nil {
stringValue := version.String()
if err = e.EncodeToken(xml.CharData([]byte(stringValue))); err == nil {
if err = e.EncodeToken(xml.EndElement{
Name: start.Name,
}); err == nil {
return e.Flush()
}
}
}
return
}
// MarshalJSON satisfies the json.Marshaler interface
func (version Version) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", version.String())), nil
}
// UnmarshalXML satisfies the xml.Unmarshaler interface
func (version *Version) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var (
token xml.Token
err error
)
if nil == version {
return fmt.Errorf("cannot unmarshal to nil")
}
versionAttributeFound := false
if nil != start.Attr {
for _, attribute := range start.Attr {
switch attribute.Name.Local {
case "Major":
major, err := strconv.Atoi(strings.Trim(attribute.Value, " \t\r\n"))
if nil != err || major < 0 {
return parseError("Major", attribute.Value)
}
version.Major = uint(major)
versionAttributeFound = true
case "Minor":
minor, err := strconv.Atoi(strings.Trim(attribute.Value, " \t\r\n"))
if nil != err || minor < 0 {
return parseError("Minor", attribute.Value)
}
version.Minor = uint(minor)
versionAttributeFound = true
case "Revision":
revision, err := strconv.Atoi(strings.Trim(attribute.Value, " \t\r\n"))
if nil != err || revision < 0 {
return parseError("Revision", attribute.Value)
}
version.Revision = uint(revision)
versionAttributeFound = true
case "Build":
build, err := strconv.Atoi(strings.Trim(attribute.Value, " \t\r\n"))
if nil != err || build < 0 {
return parseError("Build", attribute.Value)
}
version.Build = uint(build)
versionAttributeFound = true
}
}
}
Loop:
for {
if token, err = d.Token(); err != nil {
return err
}
if token == nil {
break
}
switch se := token.(type) {
case xml.StartElement:
return errors.New("expected version string (eg. '1.0.0.0')")
case xml.CharData:
if !versionAttributeFound {
stringData := strings.Trim(string(se), " \t\r\n")
if 0 < len(stringData) {
version.Parse(stringData)
}
}
case xml.EndElement:
break Loop
}
}
return nil
}
func unquoteBytes(s []byte) (t []byte, ok bool) {
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
return
}
return s[1 : len(s)-1], true
}
// UnmarshalJSON satisfies the json.Unmarshaler interface
func (version *Version) UnmarshalJSON(data []byte) error {
cleanData, ok := unquoteBytes(data)
if ok {
return version.Parse(string(cleanData))
}
return fmt.Errorf("'%s' cannot be unmarshalled from JSON", string(data))
}