diff --git a/cmd/ls.go b/cmd/ls.go index 884aac4..5ab75a2 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -20,19 +20,26 @@ type lsCmd struct { type lsOpts struct { remote bool + num int } func newLs(lsOpts lsOpts) error { if lsOpts.remote { client := gh.NewClient(os.Getenv("GITHUB_PAT")) - releases, err := gh.GetReleases(client, false) + releases, err := gh.GetReleases(client, lsOpts.num) if err != nil { return err } - fmt.Println("version | release date | description") + fmt.Println("version | release date | description | type") for _, r := range releases { createdAt := r.GetCreatedAt() - fmt.Printf("%s | %s | %s\n", r.GetTagName(), createdAt.Format("2006-01-02"), r.GetName()) + var releaseType string + if r.GetPrerelease() { + releaseType = "pre-release" + } else { + releaseType = "release" + } + fmt.Printf("%s | %s | %s | %s \n", r.GetTagName(), createdAt.Format("2006-01-02"), r.GetName(), releaseType) } } else { entries, err := os.ReadDir(config.GetPathToVersionsDir()) @@ -43,6 +50,10 @@ func newLs(lsOpts lsOpts) error { if err != nil { return err } + if len(entries) < lsOpts.num { + lsOpts.num = len(entries) + } + entries = entries[:lsOpts.num-1] // TODO: replace with actual table fmt.Println("version | install time") fmt.Println("--------------------------------") @@ -78,6 +89,7 @@ func newLs(lsOpts lsOpts) error { func setLsOpts(lsOpts *lsOpts) { lsOpts.remote = viper.GetBool("remote") + lsOpts.num = viper.GetInt("number") } func (opts *lsOpts) Validate() error { @@ -108,6 +120,8 @@ func newLsCmd() *lsCmd { } cmd.Flags().Bool("remote", false, "list remote versions") viper.BindPFlag("remote", cmd.Flags().Lookup("remote")) + cmd.Flags().IntP("number", "n", 10, "number of versions to list") + viper.BindPFlag("number", cmd.Flags().Lookup("number")) root.cmd = cmd return root } diff --git a/cmd/use.go b/cmd/use.go index 0095955..9650171 100644 --- a/cmd/use.go +++ b/cmd/use.go @@ -7,11 +7,15 @@ import ( "path/filepath" "runtime" "sort" + "strings" "github.com/AlecAivazis/survey/v2" + "github.com/coreos/go-semver/semver" "github.com/dpastoor/qvm/internal/config" + "github.com/dpastoor/qvm/internal/gh" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" "golang.org/x/exp/maps" ) @@ -21,6 +25,7 @@ type useCmd struct { } type useOpts struct { + install bool } func newUse(useOpts useOpts, version string) error { @@ -29,11 +34,50 @@ func newUse(useOpts useOpts, version string) error { return err } versions := maps.Keys(iv) - sort.Sort(sort.Reverse(sort.StringSlice(versions))) + var semVersions semver.Versions + // sorting has some issues given how the character values will present individually + // for example a high value double digit patch version will be sorted before a lower + // value triple digit patch version. For example, sorting shows ordering like: + // v1.2.89 v1.2.237 v1.2.112 v1.1.84 v1.1.251 v1.1.189 + // where .89 is > 237 + // using go-semver this works + // as will get 1.2.237 1.2.112 1.2.89 1.1.251 1.1.189 1.1.168 1.1.84 + for _, v := range versions { + ver, err := semver.NewVersion(strings.TrimPrefix(v, "v")) + if err != nil { + // we're just going to warn rather than error right now in case some + // releases end up not following semver and would rather the tool not blow up + log.Errorf("could not parse semver value for %s with err %s\n ", v, err) + continue + } + semVersions = append(semVersions, ver) + } + sort.Sort(sort.Reverse(semVersions)) + // note this could be a bug if ever we do get nonparseable versions thrown out above + // will cross that bridge if we get there + for i, v := range semVersions { + versions[i] = "v" + v.String() + } + // convert back to string for later options if len(iv) == 0 { return errors.New("no installed versions found, please install a version first") } + if version == "release" { + client := gh.NewClient(os.Getenv("GITHUB_PAT")) + latestRelease, err := gh.GetLatestRelease(client) + if err != nil { + return err + } + version = latestRelease.GetTagName() + } if version == "latest" { + if useOpts.install { + err = newInstall(installOpts{progress: true}, "latest") + if err != nil { + return err + } + } + // add back the v we trimmed for semver version = versions[0] } if version == "" { @@ -82,7 +126,7 @@ func newUse(useOpts useOpts, version string) error { } func setUseOpts(useOpts *useOpts) { - + useOpts.install = viper.GetBool("install") } func (opts *useOpts) Validate() error { @@ -115,6 +159,8 @@ func newUseCmd() *useCmd { return nil }, } + cmd.Flags().Bool("install", false, "install the version if not already installed") + viper.BindPFlag("install", cmd.Flags().Lookup("install")) root.cmd = cmd return root } diff --git a/go.mod b/go.mod index 9a99be9..ffbbe18 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/AlecAivazis/survey/v2 v2.3.5-0.20220530090844-e47352f91434 github.com/adrg/xdg v0.4.0 + github.com/coreos/go-semver v0.3.0 github.com/dustin/go-humanize v1.0.0 github.com/google/go-github/v44 v44.1.0 github.com/mholt/archiver/v4 v4.0.0-alpha.6.0.20220421032531-8a97d87612e9 diff --git a/go.sum b/go.sum index e1475d4..103338f 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI= github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=