From 3355e6915fcdc7c8a3d5277a40661e892ab623e8 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Thu, 28 May 2020 11:13:21 -0500 Subject: [PATCH 01/22] add gh repo garden --- api/client.go | 37 ++++ command/repo_garden.go | 421 +++++++++++++++++++++++++++++++++++++++++ utils/color.go | 5 + 3 files changed, 463 insertions(+) create mode 100644 command/repo_garden.go diff --git a/api/client.go b/api/client.go index 1c8c1eaa203..b35a469a396 100644 --- a/api/client.go +++ b/api/client.go @@ -220,6 +220,43 @@ func (c Client) GraphQL(query string, variables map[string]interface{}, data int return handleResponse(resp, data) } +func (c Client) RESTWithResponse(method string, p string, body io.Reader, data interface{}) (*http.Response, error) { + url := "https://api.github.com/" + p + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + + resp, err := c.http.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + success := resp.StatusCode >= 200 && resp.StatusCode < 300 + if !success { + return nil, handleHTTPError(resp) + } + + if resp.StatusCode == http.StatusNoContent { + return resp, nil + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(b, &data) + if err != nil { + return nil, err + } + + return resp, nil +} + // REST performs a REST request and parses the response. func (c Client) REST(method string, p string, body io.Reader, data interface{}) error { url := "https://api.github.com/" + p diff --git a/command/repo_garden.go b/command/repo_garden.go new file mode 100644 index 00000000000..32e1c54214f --- /dev/null +++ b/command/repo_garden.go @@ -0,0 +1,421 @@ +package command + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/rand" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/cli/cli/api" + "github.com/cli/cli/internal/ghrepo" + "github.com/cli/cli/utils" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" +) + +type Geometry struct { + Width int + Height int + Density float64 + Repository ghrepo.Interface +} + +type Player struct { + X int + Y int + Char string + Geo *Geometry + ShoeMoistureContent int +} + +type Commit struct { + Email string + Handle string + Sha string + Char string +} + +type Cell struct { + Char string + StatusLine string +} + +const ( + DirUp = iota + DirDown + DirLeft + DirRight +) + +type Direction = int + +func (p *Player) move(direction Direction) { + switch direction { + case DirUp: + if p.Y == 0 { + return + } + p.Y-- + case DirDown: + if p.Y == p.Geo.Height-1 { + return + } + p.Y++ + case DirLeft: + if p.X == 0 { + return + } + p.X-- + case DirRight: + if p.X == p.Geo.Width-1 { + return + } + p.X++ + } +} + +func init() { + repoCmd.AddCommand(repoGardenCmd) +} + +var repoGardenCmd = &cobra.Command{ + Use: "garden", + Short: "Wander around a repository as a garden", + Long: "Use WASD or vi keys to move and q to quit.", + RunE: repoGarden, +} + +func repoGarden(cmd *cobra.Command, args []string) error { + ctx := contextForCommand(cmd) + client, err := apiClientForContext(ctx) + if err != nil { + return err + } + + out := colorableOut(cmd) + + isTTY := false + outFile, isFile := out.(*os.File) + if isFile { + isTTY = utils.IsTerminal(outFile) + if isTTY { + // FIXME: duplicates colorableOut + out = utils.NewColorable(outFile) + } + } + + if !isTTY { + return errors.New("must be connected to a terminal") + } + + var baseRepo ghrepo.Interface + if len(args) > 0 { + baseRepo, err = ghrepo.FromFullName(args[0]) + } else { + baseRepo, err = determineBaseRepo(client, cmd, ctx) + } + if err != nil { + return err + } + + seed := computeSeed(ghrepo.FullName(baseRepo)) + rand.Seed(seed) + + termWidth, termHeight, err := terminal.GetSize(int(outFile.Fd())) + if err != nil { + return err + } + + termWidth -= 10 + termHeight -= 10 + + geo := &Geometry{ + Width: termWidth, + Height: termHeight, + Repository: baseRepo, + // TODO based on number of commits/cells instead of just hardcoding + Density: 0.3, + } + + maxCommits := geo.Width * geo.Height + + commits, err := getCommits(client, baseRepo, maxCommits) + if err != nil { + return err + } + player := &Player{0, 0, utils.Bold("@"), geo, 0} + + clear() + garden := plantGarden(commits, geo) + drawGarden(out, garden, player) + + // thanks stackoverflow https://stackoverflow.com/a/17278776 + _ = exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() + _ = exec.Command("stty", "-F", "/dev/tty", "-echo").Run() + + var b []byte = make([]byte, 1) + for { + _, err := os.Stdin.Read(b) + if err != nil { + return err + } + + quitting := false + switch { + case isLeft(b): + player.move(DirLeft) + case isRight(b): + player.move(DirRight) + case isUp(b): + player.move(DirUp) + case isDown(b): + player.move(DirDown) + case isQuit(b): + quitting = true + } + + if quitting { + break + } + + clear() + drawGarden(out, garden, player) + } + + fmt.Println() + fmt.Println(utils.Bold("You turn and walk away from the wildflower garden...")) + + return nil +} + +// TODO fix arrow keys + +func isLeft(b []byte) bool { + return bytes.EqualFold(b, []byte("a")) || bytes.EqualFold(b, []byte("h")) +} + +func isRight(b []byte) bool { + return bytes.EqualFold(b, []byte("d")) || bytes.EqualFold(b, []byte("l")) +} + +func isDown(b []byte) bool { + return bytes.EqualFold(b, []byte("s")) || bytes.EqualFold(b, []byte("j")) +} + +func isUp(b []byte) bool { + return bytes.EqualFold(b, []byte("w")) || bytes.EqualFold(b, []byte("k")) +} + +func isQuit(b []byte) bool { + return bytes.EqualFold(b, []byte("q")) +} + +func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { + cellIx := 0 + grassCell := &Cell{utils.RGB(0, 200, 0, ","), "You're standing on a patch of grass in a field of wildflowers."} + garden := [][]*Cell{} + streamIx := rand.Intn(geo.Width - 1) + if streamIx == geo.Width/2 { + streamIx-- + } + tint := 0 + for y := 0; y < geo.Height; y++ { + if cellIx == len(commits)-1 { + break + } + garden = append(garden, []*Cell{}) + for x := 0; x < geo.Width; x++ { + if (y > 0 && (x == 0 || x == geo.Width-1)) || y == geo.Height-1 { + garden[y] = append(garden[y], &Cell{ + Char: utils.RGB(0, 150, 0, "^"), + StatusLine: "You're standing under a tall, leafy tree.", + }) + continue + } + if x == streamIx { + garden[y] = append(garden[y], &Cell{ + Char: utils.RGB(tint, tint, 255, "#"), + StatusLine: "You're standing in a shallow stream. It's refreshing.", + }) + tint += 15 + streamIx-- + if rand.Float64() < 0.5 { + streamIx++ + } + if streamIx < 0 { + streamIx = 0 + } + if streamIx > geo.Width { + streamIx = geo.Width + } + continue + } + if y == 0 && (x < geo.Width/2 || x > geo.Width/2) { + garden[y] = append(garden[y], &Cell{ + Char: utils.RGB(0, 200, 0, ","), + StatusLine: "You're standing by a wildflower garden. There is a light breeze.", + }) + continue + } else if y == 0 && x == geo.Width/2 { + garden[y] = append(garden[y], &Cell{ + Char: utils.RGB(139, 69, 19, "+"), + StatusLine: fmt.Sprintf("You're standing in front of a weather-beaten sign that says %s.", ghrepo.FullName(geo.Repository)), + }) + continue + } + + if cellIx == len(commits)-1 { + garden[y] = append(garden[y], grassCell) + continue + } + + chance := rand.Float64() + if chance <= geo.Density { + commit := commits[cellIx] + garden[y] = append(garden[y], &Cell{ + Char: commits[cellIx].Char, + StatusLine: fmt.Sprintf("You're standing at a flower called %s planted by %s.", commit.Sha[0:6], commit.Handle), + }) + cellIx++ + } else { + garden[y] = append(garden[y], grassCell) + } + } + } + + return garden +} + +func drawGarden(out io.Writer, garden [][]*Cell, player *Player) { + statusLine := "" + for y, gardenRow := range garden { + for x, gardenCell := range gardenRow { + char := "" + underPlayer := (player.X == x && player.Y == y) + if underPlayer { + statusLine = gardenCell.StatusLine + char = utils.Bold(player.Char) + + if strings.Contains(gardenCell.StatusLine, "stream") { + player.ShoeMoistureContent = 5 + } else { + if player.ShoeMoistureContent > 1 { + statusLine += "\nYour shoes squish with water from the stream." + } else if player.ShoeMoistureContent == 1 { + statusLine += "\nYour shoes seem to have dried out." + } + + if player.ShoeMoistureContent > 0 { + player.ShoeMoistureContent-- + } + } + } else { + char = gardenCell.Char + } + + fmt.Fprint(out, char) + } + fmt.Fprintln(out) + } + + fmt.Println() + fmt.Fprintln(out, utils.Bold(statusLine)) +} + +func shaToColorFunc(sha string) func(string) string { + return func(c string) string { + red, err := strconv.ParseInt(sha[0:2], 16, 64) + if err != nil { + panic(err) + } + + green, err := strconv.ParseInt(sha[2:4], 16, 64) + if err != nil { + panic(err) + } + + blue, err := strconv.ParseInt(sha[4:6], 16, 64) + if err != nil { + panic(err) + } + + return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", red, green, blue, c) + } +} + +func computeSeed(seed string) int64 { + lol := "" + + for _, r := range seed { + lol += fmt.Sprintf("%d", int(r)) + } + + result, err := strconv.ParseInt(lol[0:10], 10, 64) + if err != nil { + panic(err) + } + + return result +} + +func getCommits(client *api.Client, repo ghrepo.Interface, maxCommits int) ([]*Commit, error) { + type Item struct { + Author struct { + Login string + } + Sha string + } + + type Result []Item + + commits := []*Commit{} + + pathF := func(page int) string { + return fmt.Sprintf("repos/%s/%s/commits?per_page=100&page=%d", repo.RepoOwner(), repo.RepoName(), page) + } + + page := 1 + paginating := true + fmt.Println("gathering commits; this could take a minute...") + for paginating { + if len(commits) >= maxCommits { + break + } + result := Result{} + resp, err := client.RESTWithResponse("GET", pathF(page), nil, &result) + if err != nil { + return nil, err + } + for _, r := range result { + colorFunc := shaToColorFunc(r.Sha) + handle := r.Author.Login + if handle == "" { + handle = "a mysterious stranger" + } + commits = append(commits, &Commit{ + Handle: handle, + Sha: r.Sha, + Char: colorFunc(string(handle[0])), + }) + } + link := resp.Header["Link"] + if !strings.Contains(link[0], "last") { + paginating = false + } + page++ + time.Sleep(500) + } + + // reverse to get older commits first + for i, j := 0, len(commits)-1; i < j; i, j = i+1, j-1 { + commits[i], commits[j] = commits[j], commits[i] + } + + return commits, nil +} diff --git a/utils/color.go b/utils/color.go index 4940fe26b60..e3218db12ce 100644 --- a/utils/color.go +++ b/utils/color.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io" "os" @@ -59,3 +60,7 @@ func isColorEnabled() bool { } return _isColorEnabled } + +func RGB(r, g, b int, x string) string { + return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, x) +} From c0d094fa2b06d692b0bfc0d151889d1e603e9b95 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 14:07:26 -0500 Subject: [PATCH 02/22] move file --- command/repo_garden.go => pkg/cmd/repo/garden.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename command/repo_garden.go => pkg/cmd/repo/garden.go (100%) diff --git a/command/repo_garden.go b/pkg/cmd/repo/garden.go similarity index 100% rename from command/repo_garden.go rename to pkg/cmd/repo/garden.go From 09de5cd096e1b399fd7036fb831aff774a757b2f Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 14:44:13 -0500 Subject: [PATCH 03/22] oops --- pkg/cmd/repo/{ => garden}/garden.go | 90 +++++++++++++++++++---------- pkg/cmd/repo/repo.go | 2 + 2 files changed, 60 insertions(+), 32 deletions(-) rename pkg/cmd/repo/{ => garden}/garden.go (82%) diff --git a/pkg/cmd/repo/garden.go b/pkg/cmd/repo/garden/garden.go similarity index 82% rename from pkg/cmd/repo/garden.go rename to pkg/cmd/repo/garden/garden.go index 32e1c54214f..cfe1ea7ad21 100644 --- a/pkg/cmd/repo/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -1,4 +1,4 @@ -package command +package garden import ( "bytes" @@ -6,6 +6,7 @@ import ( "fmt" "io" "math/rand" + "net/http" "os" "os/exec" "strconv" @@ -13,10 +14,12 @@ import ( "time" "github.com/cli/cli/api" + "github.com/cli/cli/internal/ghinstance" "github.com/cli/cli/internal/ghrepo" + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/iostreams" "github.com/cli/cli/utils" "github.com/spf13/cobra" - "golang.org/x/crypto/ssh/terminal" ) type Geometry struct { @@ -80,54 +83,77 @@ func (p *Player) move(direction Direction) { } } -func init() { - repoCmd.AddCommand(repoGardenCmd) -} +type GardenOptions struct { + HttpClient func() (*http.Client, error) + IO *iostreams.IOStreams + BaseRepo func() (ghrepo.Interface, error) -var repoGardenCmd = &cobra.Command{ - Use: "garden", - Short: "Wander around a repository as a garden", - Long: "Use WASD or vi keys to move and q to quit.", - RunE: repoGarden, + RepoArg string } -func repoGarden(cmd *cobra.Command, args []string) error { - ctx := contextForCommand(cmd) - client, err := apiClientForContext(ctx) - if err != nil { - return err +func NewCmdGarden(f *cmdutil.Factory, runF func(*GardenOptions) error) *cobra.Command { + opts := GardenOptions{ + IO: f.IOStreams, + HttpClient: f.HttpClient, + BaseRepo: f.BaseRepo, } - out := colorableOut(cmd) - - isTTY := false - outFile, isFile := out.(*os.File) - if isFile { - isTTY = utils.IsTerminal(outFile) - if isTTY { - // FIXME: duplicates colorableOut - out = utils.NewColorable(outFile) - } + cmd := &cobra.Command{ + Use: "garden []", + Short: "Explore a git repository as a garden", + Long: "Use WASD or vi keys to move. q to quit.", + RunE: func(c *cobra.Command, args []string) error { + if len(args) > 0 { + opts.RepoArg = args[0] + } + if runF != nil { + return runF(&opts) + } + return gardenRun(&opts) + }, } +} + +func gardenRun(opts *GardenOptions) error { + out := opts.IO.Out - if !isTTY { + if opts.IO.IsStdoutTTY() { return errors.New("must be connected to a terminal") } - var baseRepo ghrepo.Interface - if len(args) > 0 { - baseRepo, err = ghrepo.FromFullName(args[0]) + var toView ghrepo.Interface + apiClient := api.NewClientFromHTTP(httpClient) + if opts.RepoArg == "" { + var err error + toView, err = opts.BaseRepo() + if err != nil { + return err + } } else { - baseRepo, err = determineBaseRepo(client, cmd, ctx) + var err error + viewURL := opts.RepoArg + if !strings.Contains(viewURL, "/") { + currentUser, err := api.CurrentLoginName(apiClient, ghinstance.Default()) + if err != nil { + return err + } + viewURL = currentUser + "/" + viewURL + } + toView, err = ghrepo.FromFullName(viewURL) + if err != nil { + return fmt.Errorf("argument error: %w", err) + } } + + repo, err := api.GitHubRepo(apiClient, toView) if err != nil { return err } - seed := computeSeed(ghrepo.FullName(baseRepo)) + seed := computeSeed(ghrepo.FullName(repo)) rand.Seed(seed) - termWidth, termHeight, err := terminal.GetSize(int(outFile.Fd())) + termWidth, termHeight, err := utils.TerminalSize(out) if err != nil { return err } diff --git a/pkg/cmd/repo/repo.go b/pkg/cmd/repo/repo.go index 551c966410b..02e6c368b00 100644 --- a/pkg/cmd/repo/repo.go +++ b/pkg/cmd/repo/repo.go @@ -6,6 +6,7 @@ import ( repoCreateCmd "github.com/cli/cli/pkg/cmd/repo/create" creditsCmd "github.com/cli/cli/pkg/cmd/repo/credits" repoForkCmd "github.com/cli/cli/pkg/cmd/repo/fork" + gardenCmd "github.com/cli/cli/pkg/cmd/repo/garden" repoViewCmd "github.com/cli/cli/pkg/cmd/repo/view" "github.com/cli/cli/pkg/cmdutil" "github.com/spf13/cobra" @@ -36,6 +37,7 @@ func NewCmdRepo(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(repoCloneCmd.NewCmdClone(f, nil)) cmd.AddCommand(repoCreateCmd.NewCmdCreate(f, nil)) cmd.AddCommand(creditsCmd.NewCmdRepoCredits(f, nil)) + cmd.AddCommand(gardenCmd.NewCmdGarden(f, nil)) return cmd } From e7000db3701f5c59c2c44eb0494c0a59425a753f Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 15:34:57 -0500 Subject: [PATCH 04/22] fixes --- pkg/cmd/repo/garden/garden.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index cfe1ea7ad21..73a6eaae078 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -112,15 +112,22 @@ func NewCmdGarden(f *cmdutil.Factory, runF func(*GardenOptions) error) *cobra.Co return gardenRun(&opts) }, } + + return cmd } func gardenRun(opts *GardenOptions) error { out := opts.IO.Out - if opts.IO.IsStdoutTTY() { + if !opts.IO.IsStdoutTTY() { return errors.New("must be connected to a terminal") } + httpClient, err := opts.HttpClient() + if err != nil { + return err + } + var toView ghrepo.Interface apiClient := api.NewClientFromHTTP(httpClient) if opts.RepoArg == "" { @@ -145,12 +152,7 @@ func gardenRun(opts *GardenOptions) error { } } - repo, err := api.GitHubRepo(apiClient, toView) - if err != nil { - return err - } - - seed := computeSeed(ghrepo.FullName(repo)) + seed := computeSeed(ghrepo.FullName(toView)) rand.Seed(seed) termWidth, termHeight, err := utils.TerminalSize(out) @@ -164,21 +166,21 @@ func gardenRun(opts *GardenOptions) error { geo := &Geometry{ Width: termWidth, Height: termHeight, - Repository: baseRepo, + Repository: toView, // TODO based on number of commits/cells instead of just hardcoding Density: 0.3, } maxCommits := geo.Width * geo.Height - commits, err := getCommits(client, baseRepo, maxCommits) + commits, err := getCommits(apiClient, toView, maxCommits) if err != nil { return err } player := &Player{0, 0, utils.Bold("@"), geo, 0} - clear() garden := plantGarden(commits, geo) + fmt.Printf("\033[2J") drawGarden(out, garden, player) // thanks stackoverflow https://stackoverflow.com/a/17278776 @@ -210,7 +212,7 @@ func gardenRun(opts *GardenOptions) error { break } - clear() + fmt.Printf("\033[2J") drawGarden(out, garden, player) } From 223aee58ce9ff5a7f65ce3d39b04c323c664d5bf Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 15:53:34 -0500 Subject: [PATCH 05/22] fix clearing --- pkg/cmd/repo/garden/garden.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 73a6eaae078..dbc56f3301e 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "os/exec" + "runtime" "strconv" "strings" "time" @@ -180,7 +181,7 @@ func gardenRun(opts *GardenOptions) error { player := &Player{0, 0, utils.Bold("@"), geo, 0} garden := plantGarden(commits, geo) - fmt.Printf("\033[2J") + clear(opts.IO) drawGarden(out, garden, player) // thanks stackoverflow https://stackoverflow.com/a/17278776 @@ -212,7 +213,7 @@ func gardenRun(opts *GardenOptions) error { break } - fmt.Printf("\033[2J") + clear(opts.IO) drawGarden(out, garden, player) } @@ -447,3 +448,14 @@ func getCommits(client *api.Client, repo ghrepo.Interface, maxCommits int) ([]*C return commits, nil } + +func clear(io *iostreams.IOStreams) { + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("cmd", "/c", "cls") + } else { + cmd = exec.Command("clear") + } + cmd.Stdout = io.Out + _ = cmd.Run() +} From dd84ce41e9cb9b9478a9c819b3b3363cd45eb38a Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 16:16:26 -0500 Subject: [PATCH 06/22] block windows sadly --- pkg/cmd/repo/garden/garden.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index dbc56f3301e..7036de99f30 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -120,6 +120,10 @@ func NewCmdGarden(f *cmdutil.Factory, runF func(*GardenOptions) error) *cobra.Co func gardenRun(opts *GardenOptions) error { out := opts.IO.Out + if runtime.GOOS == "windows" { + return errors.New("sorry :( this command only works on linux and macos") + } + if !opts.IO.IsStdoutTTY() { return errors.New("must be connected to a terminal") } @@ -450,12 +454,7 @@ func getCommits(client *api.Client, repo ghrepo.Interface, maxCommits int) ([]*C } func clear(io *iostreams.IOStreams) { - var cmd *exec.Cmd - if runtime.GOOS == "windows" { - cmd = exec.Command("cmd", "/c", "cls") - } else { - cmd = exec.Command("clear") - } + cmd := exec.Command("clear") cmd.Stdout = io.Out _ = cmd.Run() } From e3308eca9cf9a437aedec07ecd7202cc9852bd92 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 16:33:40 -0500 Subject: [PATCH 07/22] broken wip --- api/client.go | 2 +- pkg/cmd/repo/garden/garden.go | 58 +------------------ pkg/cmd/repo/garden/http.go | 106 ++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 58 deletions(-) create mode 100644 pkg/cmd/repo/garden/http.go diff --git a/api/client.go b/api/client.go index 195f4e249dd..bdd6d483779 100644 --- a/api/client.go +++ b/api/client.go @@ -293,7 +293,7 @@ func (c Client) RESTWithResponse(method string, p string, body io.Reader, data i success := resp.StatusCode >= 200 && resp.StatusCode < 300 if !success { - return nil, handleHTTPError(resp) + return nil, errors.New("api call failed") } if resp.StatusCode == http.StatusNoContent { diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 7036de99f30..37054312463 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -178,7 +178,7 @@ func gardenRun(opts *GardenOptions) error { maxCommits := geo.Width * geo.Height - commits, err := getCommits(apiClient, toView, maxCommits) + commits, err := getCommits(httpClient, toView, maxCommits) if err != nil { return err } @@ -397,62 +397,6 @@ func computeSeed(seed string) int64 { return result } -func getCommits(client *api.Client, repo ghrepo.Interface, maxCommits int) ([]*Commit, error) { - type Item struct { - Author struct { - Login string - } - Sha string - } - - type Result []Item - - commits := []*Commit{} - - pathF := func(page int) string { - return fmt.Sprintf("repos/%s/%s/commits?per_page=100&page=%d", repo.RepoOwner(), repo.RepoName(), page) - } - - page := 1 - paginating := true - fmt.Println("gathering commits; this could take a minute...") - for paginating { - if len(commits) >= maxCommits { - break - } - result := Result{} - resp, err := client.RESTWithResponse("GET", pathF(page), nil, &result) - if err != nil { - return nil, err - } - for _, r := range result { - colorFunc := shaToColorFunc(r.Sha) - handle := r.Author.Login - if handle == "" { - handle = "a mysterious stranger" - } - commits = append(commits, &Commit{ - Handle: handle, - Sha: r.Sha, - Char: colorFunc(string(handle[0])), - }) - } - link := resp.Header["Link"] - if !strings.Contains(link[0], "last") { - paginating = false - } - page++ - time.Sleep(500) - } - - // reverse to get older commits first - for i, j := 0, len(commits)-1; i < j; i, j = i+1, j-1 { - commits[i], commits[j] = commits[j], commits[i] - } - - return commits, nil -} - func clear(io *iostreams.IOStreams) { cmd := exec.Command("clear") cmd.Stdout = io.Out diff --git a/pkg/cmd/repo/garden/http.go b/pkg/cmd/repo/garden/http.go new file mode 100644 index 00000000000..47009accf72 --- /dev/null +++ b/pkg/cmd/repo/garden/http.go @@ -0,0 +1,106 @@ +package garden + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/cli/cli/internal/ghrepo" +) + +func getCommits(client *http.Client, repo ghrepo.Interface, maxCommits int) ([]*Commit, error) { + type Item struct { + Author struct { + Login string + } + Sha string + } + + type Result []Item + + commits := []*Commit{} + + pathF := func(page int) string { + return fmt.Sprintf("repos/%s/%s/commits?per_page=100&page=%d", repo.RepoOwner(), repo.RepoName(), page) + } + + page := 1 + paginating := true + fmt.Println("gathering commits; this could take a minute...") + for paginating { + if len(commits) >= maxCommits { + break + } + result := Result{} + resp, err := getResponse(client, pathF(page), &result) + if err != nil { + return nil, err + } + for _, r := range result { + colorFunc := shaToColorFunc(r.Sha) + handle := r.Author.Login + if handle == "" { + handle = "a mysterious stranger" + } + commits = append(commits, &Commit{ + Handle: handle, + Sha: r.Sha, + Char: colorFunc(string(handle[0])), + }) + } + link := resp.Header["Link"] + if !strings.Contains(link[0], "last") { + paginating = false + } + page++ + time.Sleep(500) + } + + // reverse to get older commits first + for i, j := 0, len(commits)-1; i < j; i, j = i+1, j-1 { + commits[i], commits[j] = commits[j], commits[i] + } + + return commits, nil +} + +func getResponse(client *http.Client, path string, body io.Reader) (*http.Response, error) { + url := "https://api.github.com/" + p + req, err := http.NewRequest("GET", url, body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json; charset=utf-8") + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + success := resp.StatusCode >= 200 && resp.StatusCode < 300 + if !success { + return nil, errors.New("api call failed") + } + + if resp.StatusCode == http.StatusNoContent { + return resp, nil + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(b, &data) + if err != nil { + return nil, err + } + + return resp, nil +} From 968e6ed523e859c3c33f45b2af71654601add7c9 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 16:37:40 -0500 Subject: [PATCH 08/22] fix api thing --- pkg/cmd/repo/garden/garden.go | 1 - pkg/cmd/repo/garden/http.go | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 37054312463..fedeb7c9c4e 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -12,7 +12,6 @@ import ( "runtime" "strconv" "strings" - "time" "github.com/cli/cli/api" "github.com/cli/cli/internal/ghinstance" diff --git a/pkg/cmd/repo/garden/http.go b/pkg/cmd/repo/garden/http.go index 47009accf72..05bd9a76228 100644 --- a/pkg/cmd/repo/garden/http.go +++ b/pkg/cmd/repo/garden/http.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "io" "io/ioutil" "net/http" "strings" @@ -69,9 +68,9 @@ func getCommits(client *http.Client, repo ghrepo.Interface, maxCommits int) ([]* return commits, nil } -func getResponse(client *http.Client, path string, body io.Reader) (*http.Response, error) { - url := "https://api.github.com/" + p - req, err := http.NewRequest("GET", url, body) +func getResponse(client *http.Client, path string, data interface{}) (*http.Response, error) { + url := "https://api.github.com/" + path + req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err } From 8498bd2ac761a31d3d0b65416131c257a520bdd1 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 16:38:26 -0500 Subject: [PATCH 09/22] do not add to client --- api/client.go | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/api/client.go b/api/client.go index bdd6d483779..2556f6317bb 100644 --- a/api/client.go +++ b/api/client.go @@ -276,43 +276,6 @@ func (c Client) GraphQL(hostname string, query string, variables map[string]inte return handleResponse(resp, data) } -func (c Client) RESTWithResponse(method string, p string, body io.Reader, data interface{}) (*http.Response, error) { - url := "https://api.github.com/" + p - req, err := http.NewRequest(method, url, body) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json; charset=utf-8") - - resp, err := c.http.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - success := resp.StatusCode >= 200 && resp.StatusCode < 300 - if !success { - return nil, errors.New("api call failed") - } - - if resp.StatusCode == http.StatusNoContent { - return resp, nil - } - - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - err = json.Unmarshal(b, &data) - if err != nil { - return nil, err - } - - return resp, nil -} - func graphQLClient(h *http.Client, hostname string) *graphql.Client { return graphql.NewClient(ghinstance.GraphQLEndpoint(hostname), h) } From e97bcb9b606a136f4303c17cfd497842ef5e42f2 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 16:41:19 -0500 Subject: [PATCH 10/22] move helper as it does not work on windows --- pkg/cmd/repo/garden/garden.go | 14 +++++++++----- utils/color.go | 5 ----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index fedeb7c9c4e..fe0356b1111 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -250,7 +250,7 @@ func isQuit(b []byte) bool { func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { cellIx := 0 - grassCell := &Cell{utils.RGB(0, 200, 0, ","), "You're standing on a patch of grass in a field of wildflowers."} + grassCell := &Cell{RGB(0, 200, 0, ","), "You're standing on a patch of grass in a field of wildflowers."} garden := [][]*Cell{} streamIx := rand.Intn(geo.Width - 1) if streamIx == geo.Width/2 { @@ -265,14 +265,14 @@ func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { for x := 0; x < geo.Width; x++ { if (y > 0 && (x == 0 || x == geo.Width-1)) || y == geo.Height-1 { garden[y] = append(garden[y], &Cell{ - Char: utils.RGB(0, 150, 0, "^"), + Char: RGB(0, 150, 0, "^"), StatusLine: "You're standing under a tall, leafy tree.", }) continue } if x == streamIx { garden[y] = append(garden[y], &Cell{ - Char: utils.RGB(tint, tint, 255, "#"), + Char: RGB(tint, tint, 255, "#"), StatusLine: "You're standing in a shallow stream. It's refreshing.", }) tint += 15 @@ -290,13 +290,13 @@ func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { } if y == 0 && (x < geo.Width/2 || x > geo.Width/2) { garden[y] = append(garden[y], &Cell{ - Char: utils.RGB(0, 200, 0, ","), + Char: RGB(0, 200, 0, ","), StatusLine: "You're standing by a wildflower garden. There is a light breeze.", }) continue } else if y == 0 && x == geo.Width/2 { garden[y] = append(garden[y], &Cell{ - Char: utils.RGB(139, 69, 19, "+"), + Char: RGB(139, 69, 19, "+"), StatusLine: fmt.Sprintf("You're standing in front of a weather-beaten sign that says %s.", ghrepo.FullName(geo.Repository)), }) continue @@ -401,3 +401,7 @@ func clear(io *iostreams.IOStreams) { cmd.Stdout = io.Out _ = cmd.Run() } + +func RGB(r, g, b int, x string) string { + return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, x) +} diff --git a/utils/color.go b/utils/color.go index dd16b1fd272..123e0927c6e 100644 --- a/utils/color.go +++ b/utils/color.go @@ -1,7 +1,6 @@ package utils import ( - "fmt" "io" "os" @@ -47,7 +46,3 @@ func isColorEnabled() bool { // TODO ignores cmd.OutOrStdout return IsTerminal(os.Stdout) } - -func RGB(r, g, b int, x string) string { - return fmt.Sprintf("\033[38;2;%d;%d;%dm%s\033[0m", r, g, b, x) -} From 619960c163efa8fcecc13f026fdd881f7c972fb3 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 25 Aug 2020 16:43:10 -0500 Subject: [PATCH 11/22] hide command --- pkg/cmd/repo/garden/garden.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index fe0356b1111..6bb4d3eff54 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -99,9 +99,10 @@ func NewCmdGarden(f *cmdutil.Factory, runF func(*GardenOptions) error) *cobra.Co } cmd := &cobra.Command{ - Use: "garden []", - Short: "Explore a git repository as a garden", - Long: "Use WASD or vi keys to move. q to quit.", + Use: "garden []", + Short: "Explore a git repository as a garden", + Long: "Use WASD or vi keys to move. q to quit.", + Hidden: true, RunE: func(c *cobra.Command, args []string) error { if len(args) > 0 { opts.RepoArg = args[0] From 44eaf8525cbe9b0a94361ff15073f411dd143ee3 Mon Sep 17 00:00:00 2001 From: nate smith Date: Thu, 27 Aug 2020 13:27:38 -0500 Subject: [PATCH 12/22] macos fix --- pkg/cmd/repo/garden/garden.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 6bb4d3eff54..6a704c1a71b 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -7,7 +7,6 @@ import ( "io" "math/rand" "net/http" - "os" "os/exec" "runtime" "strconv" @@ -189,12 +188,17 @@ func gardenRun(opts *GardenOptions) error { drawGarden(out, garden, player) // thanks stackoverflow https://stackoverflow.com/a/17278776 - _ = exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() - _ = exec.Command("stty", "-F", "/dev/tty", "-echo").Run() + if runtime.GOOS == "darwin" { + _ = exec.Command("stty", "-f", "/dev/tty", "cbreak", "min", "1").Run() + _ = exec.Command("stty", "-f", "/dev/tty", "-echo").Run() + } else { + _ = exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() + _ = exec.Command("stty", "-F", "/dev/tty", "-echo").Run() + } var b []byte = make([]byte, 1) for { - _, err := os.Stdin.Read(b) + _, err := opts.IO.In.Read(b) if err != nil { return err } From 098ffa7a77c751add79ec3dda9e2dc320b2a62ba Mon Sep 17 00:00:00 2001 From: Nate Smith Date: Tue, 8 Sep 2020 16:48:46 -0500 Subject: [PATCH 13/22] Update pkg/cmd/repo/garden/garden.go Co-authored-by: Lee Reilly --- pkg/cmd/repo/garden/garden.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 6a704c1a71b..c693e07cba1 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -234,7 +234,7 @@ func gardenRun(opts *GardenOptions) error { // TODO fix arrow keys func isLeft(b []byte) bool { - return bytes.EqualFold(b, []byte("a")) || bytes.EqualFold(b, []byte("h")) + return bytes.EqualFold(b, []byte("a")) || bytes.EqualFold(b, []byte("q")) || bytes.EqualFold(b, []byte("h")) } func isRight(b []byte) bool { @@ -246,7 +246,7 @@ func isDown(b []byte) bool { } func isUp(b []byte) bool { - return bytes.EqualFold(b, []byte("w")) || bytes.EqualFold(b, []byte("k")) + return bytes.EqualFold(b, []byte("w")) || bytes.EqualFold(b, []byte("z")) || bytes.EqualFold(b, []byte("k")) } func isQuit(b []byte) bool { From 72e0b5bbf5f133eb78a7e0ee4b94557c8c8fbe4f Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 11:45:55 -0500 Subject: [PATCH 14/22] default for key input loop --- pkg/cmd/repo/garden/garden.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index c693e07cba1..e9cb00266f9 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -204,6 +204,7 @@ func gardenRun(opts *GardenOptions) error { } quitting := false + continuing := false switch { case isLeft(b): player.move(DirLeft) @@ -215,6 +216,12 @@ func gardenRun(opts *GardenOptions) error { player.move(DirDown) case isQuit(b): quitting = true + default: + continuing = true + } + + if continuing { + continue } if quitting { From ea69c63767e282d0150ea7a24b99a83226d0107e Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 14:22:28 -0500 Subject: [PATCH 15/22] get redrawing working --- pkg/cmd/repo/garden/garden.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index e9cb00266f9..78e1964b486 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -203,6 +203,9 @@ func gardenRun(opts *GardenOptions) error { return err } + oldX := player.X + oldY := player.Y + quitting := false continuing := false switch { @@ -228,8 +231,22 @@ func gardenRun(opts *GardenOptions) error { break } - clear(opts.IO) - drawGarden(out, garden, player) + fmt.Fprint(out, "\033[;H") // move to top left + for x := 0; x < oldX && x < player.Geo.Width; x++ { + fmt.Fprint(out, "\033[C") + } + for y := 0; y < oldY && y < player.Geo.Height; y++ { + fmt.Fprint(out, "\033[B") + } + fmt.Fprint(out, garden[oldY][oldX].Char) + fmt.Fprint(out, "\033[;H") // move to top left + for x := 0; x < player.X && x < player.Geo.Width; x++ { + fmt.Fprint(out, "\033[C") + } + for y := 0; y < player.Y && y < player.Geo.Height; y++ { + fmt.Fprint(out, "\033[B") + } + fmt.Fprint(out, player.Char) } fmt.Println() From 1a3daa157063c1098aa1cbc74b2d25070b281d90 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 14:48:34 -0500 Subject: [PATCH 16/22] clean up garden update, it all works --- pkg/cmd/repo/garden/garden.go | 79 ++++++++++++++++++++++++++++------- 1 file changed, 63 insertions(+), 16 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 78e1964b486..74f5429d1d3 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -57,29 +57,31 @@ const ( type Direction = int -func (p *Player) move(direction Direction) { +func (p *Player) move(direction Direction) bool { switch direction { case DirUp: if p.Y == 0 { - return + return false } p.Y-- case DirDown: if p.Y == p.Geo.Height-1 { - return + return false } p.Y++ case DirLeft: if p.X == 0 { - return + return false } p.X-- case DirRight: if p.X == p.Geo.Width-1 { - return + return false } p.X++ } + + return true } type GardenOptions struct { @@ -206,24 +208,26 @@ func gardenRun(opts *GardenOptions) error { oldX := player.X oldY := player.Y + moved := false + quitting := false continuing := false switch { case isLeft(b): - player.move(DirLeft) + moved = player.move(DirLeft) case isRight(b): - player.move(DirRight) + moved = player.move(DirRight) case isUp(b): - player.move(DirUp) + moved = player.move(DirUp) case isDown(b): - player.move(DirDown) + moved = player.move(DirDown) case isQuit(b): quitting = true default: continuing = true } - if continuing { + if !moved || continuing { continue } @@ -231,6 +235,11 @@ func gardenRun(opts *GardenOptions) error { break } + underPlayer := garden[player.Y][player.X] + previousCell := garden[oldY][oldX] + + // print whatever was just under player + fmt.Fprint(out, "\033[;H") // move to top left for x := 0; x < oldX && x < player.Geo.Width; x++ { fmt.Fprint(out, "\033[C") @@ -238,7 +247,9 @@ func gardenRun(opts *GardenOptions) error { for y := 0; y < oldY && y < player.Geo.Height; y++ { fmt.Fprint(out, "\033[B") } - fmt.Fprint(out, garden[oldY][oldX].Char) + fmt.Fprint(out, previousCell.Char) + + // print player character fmt.Fprint(out, "\033[;H") // move to top left for x := 0; x < player.X && x < player.Geo.Width; x++ { fmt.Fprint(out, "\033[C") @@ -247,6 +258,28 @@ func gardenRun(opts *GardenOptions) error { fmt.Fprint(out, "\033[B") } fmt.Fprint(out, player.Char) + + // handle stream wettening + + if strings.Contains(underPlayer.StatusLine, "stream") { + player.ShoeMoistureContent = 5 + } else { + if player.ShoeMoistureContent > 0 { + player.ShoeMoistureContent-- + } + } + + // status line stuff + sl := statusLine(garden, player) + + fmt.Fprint(out, "\033[;H") // move to top left + for y := 0; y < player.Geo.Height-1; y++ { + fmt.Fprint(out, "\033[B") + } + fmt.Fprintln(out) + fmt.Fprintln(out) + + fmt.Fprint(out, utils.Bold(sl)) } fmt.Println() @@ -354,22 +387,23 @@ func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { } func drawGarden(out io.Writer, garden [][]*Cell, player *Player) { - statusLine := "" + fmt.Fprint(out, "\033[?25l") + sl := "" for y, gardenRow := range garden { for x, gardenCell := range gardenRow { char := "" underPlayer := (player.X == x && player.Y == y) if underPlayer { - statusLine = gardenCell.StatusLine + sl = gardenCell.StatusLine char = utils.Bold(player.Char) if strings.Contains(gardenCell.StatusLine, "stream") { player.ShoeMoistureContent = 5 } else { if player.ShoeMoistureContent > 1 { - statusLine += "\nYour shoes squish with water from the stream." + sl += "\nYour shoes squish with water from the stream." } else if player.ShoeMoistureContent == 1 { - statusLine += "\nYour shoes seem to have dried out." + sl += "\nYour shoes seem to have dried out." } if player.ShoeMoistureContent > 0 { @@ -386,7 +420,20 @@ func drawGarden(out io.Writer, garden [][]*Cell, player *Player) { } fmt.Println() - fmt.Fprintln(out, utils.Bold(statusLine)) + fmt.Fprintln(out, utils.Bold(sl)) +} + +func statusLine(garden [][]*Cell, player *Player) string { + statusLine := garden[player.Y][player.X].StatusLine + " " + if player.ShoeMoistureContent > 1 { + statusLine += "\nYour shoes squish with water from the stream." + } else if player.ShoeMoistureContent == 1 { + statusLine += "\nYour shoes seem to have dried out." + } else { + statusLine += "\n " + } + + return statusLine } func shaToColorFunc(sha string) func(string) string { From db9afcaca824e2523da5975e141b4f47e31f784e Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 15:02:33 -0500 Subject: [PATCH 17/22] notes --- pkg/cmd/repo/garden/garden.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 74f5429d1d3..770ad1c0367 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -207,11 +207,10 @@ func gardenRun(opts *GardenOptions) error { oldX := player.X oldY := player.Y - moved := false - quitting := false continuing := false + switch { case isLeft(b): moved = player.move(DirLeft) @@ -289,6 +288,8 @@ func gardenRun(opts *GardenOptions) error { } // TODO fix arrow keys +// TODO detect ctrl+c if possible +// TODO reshow cursor and clear terminal on quit func isLeft(b []byte) bool { return bytes.EqualFold(b, []byte("a")) || bytes.EqualFold(b, []byte("q")) || bytes.EqualFold(b, []byte("h")) @@ -387,7 +388,7 @@ func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { } func drawGarden(out io.Writer, garden [][]*Cell, player *Player) { - fmt.Fprint(out, "\033[?25l") + fmt.Fprint(out, "\033[?25l") // hide cursor. it needs to be restored at command exit. sl := "" for y, gardenRow := range garden { for x, gardenCell := range gardenRow { From 5c3bad472452697037ccaab8a2161594951add32 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 15:46:49 -0500 Subject: [PATCH 18/22] fix arrow keys and just do wads/arrows/vi --- pkg/cmd/repo/garden/garden.go | 45 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 770ad1c0367..6166e3b548d 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -102,7 +102,7 @@ func NewCmdGarden(f *cmdutil.Factory, runF func(*GardenOptions) error) *cobra.Co cmd := &cobra.Command{ Use: "garden []", Short: "Explore a git repository as a garden", - Long: "Use WASD or vi keys to move. q to quit.", + Long: "Use arrow keys, WASD or vi keys to move. q to quit.", Hidden: true, RunE: func(c *cobra.Command, args []string) error { if len(args) > 0 { @@ -198,12 +198,9 @@ func gardenRun(opts *GardenOptions) error { _ = exec.Command("stty", "-F", "/dev/tty", "-echo").Run() } - var b []byte = make([]byte, 1) + var b []byte = make([]byte, 3) for { - _, err := opts.IO.In.Read(b) - if err != nil { - return err - } + _, _ = opts.IO.In.Read(b) oldX := player.X oldY := player.Y @@ -226,14 +223,14 @@ func gardenRun(opts *GardenOptions) error { continuing = true } - if !moved || continuing { - continue - } - if quitting { break } + if !moved || continuing { + continue + } + underPlayer := garden[player.Y][player.X] previousCell := garden[oldY][oldX] @@ -281,34 +278,40 @@ func gardenRun(opts *GardenOptions) error { fmt.Fprint(out, utils.Bold(sl)) } - fmt.Println() - fmt.Println(utils.Bold("You turn and walk away from the wildflower garden...")) + clear(opts.IO) + fmt.Fprint(out, "\033[?25h") + fmt.Fprintln(out) + fmt.Fprintln(out, utils.Bold("You turn and walk away from the wildflower garden...")) return nil } -// TODO fix arrow keys -// TODO detect ctrl+c if possible -// TODO reshow cursor and clear terminal on quit - func isLeft(b []byte) bool { - return bytes.EqualFold(b, []byte("a")) || bytes.EqualFold(b, []byte("q")) || bytes.EqualFold(b, []byte("h")) + left := []byte{27, 91, 68} + r := rune(b[0]) + return bytes.EqualFold(b, left) || r == 'a' || r == 'h' } func isRight(b []byte) bool { - return bytes.EqualFold(b, []byte("d")) || bytes.EqualFold(b, []byte("l")) + right := []byte{27, 91, 67} + r := rune(b[0]) + return bytes.EqualFold(b, right) || r == 'd' || r == 'l' } func isDown(b []byte) bool { - return bytes.EqualFold(b, []byte("s")) || bytes.EqualFold(b, []byte("j")) + down := []byte{27, 91, 66} + r := rune(b[0]) + return bytes.EqualFold(b, down) || r == 's' || r == 'j' } func isUp(b []byte) bool { - return bytes.EqualFold(b, []byte("w")) || bytes.EqualFold(b, []byte("z")) || bytes.EqualFold(b, []byte("k")) + up := []byte{27, 91, 65} + r := rune(b[0]) + return bytes.EqualFold(b, up) || r == 'w' || r == 'k' } func isQuit(b []byte) bool { - return bytes.EqualFold(b, []byte("q")) + return rune(b[0]) == 'q' } func plantGarden(commits []*Commit, geo *Geometry) [][]*Cell { From cc4678a3fb104ca0ef1e55660d47d5a4d57d9a69 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 15:59:53 -0500 Subject: [PATCH 19/22] this function is only called once now --- pkg/cmd/repo/garden/garden.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 6166e3b548d..3b120f8828e 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -403,16 +403,6 @@ func drawGarden(out io.Writer, garden [][]*Cell, player *Player) { if strings.Contains(gardenCell.StatusLine, "stream") { player.ShoeMoistureContent = 5 - } else { - if player.ShoeMoistureContent > 1 { - sl += "\nYour shoes squish with water from the stream." - } else if player.ShoeMoistureContent == 1 { - sl += "\nYour shoes seem to have dried out." - } - - if player.ShoeMoistureContent > 0 { - player.ShoeMoistureContent-- - } } } else { char = gardenCell.Char From 396d9a5b5e9b31db8da304159e5644a127aca12d Mon Sep 17 00:00:00 2001 From: vilmibm Date: Mon, 14 Sep 2020 15:59:59 -0500 Subject: [PATCH 20/22] support ghes --- pkg/cmd/repo/garden/http.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/repo/garden/http.go b/pkg/cmd/repo/garden/http.go index 05bd9a76228..0cc67d82d22 100644 --- a/pkg/cmd/repo/garden/http.go +++ b/pkg/cmd/repo/garden/http.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/cli/cli/internal/ghinstance" "github.com/cli/cli/internal/ghrepo" ) @@ -69,7 +70,7 @@ func getCommits(client *http.Client, repo ghrepo.Interface, maxCommits int) ([]* } func getResponse(client *http.Client, path string, data interface{}) (*http.Response, error) { - url := "https://api.github.com/" + path + url := ghinstance.RESTPrefix(ghinstance.OverridableDefault()) + path req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err From 9b789abb95bcc3d8ab9ae5e657ea8ce5cd105943 Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 15 Sep 2020 09:47:38 -0500 Subject: [PATCH 21/22] add a progress indicator --- pkg/cmd/repo/garden/garden.go | 3 +++ pkg/cmd/repo/garden/http.go | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 3b120f8828e..14f939c28e2 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -179,7 +179,10 @@ func gardenRun(opts *GardenOptions) error { maxCommits := geo.Width * geo.Height + opts.IO.StartProgressIndicator() + fmt.Fprintln(out, "gathering commits; this could take a minute...") commits, err := getCommits(httpClient, toView, maxCommits) + opts.IO.StopProgressIndicator() if err != nil { return err } diff --git a/pkg/cmd/repo/garden/http.go b/pkg/cmd/repo/garden/http.go index 0cc67d82d22..ff7f47fe07a 100644 --- a/pkg/cmd/repo/garden/http.go +++ b/pkg/cmd/repo/garden/http.go @@ -31,7 +31,6 @@ func getCommits(client *http.Client, repo ghrepo.Interface, maxCommits int) ([]* page := 1 paginating := true - fmt.Println("gathering commits; this could take a minute...") for paginating { if len(commits) >= maxCommits { break From 2600c5460a1f347ad627bfca9fac242a2ab3b67a Mon Sep 17 00:00:00 2001 From: vilmibm Date: Tue, 15 Sep 2020 09:51:43 -0500 Subject: [PATCH 22/22] cap maxCommits --- pkg/cmd/repo/garden/garden.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/repo/garden/garden.go b/pkg/cmd/repo/garden/garden.go index 14f939c28e2..46761b253b5 100644 --- a/pkg/cmd/repo/garden/garden.go +++ b/pkg/cmd/repo/garden/garden.go @@ -177,7 +177,7 @@ func gardenRun(opts *GardenOptions) error { Density: 0.3, } - maxCommits := geo.Width * geo.Height + maxCommits := (geo.Width * geo.Height) / 2 opts.IO.StartProgressIndicator() fmt.Fprintln(out, "gathering commits; this could take a minute...")