Skip to content

Commit b64e1a4

Browse files
authored
Merge pull request #8 from jaevans/add-script-functions
feat: Add script functions that indicate results with the exit code
2 parents 23e0c7c + 5b9d26d commit b64e1a4

4 files changed

Lines changed: 198 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ tmp/
1313
dist/
1414

1515
semvertool
16+
17+
coverprofile.out

cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,6 @@ func init() {
4343

4444
// Add the sort subcommand to the root command
4545
rootCmd.AddCommand(SortCmd)
46+
47+
rootCmd.AddCommand(scriptCmd)
4648
}

cmd/script.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
Copyright © 2025 James Evans
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"os"
9+
10+
"github.com/Masterminds/semver/v3"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
// scriptCmd represents the script command
15+
var scriptCmd = &cobra.Command{
16+
Use: "script",
17+
Short: "Script utilities for semantic versioning",
18+
Long: `Provides utilities for scripting with semantic versions.
19+
20+
These commands are designed to be used in shell scripts, returning exit codes
21+
that can be used in conditionals.`,
22+
}
23+
24+
// CompareVersions compares two semantic versions and returns:
25+
// 0 if versions are equal
26+
// 11 if v1 is greater than v2 (v1 is newer)
27+
// 12 if v2 is greater than v1 (v2 is newer)
28+
// Returns an error if either version is invalid
29+
func CompareVersions(v1string, v2string string) (int, error) {
30+
v1, err := semver.NewVersion(v1string)
31+
if err != nil {
32+
return 0, fmt.Errorf("invalid version: %s", v1string)
33+
}
34+
35+
v2, err := semver.NewVersion(v2string)
36+
if err != nil {
37+
return 0, fmt.Errorf("invalid version: %s", v2string)
38+
}
39+
40+
if v1.LessThan(v2) {
41+
return 12, nil // v2 is newer
42+
} else if v1.Equal(v2) {
43+
return 0, nil // equal versions
44+
} else {
45+
return 11, nil // v1 is newer
46+
}
47+
}
48+
49+
// IsReleased checks if a version is a release version (no prerelease or metadata)
50+
// Returns true for release versions, false for prerelease versions or those with metadata
51+
// Returns an error if the version is invalid
52+
func IsReleased(versionString string) (bool, error) {
53+
v, err := semver.NewVersion(versionString)
54+
if err != nil {
55+
return false, fmt.Errorf("invalid version: %s", versionString)
56+
}
57+
58+
return v.Prerelease() == "" && v.Metadata() == "", nil
59+
}
60+
61+
// compareCmd represents the compare subcommand
62+
var compareCmd = &cobra.Command{
63+
Use: "compare <version1> <version2>",
64+
Short: "Compare two semantic versions",
65+
Long: `Compare two semantic versions and return an exit code based on the comparison:
66+
67+
0: version1 = version2
68+
11: version1 > version2
69+
12: version1 < version2
70+
71+
If there is an error, the command will return 1.`,
72+
Args: cobra.ExactArgs(2),
73+
Run: func(cmd *cobra.Command, args []string) {
74+
result, err := CompareVersions(args[0], args[1])
75+
if err != nil {
76+
fmt.Fprintf(os.Stderr, "%s\n", err)
77+
os.Exit(1)
78+
}
79+
os.Exit(result)
80+
},
81+
}
82+
83+
// releasedCmd represents the released subcommand
84+
var releasedCmd = &cobra.Command{
85+
Use: "released <version>",
86+
Short: "Check if a version is a release version",
87+
Long: `Check if a version is a release version (not a prerelease and has no metadata).
88+
89+
Returns exit code 0 if the version is a release version (X.Y.Z only),
90+
Returns exit code 1 if the version is a prerelease or has metadata.`,
91+
Args: cobra.ExactArgs(1),
92+
Run: func(cmd *cobra.Command, args []string) {
93+
isReleased, err := IsReleased(args[0])
94+
if err != nil {
95+
fmt.Fprintf(os.Stderr, "%s\n", err)
96+
os.Exit(1)
97+
}
98+
99+
if isReleased {
100+
os.Exit(0)
101+
} else {
102+
os.Exit(1)
103+
}
104+
},
105+
}
106+
107+
func init() {
108+
scriptCmd.AddCommand(compareCmd)
109+
scriptCmd.AddCommand(releasedCmd)
110+
}

cmd/script_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
// Tests for CompareVersions function
10+
func TestCompareVersionsLessThan(t *testing.T) {
11+
result, err := CompareVersions("1.0.0", "2.0.0")
12+
assert.NoError(t, err)
13+
assert.Equal(t, 12, result) // v2 is newer
14+
}
15+
16+
func TestCompareVersionsEqual(t *testing.T) {
17+
result, err := CompareVersions("1.0.0", "1.0.0")
18+
assert.NoError(t, err)
19+
assert.Equal(t, 0, result) // equal versions
20+
}
21+
22+
func TestCompareVersionsGreaterThan(t *testing.T) {
23+
result, err := CompareVersions("2.0.0", "1.0.0")
24+
assert.NoError(t, err)
25+
assert.Equal(t, 11, result) // v1 is newer
26+
}
27+
28+
func TestCompareVersionsFirstInvalid(t *testing.T) {
29+
_, err := CompareVersions("invalid", "1.0.0")
30+
assert.Error(t, err)
31+
}
32+
33+
func TestCompareVersionsSecondInvalid(t *testing.T) {
34+
_, err := CompareVersions("1.0.0", "invalid")
35+
assert.Error(t, err)
36+
}
37+
38+
func TestCompareVersionsPatchVersions(t *testing.T) {
39+
result, err := CompareVersions("1.0.1", "1.0.2")
40+
assert.NoError(t, err)
41+
assert.Equal(t, 12, result) // v2 is newer
42+
}
43+
44+
func TestCompareVersionsPrereleaseVsRelease(t *testing.T) {
45+
result, err := CompareVersions("1.0.0-alpha", "1.0.0")
46+
assert.NoError(t, err)
47+
assert.Equal(t, 12, result) // v2 is newer
48+
}
49+
50+
// Tests for IsReleased function
51+
func TestIsReleasedSimpleReleaseVersion(t *testing.T) {
52+
result, err := IsReleased("1.0.0")
53+
assert.NoError(t, err)
54+
assert.True(t, result)
55+
}
56+
57+
func TestIsReleasedPrereleaseVersion(t *testing.T) {
58+
result, err := IsReleased("1.0.0-alpha.1")
59+
assert.NoError(t, err)
60+
assert.False(t, result)
61+
}
62+
63+
func TestIsReleasedVersionWithMetadata(t *testing.T) {
64+
result, err := IsReleased("1.0.0+20130313144700")
65+
assert.NoError(t, err)
66+
assert.False(t, result)
67+
}
68+
69+
func TestIsReleasedPrereleaseWithMetadata(t *testing.T) {
70+
result, err := IsReleased("1.0.0-beta.1+exp.sha.5114f85")
71+
assert.NoError(t, err)
72+
assert.False(t, result)
73+
}
74+
75+
func TestIsReleasedInvalidVersion(t *testing.T) {
76+
_, err := IsReleased("invalid")
77+
assert.Error(t, err)
78+
}
79+
80+
func TestIsReleasedComplexVersion(t *testing.T) {
81+
result, err := IsReleased("2.1.0-rc.2")
82+
assert.NoError(t, err)
83+
assert.False(t, result)
84+
}

0 commit comments

Comments
 (0)