Skip to content

Commit 8c36a88

Browse files
committed
feat: supports 'g use' switching to the version specified in go.mod. #163
1 parent 11b0dfd commit 8c36a88

4 files changed

Lines changed: 106 additions & 30 deletions

File tree

cli/commands.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ var (
6363
},
6464
{
6565
Name: "use",
66-
Usage: "Switch to specified version",
66+
Usage: "Switch to specified version. Uses go.mod if available and version is omitted.",
6767
UsageText: "g use <version>",
6868
Action: use,
6969
},

cli/install.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/dixonwille/wlog/v3"
3232
"github.com/dixonwille/wmenu/v5"
3333
"github.com/mholt/archiver/v3"
34+
"github.com/pkg/errors"
3435
"github.com/urfave/cli/v2"
3536
"github.com/voidint/g/collector"
3637
"github.com/voidint/g/version"
@@ -169,13 +170,24 @@ func install(ctx *cli.Context) (err error) {
169170
return nil
170171
}
171172

172-
// Recreate symbolic link.
173+
if err = switchVersion(vname); err != nil {
174+
return cli.Exit(errstring(err), 1)
175+
}
176+
return nil
177+
}
178+
179+
func switchVersion(vname string) error {
180+
targetV := filepath.Join(versionsDir, vname)
181+
182+
// Recreate symbolic link
173183
_ = os.Remove(goroot)
174184

175-
if err = mkSymlink(targetV, goroot); err != nil {
176-
return cli.Exit(errstring(err), 1)
185+
if err := mkSymlink(targetV, goroot); err != nil {
186+
return errors.WithStack(err)
187+
}
188+
if output, err := exec.Command(filepath.Join(goroot, "bin", "go"), "version").Output(); err == nil {
189+
fmt.Printf("Now using %s", strings.TrimPrefix(string(output), "go version "))
177190
}
178-
fmt.Printf("Now using go%s\n", v.Name())
179191
return nil
180192
}
181193

@@ -186,5 +198,8 @@ func mkSymlink(oldname, newname string) (err error) {
186198
return nil
187199
}
188200
}
189-
return os.Symlink(oldname, newname)
201+
if err = os.Symlink(oldname, newname); err != nil {
202+
return errors.WithStack(err)
203+
}
204+
return nil
190205
}

cli/ls.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,36 @@ import (
2525
"sort"
2626

2727
"github.com/k0kubun/go-ansi"
28+
"github.com/pkg/errors"
2829
"github.com/urfave/cli/v2"
2930
"github.com/voidint/g/version"
3031
)
3132

