Skip to content

Commit c09cca4

Browse files
committed
refactor: simplify Go version logic with centralized VersionManager
- Extract complex version handling logic into dedicated VersionManager struct - Replace 100+ line judgeVersion function with focused, testable methods - Use consistent semver library parsing for better reliability - Eliminate recursive calls and nested conditions - Improve error handling with proper validation - Add comprehensive version resolution methods for different patterns - Maintain backward compatibility with existing API - Update test expectations to match corrected version resolution - Clean up unused imports and fix compilation warnings
1 parent f67fe71 commit c09cca4

4 files changed

Lines changed: 335 additions & 173 deletions

File tree

gobrew.go

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import (
77
"net/url"
88
"os"
99
"path/filepath"
10-
"regexp"
1110
"sort"
12-
"strconv"
1311
"strings"
1412
"time"
1513

@@ -58,6 +56,7 @@ type GoBrew struct {
5856
currentGoDir string
5957
downloadsDir string
6058
cacheFile string
59+
versionManager *VersionManager
6160
Config
6261
}
6362

@@ -90,6 +89,8 @@ func NewGoBrew(config Config) GoBrew {
9089
cacheFile: cacheFile,
9190
}
9291

92+
gb.versionManager = NewVersionManager(&gb)
93+
9394
if gb.ClearCache {
9495
_ = os.RemoveAll(gb.cacheFile)
9596
}
@@ -100,10 +101,10 @@ func NewGoBrew(config Config) GoBrew {
100101
// Interactive used by default
101102
func (gb *GoBrew) Interactive(ask bool) {
102103
currentVersion := gb.CurrentVersion()
103-
currentMajorVersion := extractMajorVersion(currentVersion)
104+
currentMajorVersion := gb.versionManager.ExtractMajorVersion(currentVersion)
104105

105106
latestVersion := gb.getLatestVersion()
106-
latestMajorVersion := extractMajorVersion(latestVersion)
107+
latestMajorVersion := gb.versionManager.ExtractMajorVersion(latestVersion)
107108

108109
modVersion := NoneVersion
109110
if gb.hasModFile() {
@@ -160,10 +161,18 @@ func (gb *GoBrew) Interactive(ask bool) {
160161
fmt.Println(" Please consider updating your go.mod file")
161162
c := true
162163
if ask {
163-
c = askForConfirmation("🤔 Do you want to use GO version same as go.mod version (" + modVersion + "@latest)?")
164+
versionToUse := modVersion
165+
if strings.Count(modVersion, ".") == 1 {
166+
versionToUse = modVersion + "@latest"
167+
}
168+
c = askForConfirmation("🤔 Do you want to use GO version same as go.mod version (" + versionToUse + ")?")
164169
}
165170
if c {
166-
gb.Use(modVersion + "@latest")
171+
versionToUse := modVersion
172+
if strings.Count(modVersion, ".") == 1 {
173+
versionToUse = modVersion + "@latest"
174+
}
175+
gb.Use(versionToUse)
167176
}
168177
return
169178
}
@@ -223,9 +232,12 @@ func (gb *GoBrew) ListVersions() {
223232
cv := gb.CurrentVersion()
224233

225234
versionsSemantic := make([]*semver.Version, 0)
235+
rcBetaVersions := make([]string, 0)
226236

227237
for _, f := range files {
228-
if v, err := semver.NewVersion(f.Name()); err == nil {
238+
if gb.versionManager.isBetaOrRC(f.Name()) {
239+
rcBetaVersions = append(rcBetaVersions, f.Name())
240+
} else if v, err := semver.NewVersion(f.Name()); err == nil {
229241
versionsSemantic = append(versionsSemantic, v)
230242
}
231243
}
@@ -235,15 +247,9 @@ func (gb *GoBrew) ListVersions() {
235247

236248
for _, versionSemantic := range versionsSemantic {
237249
version := versionSemantic.String()
238-
// 1.8.0 -> 1.8, if version < 1.21.0
239-
reMajorVersion := regexp.MustCompile("([0-9]+).([0-9]+).0")
240-
if len(reMajorVersion.FindStringSubmatch(version)) > 1 {
241-
vv, _ := strconv.Atoi(reMajorVersion.FindStringSubmatch(version)[2])
242-
if vv < 21 {
243-
if reMajorVersion.MatchString(version) {
244-
version = strings.Split(version, ".")[0] + "." + strings.Split(version, ".")[1]
245-
}
246-
}
250+
// For versions < 1.21.0, display as major.minor
251+
if versionSemantic.Major() == 1 && versionSemantic.Minor() < 21 && versionSemantic.Patch() == 0 {
252+
version = fmt.Sprintf("%d.%d", versionSemantic.Major(), versionSemantic.Minor())
247253
}
248254
if version == cv {
249255
version = cv + "*"
@@ -254,17 +260,12 @@ func (gb *GoBrew) ListVersions() {
254260
}
255261

256262
// print rc and beta versions in the end
257-
for _, f := range files {
258-
rcVersion := f.Name()
259-
r := regexp.MustCompile("beta.*|rc.*")
260-
matches := r.FindAllString(rcVersion, -1)
261-
if len(matches) == 1 {
262-
if rcVersion == cv {
263-
rcVersion = cv + "*"
264-
color.Successln(rcVersion)
265-
} else {
266-
color.Infoln(rcVersion)
267-
}
263+
for _, rcVersion := range rcBetaVersions {
264+
if rcVersion == cv {
265+
rcVersion = cv + "*"
266+
color.Successln(rcVersion)
267+
} else {
268+
color.Infoln(rcVersion)
268269
}
269270
}
270271

@@ -321,19 +322,18 @@ func (gb *GoBrew) Install(version string) string {
321322
color.Errorln("[Error] No version provided")
322323
os.Exit(1)
323324
}
324-
// if version has 2 dots, then remove the @latest or @dev-latest
325-
if strings.Count(version, ".") == 2 {
326-
if strings.HasSuffix(version, "@latest") || strings.HasSuffix(version, "@dev-latest") {
327-
version = strings.TrimSuffix(version, "@latest")
328-
version = strings.TrimSuffix(version, "@dev-latest")
329-
}
330-
}
325+
// Use VersionManager to resolve the version
331326
tmpVersion := version
332-
version = gb.judgeVersion(version)
333-
if version == NoneVersion {
327+
resolvedVersion, err := gb.versionManager.ResolveVersion(version)
328+
if err != nil {
329+
color.Errorln("[Error]", err)
330+
os.Exit(1)
331+
}
332+
if resolvedVersion == NoneVersion {
334333
color.Errorln("[Error] Version", tmpVersion, "does not exists")
335334
os.Exit(1)
336335
}
336+
version = resolvedVersion
337337
if gb.existsVersion(version) {
338338
color.Infof("==> [Info] Version: %s exists\n", version)
339339
return version

helpers.go

Lines changed: 9 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"path/filepath"
1313
"regexp"
1414
"runtime"
15-
"slices"
1615
"sort"
1716
"strings"
1817
"time"
@@ -25,29 +24,8 @@ import (
2524
)
2625

2726
func (gb *GoBrew) getLatestVersion() string {
28-
getGolangVersions := gb.getGolangVersions()
29-
30-
// Filter out beta and rc versions and create semantic versions
31-
var validVersions []*semver.Version
32-
for _, version := range getGolangVersions {
33-
r := regexp.MustCompile("beta.*|rc.*")
34-
matches := r.FindAllString(version, -1)
35-
if len(matches) == 0 {
36-
if v, err := semver.NewVersion(version); err == nil {
37-
validVersions = append(validVersions, v)
38-
}
39-
}
40-
}
41-
42-
if len(validVersions) == 0 {
43-
return ""
44-
}
45-
46-
// Sort semantic versions
47-
sort.Sort(semver.Collection(validVersions))
48-
49-
// Return the latest version (last in sorted order)
50-
return validVersions[len(validVersions)-1].String()
27+
// Use VersionManager for better reliability
28+
return gb.versionManager.getLatestStableVersion()
5129
}
5230

5331
func (gb *GoBrew) getArch() string {
@@ -183,120 +161,14 @@ func (gb *GoBrew) cleanDownloadsDir() {
183161
_ = os.RemoveAll(gb.downloadsDir)
184162
}
185163

164+
// judgeVersion is a wrapper around VersionManager.ResolveVersion for backward compatibility
165+
// Deprecated: Use gb.versionManager.ResolveVersion() instead
186166
func (gb *GoBrew) judgeVersion(version string) string {
187-
judgedVersion := NoneVersion
188-
rcBetaOk := false
189-
reRcOrBeta := regexp.MustCompile("beta.*|rc.*")
190-
191-
// check if version string ends with x
192-
if strings.HasSuffix(version, "x") {
193-
judgedVersion = strings.TrimSuffix(version, "x")
194-
}
195-
196-
if strings.HasSuffix(version, ".x") {
197-
judgedVersion = strings.TrimSuffix(version, ".x")
198-
}
199-
if strings.HasSuffix(version, "@latest") {
200-
judgedVersion = strings.TrimSuffix(version, "@latest")
201-
}
202-
if strings.HasSuffix(version, "@dev-latest") {
203-
judgedVersion = strings.TrimSuffix(version, "@dev-latest")
204-
rcBetaOk = true
205-
}
206-
207-
if version == "mod" {
208-
// get version by reading the mod file of Go
209-
modVersion := gb.getModVersion()
210-
// if modVersion is like 1.19, 1.20, 1.21 then appened @latest to it
211-
if strings.Count(modVersion, ".") == 1 {
212-
modVersion += "@latest"
213-
}
214-
return gb.judgeVersion(modVersion)
215-
}
216-
groupedVersions := gb.ListRemoteVersions(false) // donot print
217-
218-
// latest will pick the latest version excluding rc and beta
219-
// dev-latest will first remove rc and beta from the list of versions and then pick the latest version
220-
if version == "latest" || version == "dev-latest" {
221-
groupedVersionKeys := make([]string, 0, len(groupedVersions))
222-
for groupedVersionKey := range groupedVersions {
223-
groupedVersionKeys = append(groupedVersionKeys, groupedVersionKey)
224-
}
225-
versionsSemantic := make([]*semver.Version, 0)
226-
for _, r := range groupedVersionKeys {
227-
if v, err := semver.NewVersion(r); err == nil {
228-
versionsSemantic = append(versionsSemantic, v)
229-
}
230-
}
231-
if len(versionsSemantic) == 0 {
232-
return NoneVersion
233-
}
234-
235-
// sort semantic versions
236-
sort.Sort(semver.Collection(versionsSemantic))
237-
// loop in reverse
238-
for i := len(versionsSemantic) - 1; i >= 0; i-- {
239-
judgedVersions := groupedVersions[versionsSemantic[i].Original()]
240-
// get last element
241-
if version == "dev-latest" {
242-
if len(judgedVersions) == 0 {
243-
return NoneVersion
244-
}
245-
// Filter versions containing "rc" or "beta"
246-
filteredVersions := gb.filterVersions(judgedVersions, []string{"rc", "beta"})
247-
248-
// Get the last element of the filtered slice
249-
var lastVersion string
250-
if len(filteredVersions) > 0 {
251-
lastVersion = filteredVersions[len(filteredVersions)-1]
252-
}
253-
254-
return lastVersion
255-
}
256-
257-
// loop in reverse
258-
for j := len(judgedVersions) - 1; j >= 0; j-- {
259-
matches := reRcOrBeta.FindAllString(judgedVersions[j], -1)
260-
if len(matches) == 0 {
261-
return judgedVersions[j]
262-
}
263-
}
264-
}
265-
266-
latest := versionsSemantic[len(versionsSemantic)-1].String()
267-
return gb.judgeVersion(latest)
268-
}
269-
270-
if judgedVersion != NoneVersion {
271-
// check if judgedVersion is in the groupedVersions
272-
if _, ok := groupedVersions[judgedVersion]; ok {
273-
// get last item in the groupedVersions excluding rc and beta
274-
// loop in reverse groupedVersions
275-
for i := len(groupedVersions[judgedVersion]) - 1; i >= 0; i-- {
276-
matches := reRcOrBeta.FindAllString(groupedVersions[judgedVersion][i], -1)
277-
if len(matches) == 0 {
278-
return groupedVersions[judgedVersion][i]
279-
}
280-
}
281-
if rcBetaOk {
282-
// return last element including beta and rc if present
283-
return groupedVersions[judgedVersion][len(groupedVersions[judgedVersion])-1]
284-
}
285-
}
286-
}
287-
288-
exists := false
289-
for _, value := range groupedVersions {
290-
if slices.Contains(value, version) {
291-
exists = true
292-
break
293-
}
294-
}
295-
if !exists {
167+
resolvedVersion, err := gb.versionManager.ResolveVersion(version)
168+
if err != nil {
296169
return NoneVersion
297170
}
298-
299-
return version
171+
return resolvedVersion
300172
}
301173

302174
func (gb *GoBrew) hasModFile() bool {
@@ -498,6 +370,8 @@ func (gb *GoBrew) extract(srcTar string, dstDir string) error {
498370
return nil
499371
}
500372

373+
// extractMajorVersion is deprecated and replaced by VersionManager.ExtractMajorVersion
374+
// This function is kept for backward compatibility
501375
func extractMajorVersion(version string) string {
502376
parts := strings.Split(version, ".")
503377
if len(parts) < 2 {

helpers_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ func TestJudgeVersion(t *testing.T) {
3636
},
3737
{
3838
version: "1.18@latest",
39-
wantVersion: "1.18.9",
39+
wantVersion: "1.18.10",
4040
},
4141
{
4242
version: "1.18@dev-latest",
43-
wantVersion: "1.18.9",
43+
wantVersion: "1.18.10",
4444
},
4545
{
4646
version: "go1.18",

0 commit comments

Comments
 (0)