diff --git a/pkg/cmd/gist/gist.go b/pkg/cmd/gist/gist.go index ea10a8c40..e47f055db 100644 --- a/pkg/cmd/gist/gist.go +++ b/pkg/cmd/gist/gist.go @@ -2,6 +2,7 @@ package gist import ( gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create" + gistListCmd "github.com/cli/cli/pkg/cmd/gist/list" "github.com/cli/cli/pkg/cmdutil" "github.com/spf13/cobra" ) @@ -14,6 +15,7 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command { } cmd.AddCommand(gistCreateCmd.NewCmdCreate(f, nil)) + cmd.AddCommand(gistListCmd.NewCmdList(f, nil)) return cmd } diff --git a/pkg/cmd/gist/list/http.go b/pkg/cmd/gist/list/http.go new file mode 100644 index 000000000..e64243977 --- /dev/null +++ b/pkg/cmd/gist/list/http.go @@ -0,0 +1,52 @@ +package list + +import ( + "fmt" + "net/http" + "net/url" + "sort" + "time" + + "github.com/cli/cli/api" +) + +type Gist struct { + Description string `json:"description"` + HTMLURL string `json:"html_url"` + UpdatedAt time.Time `json:"updated_at"` + Public bool +} + +func listGists(client *http.Client, hostname string, limit int, visibility string) ([]Gist, error) { + result := []Gist{} + + query := url.Values{} + if visibility == "all" { + query.Add("per_page", fmt.Sprintf("%d", limit)) + } else { + query.Add("per_page", "100") + } + + apiClient := api.NewClientFromHTTP(client) + err := apiClient.REST(hostname, "GET", "gists?"+query.Encode(), nil, &result) + if err != nil { + return nil, err + } + + gists := []Gist{} + + for _, gist := range result { + if len(gists) == limit { + break + } + if visibility == "all" || (visibility == "private" && !gist.Public) || (visibility == "public" && gist.Public) { + gists = append(gists, gist) + } + } + + sort.SliceStable(gists, func(i, j int) bool { + return gists[i].UpdatedAt.After(gists[j].UpdatedAt) + }) + + return gists, nil +} diff --git a/pkg/cmd/gist/list/list.go b/pkg/cmd/gist/list/list.go new file mode 100644 index 000000000..bf59497f3 --- /dev/null +++ b/pkg/cmd/gist/list/list.go @@ -0,0 +1,90 @@ +package list + +import ( + "fmt" + "net/http" + + "github.com/cli/cli/internal/ghinstance" + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/iostreams" + "github.com/cli/cli/utils" + "github.com/spf13/cobra" +) + +type ListOptions struct { + IO *iostreams.IOStreams + HttpClient func() (*http.Client, error) + + Limit int + Visibility string // all, secret, public +} + +func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command { + opts := &ListOptions{ + IO: f.IOStreams, + HttpClient: f.HttpClient, + } + + cmd := &cobra.Command{ + Use: "list", + Short: "List your gists", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + if opts.Limit < 1 { + return &cmdutil.FlagError{Err: fmt.Errorf("invalid limit: %v", opts.Limit)} + } + + pub := cmd.Flags().Changed("public") + priv := cmd.Flags().Changed("private") + + opts.Visibility = "all" + if pub && !priv { + opts.Visibility = "public" + } else if priv && !pub { + opts.Visibility = "private" + } + + if runF != nil { + return runF(opts) + } + + return listRun(opts) + }, + } + + cmd.Flags().IntVarP(&opts.Limit, "limit", "L", 10, "Maximum number of gists to fetch") + cmd.Flags().Bool("public", false, "Show only public gists") + cmd.Flags().Bool("private", false, "Show only private gists") + + return cmd +} + +func listRun(opts *ListOptions) error { + client, err := opts.HttpClient() + if err != nil { + return err + } + + gists, err := listGists(client, ghinstance.OverridableDefault(), opts.Limit, opts.Visibility) + if err != nil { + return err + } + + cs := opts.IO.ColorScheme() + + tp := utils.NewTablePrinter(opts.IO) + + for _, gist := range gists { + // TODO i was getting confusing results with table printer's truncation + description := gist.Description + if len(description) > 40 { + description = description[0:37] + "..." + } + + tp.AddField(description, nil, cs.Bold) + tp.AddField(gist.HTMLURL, nil, nil) + tp.EndRow() + } + + return tp.Render() +} diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go new file mode 100644 index 000000000..03266cdf2 --- /dev/null +++ b/pkg/cmd/gist/list/list_test.go @@ -0,0 +1,84 @@ +package list + +import ( + "bytes" + "testing" + + "github.com/cli/cli/pkg/cmdutil" + "github.com/google/shlex" + "github.com/stretchr/testify/assert" +) + +func TestNewCmdList(t *testing.T) { + tests := []struct { + name string + cli string + wants ListOptions + }{ + { + name: "no arguments", + cli: "", + wants: ListOptions{ + Limit: 10, + Visibility: "all", + }, + }, + { + name: "public", + cli: "--public", + wants: ListOptions{ + Limit: 10, + Visibility: "public", + }, + }, + { + name: "private", + cli: "--private", + wants: ListOptions{ + Limit: 10, + Visibility: "private", + }, + }, + { + name: "public and private", + cli: "--private --public", + wants: ListOptions{ + Limit: 10, + Visibility: "all", + }, + }, + { + name: "limit", + cli: "--limit 30", + wants: ListOptions{ + Limit: 30, + Visibility: "all", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &cmdutil.Factory{} + + argv, err := shlex.Split(tt.cli) + assert.NoError(t, err) + + var gotOpts *ListOptions + cmd := NewCmdList(f, func(opts *ListOptions) error { + gotOpts = opts + return nil + }) + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + + _, err = cmd.ExecuteC() + assert.NoError(t, err) + + assert.Equal(t, tt.wants.Visibility, gotOpts.Visibility) + assert.Equal(t, tt.wants.Limit, gotOpts.Limit) + }) + } +}