3233
func list(ctx *cli.Context) (err error) {
33-
dirs, err := os.ReadDir(versionsDir)
34-
if err != nil || len(dirs) <= 0 {
34+
items, err := listLocalVersions(versionsDir)
35+
if err != nil || len(items) <= 0 {
3536
fmt.Printf("No version installed yet\n\n")
3637
return nil
3738
}
39+
40+
var renderMode uint8
41+
switch ctx.String("output") {
42+
case "json":
43+
renderMode = jsonMode
44+
default:
45+
renderMode = textMode
46+
}
47+
48+
render(renderMode, installed(), items, ansi.NewAnsiStdout())
49+
return nil
50+
}
51+
52+
// listLocalVersions List the versions in the specified directory in ascending order
53+
func listLocalVersions(dirPath string) ([]*version.Version, error) {
54+
dirs, err := os.ReadDir(dirPath)
55+
if err != nil {
56+
return nil, errors.WithStack(err)
57+
}
3858
items := make([]*version.Version, 0, len(dirs))
3959
for _, d := range dirs {
4060
if !d.IsDir() {
@@ -46,17 +66,7 @@ func list(ctx *cli.Context) (err error) {
4666
continue
4767
}
4868
items = append(items, v)
49-
sort.Sort(version.Collection(items))
50-
}
51-
52-
var renderMode uint8
53-
switch ctx.String("output") {
54-
case "json":
55-
renderMode = jsonMode
56-
default:
57-
renderMode = textMode
5869
}
59-
60-
render(renderMode, installed(), items, ansi.NewAnsiStdout())
61-
return nil
70+
sort.Sort(version.Collection(items)) // asc order
71+
return items, nil
6272
}

cli/use.go

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,81 @@ package cli
2222
import (
2323
"fmt"
2424
"os"
25-
"os/exec"
2625
"path/filepath"
26+
"regexp"
2727

28+
"github.com/Masterminds/semver/v3"
29+
"github.com/pkg/errors"
2830
"github.com/urfave/cli/v2"
2931
)
3032

3133
func use(ctx *cli.Context) error {
3234
vname := ctx.Args().First()
3335
if vname == "" {
34-
return cli.ShowSubcommandHelp(ctx)
36+
// Uses go.mod if available and version is omitted
37+
goModData, err := os.ReadFile("go.mod")
38+
if err != nil {
39+
if errors.Is(err, os.ErrNotExist) {
40+
return cli.Exit(wrapstring("No go.mod file found"), 1)
41+
}
42+
return cli.Exit(errstring(err), 1)
43+
}
44+
45+
goDirective := getGoDirective(goModData)
46+
wd, err := os.Getwd()
47+
if err != nil {
48+
wd = "."
49+
}
50+
if goDirective == "" {
51+
return cli.Exit(wrapstring(fmt.Sprintf("Go directive does not exist in %q files", filepath.Join(wd, "go.mod"))), 1)
52+
}
53+
fmt.Printf("Found %q with version <%s>\n", filepath.Join(wd, "go.mod"), goDirective)
54+
vname = goDirective
3555
}
36-
targetV := filepath.Join(versionsDir, vname)
3756

38-
if finfo, err := os.Stat(targetV); err != nil || !finfo.IsDir() {
39-
return cli.Exit(fmt.Sprintf("[g] The %q version does not exist, please install it first.", vname), 1)
57+
versions, err := listLocalVersions(versionsDir)
58+
if err != nil {
59+
return cli.Exit(errstring(err), 1)
4060
}
4161

42-
_ = os.Remove(goroot)
62+
// Try to match the version number strictly first
63+
for i := range versions {
64+
if versions[i].Name() != vname {
65+
continue
66+
}
67+
if err = switchVersion(versions[i].Name()); err != nil {
68+
return cli.Exit(errstring(err), 1)
69+
}
70+
return nil
71+
}
4372

44-
if err := mkSymlink(targetV, goroot); err != nil {
73+
// Try fuzzy matching the version number again
74+
cs, err := semver.NewConstraint(vname)
75+
if err != nil {
4576
return cli.Exit(errstring(err), 1)
4677
}
47-
if output, err := exec.Command(filepath.Join(goroot, "bin", "go"), "version").Output(); err == nil {
48-
fmt.Print(string(output))
78+
79+
for j := len(versions) - 1; j >= 0; j-- {
80+
if !versions[j].MatchConstraint(cs) {
81+
continue
82+
}
83+
if err = switchVersion(versions[j].Name()); err != nil {
84+
return cli.Exit(errstring(err), 1)
85+
}
86+
return nil
87+
}
88+
89+
return cli.Exit(wrapstring(fmt.Sprintf("The %q version does not exist, please install it first.", vname)), 1)
90+
}
91+
92+
var goDirectiveReg = regexp.MustCompile(`(?m)^go\s+(\d+\.\d+(?:\.\d+)?(?:beta\d+|rc\d+)?)\s*(?:$|//.*)`)
93+
94+
// getGoDirective Extract the go directive from the go.mod file.
95+
func getGoDirective(goModData []byte) string {
96+
// https://go.dev/ref/mod#go-mod-file-go
97+
match := goDirectiveReg.FindStringSubmatch(string(goModData))
98+
if len(match) > 1 {
99+
return match[1]
49100
}
50-
return nil
101+
return ""
51102
}

0 commit comments

Comments
 (0)