From 2223cc357e893b51add75d06a91a2cad8f1d5e34 Mon Sep 17 00:00:00 2001 From: James Evans Date: Tue, 6 May 2025 14:26:44 -0500 Subject: [PATCH 1/3] feat: Add script functions that indicate results with the exit code - `compare` compares two symvers and return 0 if the first is less than the second, 1 if they are equal, and 2 if the first is greater than the second. - `released` Returns 0 if the if the given version is a release version, and 1 if it is not. --- cmd/root.go | 2 + cmd/script.go | 112 +++++++++++++++++++++++++++++++++++++++++++++ cmd/script_test.go | 84 ++++++++++++++++++++++++++++++++++ 3 files changed, 198 insertions(+) create mode 100644 cmd/script.go create mode 100644 cmd/script_test.go diff --git a/cmd/root.go b/cmd/root.go index 27a614d..6290db6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -43,4 +43,6 @@ func init() { // Add the sort subcommand to the root command rootCmd.AddCommand(SortCmd) + + rootCmd.AddCommand(scriptCmd) } diff --git a/cmd/script.go b/cmd/script.go new file mode 100644 index 0000000..9cb4334 --- /dev/null +++ b/cmd/script.go @@ -0,0 +1,112 @@ +/* +Copyright © 2025 James Evans +*/ +package cmd + +import ( + "fmt" + "os" + + "github.com/Masterminds/semver/v3" + "github.com/spf13/cobra" +) + +// scriptCmd represents the script command +var scriptCmd = &cobra.Command{ + Use: "script", + Short: "Script utilities for semantic versioning", + Long: `Provides utilities for scripting with semantic versions. + +These commands are designed to be used in shell scripts, returning exit codes +that can be used in conditionals.`, +} + +// CompareVersions compares two semantic versions and returns: +// -1 if v1 < v2 +// +// 0 if v1 = v2 +// 1 if v1 > v2 +// +// Returns an error if either version is invalid +func CompareVersions(v1string, v2string string) (int, error) { + v1, err := semver.NewVersion(v1string) + if err != nil { + return 0, fmt.Errorf("invalid version: %s", v1string) + } + + v2, err := semver.NewVersion(v2string) + if err != nil { + return 0, fmt.Errorf("invalid version: %s", v2string) + } + + if v1.LessThan(v2) { + return 0, nil + } else if v1.Equal(v2) { + return 1, nil + } else { + return 2, nil + } +} + +// IsReleased checks if a version is a release version (no prerelease or metadata) +// Returns true for release versions, false for prerelease versions or those with metadata +// Returns an error if the version is invalid +func IsReleased(versionString string) (bool, error) { + v, err := semver.NewVersion(versionString) + if err != nil { + return false, fmt.Errorf("invalid version: %s", versionString) + } + + return v.Prerelease() == "" && v.Metadata() == "", nil +} + +// compareCmd represents the compare subcommand +var compareCmd = &cobra.Command{ + Use: "compare ", + Short: "Compare two semantic versions", + Long: `Compare two semantic versions and return an exit code based on the comparison: + + 0: version1 < version2 + 1: version1 = version2 + 2: version1 > version2 + + If there is an error, the command will return 3.`, + Args: cobra.ExactArgs(2), + Run: func(cmd *cobra.Command, args []string) { + result, err := CompareVersions(args[0], args[1]) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(3) + } + os.Exit(result) + }, +} + +// releasedCmd represents the released subcommand +var releasedCmd = &cobra.Command{ + Use: "released ", + Short: "Check if a version is a release version", + Long: `Check if a version is a release version (not a prerelease and has no metadata). + +Returns exit code 0 if the version is a release version (X.Y.Z only), +Returns exit code 1 if the version is a prerelease or has metadata.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + isReleased, err := IsReleased(args[0]) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + + if isReleased { + os.Exit(0) + } else { + os.Exit(1) + } + }, +} + +func init() { + scriptCmd.AddCommand(compareCmd) + scriptCmd.AddCommand(releasedCmd) +} diff --git a/cmd/script_test.go b/cmd/script_test.go new file mode 100644 index 0000000..aa0170f --- /dev/null +++ b/cmd/script_test.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Tests for CompareVersions function +func TestCompareVersionsLessThan(t *testing.T) { + result, err := CompareVersions("1.0.0", "2.0.0") + assert.NoError(t, err) + assert.Equal(t, 0, result) +} + +func TestCompareVersionsEqual(t *testing.T) { + result, err := CompareVersions("1.0.0", "1.0.0") + assert.NoError(t, err) + assert.Equal(t, 1, result) +} + +func TestCompareVersionsGreaterThan(t *testing.T) { + result, err := CompareVersions("2.0.0", "1.0.0") + assert.NoError(t, err) + assert.Equal(t, 2, result) +} + +func TestCompareVersionsFirstInvalid(t *testing.T) { + _, err := CompareVersions("invalid", "1.0.0") + assert.Error(t, err) +} + +func TestCompareVersionsSecondInvalid(t *testing.T) { + _, err := CompareVersions("1.0.0", "invalid") + assert.Error(t, err) +} + +func TestCompareVersionsPatchVersions(t *testing.T) { + result, err := CompareVersions("1.0.1", "1.0.2") + assert.NoError(t, err) + assert.Equal(t, 0, result) +} + +func TestCompareVersionsPrereleaseVsRelease(t *testing.T) { + result, err := CompareVersions("1.0.0-alpha", "1.0.0") + assert.NoError(t, err) + assert.Equal(t, 0, result) +} + +// Tests for IsReleased function +func TestIsReleasedSimpleReleaseVersion(t *testing.T) { + result, err := IsReleased("1.0.0") + assert.NoError(t, err) + assert.True(t, result) +} + +func TestIsReleasedPrereleaseVersion(t *testing.T) { + result, err := IsReleased("1.0.0-alpha.1") + assert.NoError(t, err) + assert.False(t, result) +} + +func TestIsReleasedVersionWithMetadata(t *testing.T) { + result, err := IsReleased("1.0.0+20130313144700") + assert.NoError(t, err) + assert.False(t, result) +} + +func TestIsReleasedPrereleaseWithMetadata(t *testing.T) { + result, err := IsReleased("1.0.0-beta.1+exp.sha.5114f85") + assert.NoError(t, err) + assert.False(t, result) +} + +func TestIsReleasedInvalidVersion(t *testing.T) { + _, err := IsReleased("invalid") + assert.Error(t, err) +} + +func TestIsReleasedComplexVersion(t *testing.T) { + result, err := IsReleased("2.1.0-rc.2") + assert.NoError(t, err) + assert.False(t, result) +} From 897c196be26726dc768e830270a0c3e0d709306f Mon Sep 17 00:00:00 2001 From: jaevans Date: Tue, 6 May 2025 14:28:40 -0500 Subject: [PATCH 2/3] Update cmd/script.go to fix incorrect comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/script.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/script.go b/cmd/script.go index 9cb4334..add7d4a 100644 --- a/cmd/script.go +++ b/cmd/script.go @@ -22,10 +22,9 @@ that can be used in conditionals.`, } // CompareVersions compares two semantic versions and returns: -// -1 if v1 < v2 -// -// 0 if v1 = v2 -// 1 if v1 > v2 +// 0 if v1 < v2 +// 1 if v1 = v2 +// 2 if v1 > v2 // // Returns an error if either version is invalid func CompareVersions(v1string, v2string string) (int, error) { From 5b9d26db06ae9e5def7e63eafea5c00f2d960fe9 Mon Sep 17 00:00:00 2001 From: James Evans Date: Tue, 6 May 2025 14:48:21 -0500 Subject: [PATCH 3/3] Change exit values to match rpm-vercmp. --- .gitignore | 2 ++ cmd/script.go | 23 +++++++++++------------ cmd/script_test.go | 10 +++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 1e8445d..ecbef58 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ tmp/ dist/ semvertool + +coverprofile.out diff --git a/cmd/script.go b/cmd/script.go index add7d4a..25a6235 100644 --- a/cmd/script.go +++ b/cmd/script.go @@ -22,10 +22,9 @@ that can be used in conditionals.`, } // CompareVersions compares two semantic versions and returns: -// 0 if v1 < v2 -// 1 if v1 = v2 -// 2 if v1 > v2 -// +// 0 if versions are equal +// 11 if v1 is greater than v2 (v1 is newer) +// 12 if v2 is greater than v1 (v2 is newer) // Returns an error if either version is invalid func CompareVersions(v1string, v2string string) (int, error) { v1, err := semver.NewVersion(v1string) @@ -39,11 +38,11 @@ func CompareVersions(v1string, v2string string) (int, error) { } if v1.LessThan(v2) { - return 0, nil + return 12, nil // v2 is newer } else if v1.Equal(v2) { - return 1, nil + return 0, nil // equal versions } else { - return 2, nil + return 11, nil // v1 is newer } } @@ -65,17 +64,17 @@ var compareCmd = &cobra.Command{ Short: "Compare two semantic versions", Long: `Compare two semantic versions and return an exit code based on the comparison: - 0: version1 < version2 - 1: version1 = version2 - 2: version1 > version2 + 0: version1 = version2 + 11: version1 > version2 + 12: version1 < version2 - If there is an error, the command will return 3.`, + If there is an error, the command will return 1.`, Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { result, err := CompareVersions(args[0], args[1]) if err != nil { fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(3) + os.Exit(1) } os.Exit(result) }, diff --git a/cmd/script_test.go b/cmd/script_test.go index aa0170f..6cd63ea 100644 --- a/cmd/script_test.go +++ b/cmd/script_test.go @@ -10,19 +10,19 @@ import ( func TestCompareVersionsLessThan(t *testing.T) { result, err := CompareVersions("1.0.0", "2.0.0") assert.NoError(t, err) - assert.Equal(t, 0, result) + assert.Equal(t, 12, result) // v2 is newer } func TestCompareVersionsEqual(t *testing.T) { result, err := CompareVersions("1.0.0", "1.0.0") assert.NoError(t, err) - assert.Equal(t, 1, result) + assert.Equal(t, 0, result) // equal versions } func TestCompareVersionsGreaterThan(t *testing.T) { result, err := CompareVersions("2.0.0", "1.0.0") assert.NoError(t, err) - assert.Equal(t, 2, result) + assert.Equal(t, 11, result) // v1 is newer } func TestCompareVersionsFirstInvalid(t *testing.T) { @@ -38,13 +38,13 @@ func TestCompareVersionsSecondInvalid(t *testing.T) { func TestCompareVersionsPatchVersions(t *testing.T) { result, err := CompareVersions("1.0.1", "1.0.2") assert.NoError(t, err) - assert.Equal(t, 0, result) + assert.Equal(t, 12, result) // v2 is newer } func TestCompareVersionsPrereleaseVsRelease(t *testing.T) { result, err := CompareVersions("1.0.0-alpha", "1.0.0") assert.NoError(t, err) - assert.Equal(t, 0, result) + assert.Equal(t, 12, result) // v2 is newer } // Tests for IsReleased function