From fbf653e97e2505c67b5580cce87eaca8aacd5347 Mon Sep 17 00:00:00 2001 From: Devin Pastoor Date: Sun, 23 Oct 2022 18:00:21 -0400 Subject: [PATCH 1/3] feat: add release + latest options now that multiple quarto streams --- cmd/install.go | 17 +++++++++++++++-- internal/gh/releases.go | 20 +++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/cmd/install.go b/cmd/install.go index e141cee..a7c0187 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -32,7 +32,7 @@ func newInstall(installOpts installOpts, release string) error { } if release == "" { client := gh.NewClient(os.Getenv("GITHUB_PAT")) - releases, err := gh.GetReleases(client, false) + releases, err := gh.GetReleases(client, 100) if err != nil { return err } @@ -55,7 +55,10 @@ func newInstall(installOpts installOpts, release string) error { return err } } - if release == "latest" { + + // github's latest release is literally their latest release, + // not the latest tagged version + if release == "release" { client := gh.NewClient(os.Getenv("GITHUB_PAT")) latestRelease, err := gh.GetLatestRelease(client) if err != nil { @@ -63,6 +66,16 @@ func newInstall(installOpts installOpts, release string) error { } release = latestRelease.GetTagName() } + + if release == "latest" { + client := gh.NewClient(os.Getenv("GITHUB_PAT")) + releases, err := gh.GetReleases(client, 1) + if err != nil { + return err + } + release = releases[0].GetTagName() + } + _, ok := iv[release] if ok { log.Infof("quarto version %s is already installed\n", release) diff --git a/internal/gh/releases.go b/internal/gh/releases.go index d3283c5..2551c9c 100644 --- a/internal/gh/releases.go +++ b/internal/gh/releases.go @@ -32,11 +32,19 @@ func GetLatestRelease(client *github.Client) (*github.RepositoryRelease, error) return rel, err } -func GetReleases(client *github.Client, paginate bool) ([]*github.RepositoryRelease, error) { - opts := &github.ListOptions{PerPage: 50} +func GetReleases(client *github.Client, n int) ([]*github.RepositoryRelease, error) { + // max of 50 per page + perPage := 50 + remaining := n - perPage + if n < 50 { + remaining = 0 + perPage = n + } var releases []*github.RepositoryRelease + opts := &github.ListOptions{PerPage: perPage} for { start := time.Now() + log.Tracef("perpage: %d, remaining: %d", opts.PerPage, remaining) rel, resp, err := client.Repositories.ListReleases( context.Background(), "quarto-dev", @@ -48,9 +56,15 @@ func GetReleases(client *github.Client, paginate bool) ([]*github.RepositoryRele } releases = append(releases, rel...) log.Tracef("repository release paginator: %s, page: %d", time.Since(start), resp.NextPage) - if !paginate || resp.NextPage == 0 { + if remaining <= 0 || resp.NextPage == 0 { break } + if remaining <= perPage { + opts.PerPage = remaining + remaining = 0 + } else { + remaining -= perPage + } opts.Page = resp.NextPage } return releases, nil From 22c20ac00aeba269d5f946e73ec2499f45d2fc0e Mon Sep 17 00:00:00 2001 From: Devin Pastoor Date: Sun, 23 Oct 2022 18:00:54 -0400 Subject: [PATCH 2/3] feat: improve ls and use to normalize latest/release and enable --install flag --- cmd/ls.go | 20 +++++++++++++++++--- cmd/use.go | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- go.mod | 1 + go.sum | 2 ++ 4 files changed, 68 insertions(+), 5 deletions(-) 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= From 2b16399bed413004e8fecafc68e7c04a3de7fbb0 Mon Sep 17 00:00:00 2001 From: Devin Pastoor Date: Wed, 26 Oct 2022 20:58:56 -0400 Subject: [PATCH 3/3] fixup: use and install interactions cleaned up now makes sure to actually use the installed version when using key words like latest. Previously would install the latest, but then still end up using the previously installed latest version. loosen the need to definitely have an installed version present for use, since can use --install to get a version --- cmd/install.go | 20 ++++++++++---------- cmd/use.go | 20 +++++++++++++++----- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/cmd/install.go b/cmd/install.go index a7c0187..7b0ad47 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -25,16 +25,16 @@ type installOpts struct { progress bool } -func newInstall(installOpts installOpts, release string) error { +func newInstall(installOpts installOpts, release string) (error, string) { iv, err := config.GetInstalledVersions() if err != nil && !errors.Is(err, os.ErrNotExist) { - return err + return err, "" } if release == "" { client := gh.NewClient(os.Getenv("GITHUB_PAT")) releases, err := gh.GetReleases(client, 100) if err != nil { - return err + return err, "" } versions := []string{} for _, r := range releases { @@ -52,7 +52,7 @@ func newInstall(installOpts installOpts, release string) error { }, }, &release, survey.WithPageSize(10)) if err != nil { - return err + return err, "" } } @@ -62,7 +62,7 @@ func newInstall(installOpts installOpts, release string) error { client := gh.NewClient(os.Getenv("GITHUB_PAT")) latestRelease, err := gh.GetLatestRelease(client) if err != nil { - return err + return err, "" } release = latestRelease.GetTagName() } @@ -71,7 +71,7 @@ func newInstall(installOpts installOpts, release string) error { client := gh.NewClient(os.Getenv("GITHUB_PAT")) releases, err := gh.GetReleases(client, 1) if err != nil { - return err + return err, "" } release = releases[0].GetTagName() } @@ -79,16 +79,16 @@ func newInstall(installOpts installOpts, release string) error { _, ok := iv[release] if ok { log.Infof("quarto version %s is already installed\n", release) - return nil + return nil, release } log.Info("attempting to install quarto version: ", release) res, err := pipeline.DownloadReleaseVersion(release, runtime.GOOS, installOpts.progress) if err != nil { - return err + return err, "" } log.Infof("new quarto version %s installed\n", release) log.Debugf("new quarto version installed to %s\n", res) - return nil + return nil, release } func setInstallOpts(installOpts *installOpts) { @@ -125,7 +125,7 @@ func newInstallCmd() *installCmd { wg.Add(1) go func(errc <-chan error, release string) { defer wg.Done() - err := newInstall(root.opts, release) + err, _ := newInstall(root.opts, release) errChan <- err }(errChan, arg) } diff --git a/cmd/use.go b/cmd/use.go index 9650171..452f138 100644 --- a/cmd/use.go +++ b/cmd/use.go @@ -59,11 +59,11 @@ func newUse(useOpts useOpts, version string) error { versions[i] = "v" + v.String() } // convert back to string for later options - if len(iv) == 0 { + if len(iv) == 0 && !useOpts.install { return errors.New("no installed versions found, please install a version first") } + client := gh.NewClient(os.Getenv("GITHUB_PAT")) if version == "release" { - client := gh.NewClient(os.Getenv("GITHUB_PAT")) latestRelease, err := gh.GetLatestRelease(client) if err != nil { return err @@ -72,13 +72,16 @@ func newUse(useOpts useOpts, version string) error { } if version == "latest" { if useOpts.install { - err = newInstall(installOpts{progress: true}, "latest") + // this will install further down if the version isn't already installed + repo, err := gh.GetReleases(client, 1) if err != nil { return err } + version = repo[0].GetTagName() + } else { + version = versions[0] } // add back the v we trimmed for semver - version = versions[0] } if version == "" { // not worried about an error here as an active version of @@ -100,7 +103,14 @@ func newUse(useOpts useOpts, version string) error { } quartopath, ok := iv[version] if !ok { - return fmt.Errorf("version %s not found", version) + if useOpts.install { + err, version = newInstall(installOpts{progress: true}, version) + if err != nil { + return err + } + } else { + return fmt.Errorf("version %s not found", version) + } } err = os.MkdirAll(config.GetPathToActiveBinDir(), 0755) if err != nil {