diff --git a/.gitignore b/.gitignore index 9a3a8d8..f462b89 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# ---> Go # Binaries for programs and plugins +gotest *.exe *.exe~ *.dll diff --git a/main.go b/main.go index a4227ee..84f4225 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,14 @@ package main -import "fmt" +import ( + "fmt" + + "git.bloy.org/mike/gotest/version" +) func main() { fmt.Println(sayHi("Mike")) + fmt.Printf("This is %s\n", version.VersionStr) } func sayHi(name string) string { diff --git a/version/data.go b/version/data.go index c945276..fe10444 100644 --- a/version/data.go +++ b/version/data.go @@ -8,11 +8,14 @@ import ( // gitDescribe : Git's "git describe --always --dirty" output var gitDescribe = "" -// BuildDate : Date of this build in YYYY-MM-DD format -var BuildDate = "" - // GoVersion : The go version used to compile this binary var GoVersion = runtime.Version() // OsArch : The OS and archetcture used for this binary var OsArch = fmt.Sprintf("%s (%s)", runtime.GOOS, runtime.GOARCH) + +// VersionData : The Info struct of the version specification +var VersionData = parseGitDescribe(gitDescribe) + +// VersionStr : The string representation of the version +var VersionStr = VersionData.String() diff --git a/version/data_test.go b/version/data_test.go index 854010f..6ed1781 100644 --- a/version/data_test.go +++ b/version/data_test.go @@ -1,7 +1,9 @@ package version -import "testing" -import "runtime" +import ( + "runtime" + "testing" +) func TestGoVersion(t *testing.T) { got := GoVersion diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..17f635f --- /dev/null +++ b/version/version.go @@ -0,0 +1,105 @@ +package version + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// Info : data structure for program version information +type Info struct { + Major int + Minor int + Patch int + Prerelease []string + BuildInfo []string +} + +// Equal compares two Info structs for equality +func (t Info) Equal(o Info) bool { + if t.Major != o.Major || t.Minor != o.Minor || t.Patch != o.Patch { + return false + } + if len(t.Prerelease) != len(o.Prerelease) || len(t.BuildInfo) != len(o.BuildInfo) { + return false + } + for i := 0; i < len(t.Prerelease); i++ { + if t.Prerelease[i] != o.Prerelease[i] { + return false + } + } + for i := 0; i < len(t.BuildInfo); i++ { + if t.BuildInfo[i] != o.BuildInfo[i] { + return false + } + } + return true +} + +// String returns a human friendly version string +func (t Info) String() string { + var b strings.Builder + fmt.Fprintf(&b, "v%d.%d.%d", t.Major, t.Minor, t.Patch) + if len(t.Prerelease) > 0 { + fmt.Fprintf(&b, "-%s", strings.Join(t.Prerelease, ".")) + } + if len(t.BuildInfo) > 0 { + fmt.Fprintf(&b, "+%s", strings.Join(t.BuildInfo, ".")) + } + return b.String() +} + +/* +Match groups: + +1. version number (without "v") +2. major version +3. minor version +4. patch version +5. prerelease identifier +6. commit count +7. git short hash +8. "-dirty" if dirty, else "" +*/ +const reStr = `^(?:v(([0-9]+)(?:\.([0-9]+)(?:\.([0-9]+))?)?))?(?:-((?:alpha|beta|rc|dev)(?:\.[a-zA-Z0-9]+)*))?(?:-([0-9]+)-g)?([a-f0-9A-F]+)?(?:(-dirty))?$` + +var re = regexp.MustCompile(reStr) + +func parseGitDescribe(input string) Info { + matches := re.FindStringSubmatch(input) + if matches == nil || matches[0] == "" { + return Info{0, 0, 0, []string{"dev"}, []string{"unknown"}} + } + + info := Info{} + if matches[2] != "" { + num, _ := strconv.Atoi(matches[2]) + info.Major = num + } + if matches[3] != "" { + num, _ := strconv.Atoi(matches[3]) + info.Minor = num + } + if matches[4] != "" { + num, _ := strconv.Atoi(matches[4]) + info.Patch = num + } + if matches[5] != "" { + info.Prerelease = strings.Split(matches[5], ".") + } + if matches[6] != "" || matches[7] != "" || matches[8] != "" { + info.BuildInfo = append(info.BuildInfo, "git") + if matches[6] != "" { + info.BuildInfo = append(info.BuildInfo, matches[6]) + } + if matches[7] != "" { + info.BuildInfo = append(info.BuildInfo, matches[7]) + } + if matches[8] != "" { + info.BuildInfo = append(info.BuildInfo, "dirty") + } + } + + return info +} diff --git a/version/version_test.go b/version/version_test.go new file mode 100644 index 0000000..ec72d93 --- /dev/null +++ b/version/version_test.go @@ -0,0 +1,60 @@ +package version + +import ( + "fmt" + "testing" +) + +func TestParseGitDescribe(t *testing.T) { + expected := []Info{ + Info{0, 0, 0, []string{"dev"}, []string{"unknown"}}, + Info{0, 0, 0, []string{}, []string{"git", "feedbeef"}}, + Info{1, 2, 0, []string{"dev", "1"}, []string{"git", "83", "feedbeef"}}, + Info{1, 2, 3, []string{"dev", "1"}, []string{"git", "83", "feedbeef", "dirty"}}, + Info{1, 2, 3, []string{}, []string{"git", "dirty"}}, + Info{1, 2, 3, []string{"beta"}, []string{"git", "dirty"}}, + Info{1, 2, 3, []string{"rc"}, []string{}}, + } + inputs := []string{ + "", + "feedbeef", + "v1.2-dev.1-83-gfeedbeef", + "v1.2.3-dev.1-83-gfeedbeef-dirty", + "v1.2.3-dirty", + "v1.2.3-beta-dirty", + "v1.2.3-rc", + } + for i := 0; i < len(expected); i++ { + got := parseGitDescribe(inputs[i]) + if !expected[i].Equal(got) { + t.Errorf("Got: %v, Expected: %v", got, expected[i]) + } + } +} + +func TestInfoStringer(t *testing.T) { + inputs := []Info{ + Info{0, 0, 0, []string{"dev"}, []string{"unknown"}}, + Info{0, 0, 0, []string{}, []string{"git", "feedbeef"}}, + Info{1, 2, 0, []string{"dev", "1"}, []string{"git", "83", "feedbeef"}}, + Info{1, 2, 3, []string{"dev", "1"}, []string{"git", "83", "feedbeef", "dirty"}}, + Info{1, 2, 3, []string{}, []string{"git", "dirty"}}, + Info{1, 2, 3, []string{"beta"}, []string{"git", "dirty"}}, + Info{1, 2, 3, []string{"rc"}, []string{}}, + } + expected := []string{ + "v0.0.0-dev+unknown", + "v0.0.0+git.feedbeef", + "v1.2.0-dev.1+git.83.feedbeef", + "v1.2.3-dev.1+git.83.feedbeef.dirty", + "v1.2.3+git.dirty", + "v1.2.3-beta+git.dirty", + "v1.2.3-rc", + } + for i := 0; i < len(expected); i++ { + got := fmt.Sprint(inputs[i]) + if got != expected[i] { + t.Errorf("Got: %v, Expected: %v", got, expected[i]) + } + } +}