From c04fa685d38d41e5f4e5748f867c926bbc859205 Mon Sep 17 00:00:00 2001 From: Heath Stewart Date: Wed, 9 Oct 2024 00:10:17 -0700 Subject: [PATCH] Refactor filtering into existing `gist list` Resolves feedback in issue #9704 --- pkg/cmd/gist/gist.go | 2 - pkg/cmd/gist/list/list.go | 65 ++++- pkg/cmd/gist/list/list_test.go | 84 ++++++- pkg/cmd/gist/search/search.go | 126 ---------- pkg/cmd/gist/search/search_test.go | 371 ----------------------------- pkg/cmd/gist/shared/shared.go | 80 +++---- 6 files changed, 169 insertions(+), 559 deletions(-) delete mode 100644 pkg/cmd/gist/search/search.go delete mode 100644 pkg/cmd/gist/search/search_test.go diff --git a/pkg/cmd/gist/gist.go b/pkg/cmd/gist/gist.go index bf562bf74c4..1f7fc9ea0e9 100644 --- a/pkg/cmd/gist/gist.go +++ b/pkg/cmd/gist/gist.go @@ -8,7 +8,6 @@ import ( gistEditCmd "github.com/cli/cli/v2/pkg/cmd/gist/edit" gistListCmd "github.com/cli/cli/v2/pkg/cmd/gist/list" gistRenameCmd "github.com/cli/cli/v2/pkg/cmd/gist/rename" - gistSearchCmd "github.com/cli/cli/v2/pkg/cmd/gist/search" gistViewCmd "github.com/cli/cli/v2/pkg/cmd/gist/view" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/spf13/cobra" @@ -36,7 +35,6 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(gistEditCmd.NewCmdEdit(f, nil)) cmd.AddCommand(gistDeleteCmd.NewCmdDelete(f, nil)) cmd.AddCommand(gistRenameCmd.NewCmdRename(f, nil)) - cmd.AddCommand(gistSearchCmd.NewCmdSearch(f, nil)) return cmd } diff --git a/pkg/cmd/gist/list/list.go b/pkg/cmd/gist/list/list.go index 43c4473b505..215fedcceb4 100644 --- a/pkg/cmd/gist/list/list.go +++ b/pkg/cmd/gist/list/list.go @@ -1,9 +1,15 @@ package list import ( + "fmt" "net/http" + "regexp" + "strings" + "time" "github.com/cli/cli/v2/internal/gh" + "github.com/cli/cli/v2/internal/tableprinter" + "github.com/cli/cli/v2/internal/text" "github.com/cli/cli/v2/pkg/cmd/gist/shared" "github.com/cli/cli/v2/pkg/cmdutil" "github.com/cli/cli/v2/pkg/iostreams" @@ -15,8 +21,10 @@ type ListOptions struct { Config func() (gh.Config, error) HttpClient func() (*http.Client, error) - Limit int - Visibility string // all, secret, public + Limit int + Filter *regexp.Regexp + IncludeContent bool + Visibility string // all, secret, public } func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command { @@ -28,6 +36,7 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman var flagPublic bool var flagSecret bool + var flagFilter string cmd := &cobra.Command{ Use: "list", @@ -39,6 +48,12 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman return cmdutil.FlagErrorf("invalid limit: %v", opts.Limit) } + if filter, err := regexp.CompilePOSIX(flagFilter); err != nil { + return err + } else { + opts.Filter = filter + } + opts.Visibility = "all" if flagSecret { opts.Visibility = "secret" @@ -56,6 +71,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman cmd.Flags().IntVarP(&opts.Limit, "limit", "L", 10, "Maximum number of gists to fetch") cmd.Flags().BoolVar(&flagPublic, "public", false, "Show only public gists") cmd.Flags().BoolVar(&flagSecret, "secret", false, "Show only secret gists") + cmd.Flags().StringVar(&flagFilter, "filter", "", "Filter gists using a regular expression") + cmd.Flags().BoolVar(&opts.IncludeContent, "include-content", false, "Include gists' file content when filtering") return cmd } @@ -73,7 +90,7 @@ func listRun(opts *ListOptions) error { host, _ := cfg.Authentication().DefaultHost() - gists, err := shared.ListGists(client, host, opts.Limit, opts.Visibility, false, nil) + gists, err := shared.ListGists(client, host, opts.Limit, opts.Filter, opts.IncludeContent, opts.Visibility) if err != nil { return err } @@ -82,5 +99,45 @@ func listRun(opts *ListOptions) error { return cmdutil.NewNoResultsError("no gists found") } - return shared.PrintGists(opts.IO, gists) + if err := opts.IO.StartPager(); err == nil { + defer opts.IO.StopPager() + } else { + fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err) + } + + cs := opts.IO.ColorScheme() + tp := tableprinter.New(opts.IO, tableprinter.WithHeader("ID", "DESCRIPTION", "FILES", "VISIBILITY", "UPDATED")) + + for _, gist := range gists { + fileCount := len(gist.Files) + + visibility := "public" + visColor := cs.Green + if !gist.Public { + visibility = "secret" + visColor = cs.Red + } + + description := gist.Description + if description == "" { + for filename := range gist.Files { + if !strings.HasPrefix(filename, "gistfile") { + description = filename + break + } + } + } + + tp.AddField(gist.ID) + tp.AddField( + text.RemoveExcessiveWhitespace(description), + tableprinter.WithColor(cs.Bold), + ) + tp.AddField(text.Pluralize(fileCount, "file")) + tp.AddField(visibility, tableprinter.WithColor(visColor)) + tp.AddTimeField(time.Now(), gist.UpdatedAt, cs.Gray) + tp.EndRow() + } + + return tp.Render() } diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go index a15bae6aa21..80e3178ab42 100644 --- a/pkg/cmd/gist/list/list_test.go +++ b/pkg/cmd/gist/list/list_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net/http" + "regexp" "testing" "time" @@ -19,9 +20,10 @@ import ( func TestNewCmdList(t *testing.T) { tests := []struct { - name string - cli string - wants ListOptions + name string + cli string + wants ListOptions + wantsErr bool }{ { name: "no arguments", @@ -70,6 +72,26 @@ func TestNewCmdList(t *testing.T) { Visibility: "all", }, }, + { + name: "invalid limit", + cli: "--limit 0", + wantsErr: true, + }, + { + name: "filter and include-content", + cli: "--filter octo --include-content", + wants: ListOptions{ + Limit: 10, + Filter: regexp.MustCompilePOSIX("octo"), + IncludeContent: true, + Visibility: "all", + }, + }, + { + name: "invalid filter", + cli: "--filter octo(", + wantsErr: true, + }, } for _, tt := range tests { @@ -90,6 +112,10 @@ func TestNewCmdList(t *testing.T) { cmd.SetErr(&bytes.Buffer{}) _, err = cmd.ExecuteC() + if tt.wantsErr { + assert.Error(t, err) + return + } assert.NoError(t, err) assert.Equal(t, tt.wants.Visibility, gotOpts.Visibility) @@ -358,6 +384,58 @@ func Test_listRun(t *testing.T) { `), nontty: true, }, + { + name: "with content filter", + opts: &ListOptions{ + Filter: regexp.MustCompile("octo"), + IncludeContent: true, + Visibility: "all", + }, + stubs: func(reg *httpmock.Registry) { + reg.Register( + httpmock.GraphQL(query), + httpmock.StringResponse(fmt.Sprintf( + `{ "data": { "viewer": { "gists": { "nodes": [ + { + "name": "1234", + "files": [ + { "name": "main.txt", "text": "foo" } + ], + "description": "octo match in the description", + "updatedAt": "%[1]v", + "isPublic": true + }, + { + "name": "2345", + "files": [ + { "name": "main.txt", "text": "foo" }, + { "name": "octo.txt", "text": "bar" } + ], + "description": "match in the file name", + "updatedAt": "%[1]v", + "isPublic": false + }, + { + "name": "3456", + "files": [ + { "name": "main.txt", "text": "octo in the text" } + ], + "description": "match in the file text", + "updatedAt": "%[1]v", + "isPublic": true + } + ] } } } }`, + sixHoursAgo.Format(time.RFC3339), + )), + ) + }, + wantOut: heredoc.Doc(` + ID DESCRIPTION FILES VISIBILITY UPDATED + 1234 octo match in the description 1 file public about 6 hours ago + 2345 match in the file name 2 files secret about 6 hours ago + 3456 match in the file text 1 file public about 6 hours ago + `), + }, } for _, tt := range tests { diff --git a/pkg/cmd/gist/search/search.go b/pkg/cmd/gist/search/search.go deleted file mode 100644 index b1c04a2c7ac..00000000000 --- a/pkg/cmd/gist/search/search.go +++ /dev/null @@ -1,126 +0,0 @@ -package search - -import ( - "net/http" - "regexp" - - "github.com/MakeNowJust/heredoc" - "github.com/cli/cli/v2/internal/gh" - "github.com/cli/cli/v2/pkg/cmd/gist/shared" - "github.com/cli/cli/v2/pkg/cmdutil" - "github.com/cli/cli/v2/pkg/iostreams" - "github.com/spf13/cobra" -) - -type SearchOptions struct { - IO *iostreams.IOStreams - Config func() (gh.Config, error) - HttpClient func() (*http.Client, error) - - Pattern *regexp.Regexp - Filename bool - Code bool - Visibility string // all, secret, public - Limit int -} - -func NewCmdSearch(f *cmdutil.Factory, runF func(*SearchOptions) error) *cobra.Command { - opts := &SearchOptions{ - IO: f.IOStreams, - Config: f.Config, - HttpClient: f.HttpClient, - } - - var flagPublic bool - var flagSecret bool - - cmd := &cobra.Command{ - Use: "search ", - Short: "Search your gists", - Long: heredoc.Docf(` - Search your gists' for a case-sensitive POSIX regular expression. - - By default, all gists' descriptions are searched. Pass %[1]s--filename%[1]s to search - file names, or %[1]s--code%[1]s to search content as well. - `, "`"), - Example: heredoc.Doc(` - # search public gists' descriptions for "octo" - $ gh gist search --public octo - - # search all gists' descriptions, file names, and code for "foo|bar" - $ gh gist search --filename --code "foo|bar" - `), - Args: cmdutil.ExactArgs(1, "no search pattern passed"), - RunE: func(cmd *cobra.Command, args []string) error { - var err error - opts.Pattern, err = regexp.CompilePOSIX(args[0]) - if err != nil { - return err - } - - // Replicate precedence of existing `gist` commands instead of mutually exclusive arguments. - opts.Visibility = "all" - if flagSecret { - opts.Visibility = "secret" - } else if flagPublic { - opts.Visibility = "public" - } - - if runF != nil { - return runF(opts) - } - return searchRun(opts) - }, - } - - cmd.Flags().IntVarP(&opts.Limit, "limit", "L", 10, "Maximum number of gists to fetch") - cmd.Flags().BoolVar(&flagPublic, "public", false, "Show only public gists") - cmd.Flags().BoolVar(&flagSecret, "secret", false, "Show only secret gists") - cmd.Flags().BoolVar(&opts.Filename, "filename", false, "Include file names in search") - cmd.Flags().BoolVar(&opts.Code, "code", false, "Include code in search") - - return cmd -} - -func searchRun(opts *SearchOptions) error { - client, err := opts.HttpClient() - if err != nil { - return err - } - - cfg, err := opts.Config() - if err != nil { - return err - } - - host, _ := cfg.Authentication().DefaultHost() - - gists, err := shared.ListGists(client, host, opts.Limit, opts.Visibility, opts.Code, func(gist *shared.Gist) bool { - if opts.Pattern.MatchString(gist.Description) { - return true - } - - if opts.Filename || opts.Code { - for _, file := range gist.Files { - if opts.Filename && opts.Pattern.MatchString(file.Filename) { - return true - } - - if opts.Code && opts.Pattern.MatchString(file.Content) { - return true - } - } - } - - return false - }) - if err != nil { - return err - } - - if len(gists) == 0 { - return cmdutil.NewNoResultsError("no gists found") - } - - return shared.PrintGists(opts.IO, gists) -} diff --git a/pkg/cmd/gist/search/search_test.go b/pkg/cmd/gist/search/search_test.go deleted file mode 100644 index d6f98c1a3b2..00000000000 --- a/pkg/cmd/gist/search/search_test.go +++ /dev/null @@ -1,371 +0,0 @@ -package search - -import ( - "bytes" - "fmt" - "net/http" - "regexp" - "testing" - "time" - - "github.com/MakeNowJust/heredoc" - "github.com/cli/cli/v2/internal/config" - "github.com/cli/cli/v2/internal/gh" - "github.com/cli/cli/v2/pkg/cmdutil" - "github.com/cli/cli/v2/pkg/httpmock" - "github.com/cli/cli/v2/pkg/iostreams" - "github.com/google/shlex" - "github.com/stretchr/testify/assert" -) - -func TestNewCmdSearch(t *testing.T) { - tests := []struct { - name string - cli string - wants SearchOptions - wantsErr bool - }{ - { - name: "pattern only", - cli: "octo", - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Limit: 10, - Visibility: "all", - }, - }, - { - name: "public", - cli: "--public octo", - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Limit: 10, - Visibility: "public", - }, - }, - { - name: "secret", - cli: `"foo|bar" --secret`, - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("foo|bar"), - Limit: 10, - Visibility: "secret", - }, - }, - { - name: "secret with explicit false value", - cli: "--secret=false octo", - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Limit: 10, - Visibility: "all", - }, - }, - { - name: "public and secret", - cli: "--secret --public octo", - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Limit: 10, - Visibility: "secret", - }, - }, - { - name: "limit", - cli: "--limit 30 octo", - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Limit: 30, - Visibility: "all", - }, - }, - { - name: "all arguments", - cli: "--public --secret --filename --code --limit 30 octo", - wants: SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Filename: true, - Code: true, - Limit: 30, - Visibility: "secret", - }, - }, - { - name: "invalid regexp pattern", - cli: `invalid(\\`, - wantsErr: true, - }, - } - - 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 *SearchOptions - cmd := NewCmdSearch(f, func(opts *SearchOptions) error { - gotOpts = opts - return nil - }) - cmd.SetArgs(argv) - cmd.SetIn(&bytes.Buffer{}) - cmd.SetOut(&bytes.Buffer{}) - cmd.SetErr(&bytes.Buffer{}) - - _, err = cmd.ExecuteC() - if tt.wantsErr { - assert.Error(t, err) - return - } - assert.NoError(t, err) - - assert.Equal(t, tt.wants.Pattern.String(), gotOpts.Pattern.String()) - assert.Equal(t, tt.wants.Filename, gotOpts.Filename) - assert.Equal(t, tt.wants.Code, gotOpts.Code) - assert.Equal(t, tt.wants.Visibility, gotOpts.Visibility) - assert.Equal(t, tt.wants.Limit, gotOpts.Limit) - }) - } -} - -func Test_searchRun(t *testing.T) { - const query = `query GistList\b` - sixHours, _ := time.ParseDuration("6h") - sixHoursAgo := time.Now().Add(-sixHours) - - tests := []struct { - name string - opts *SearchOptions - wantErr bool - wantOut string - stubs func(*httpmock.Registry) - nontty bool - }{ - { - name: "no gists", - opts: &SearchOptions{}, - stubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(query), - httpmock.StringResponse(`{ "data": { "viewer": { - "gists": { "nodes": [] } - } } }`)) - }, - wantErr: true, - }, - { - name: "description only", - opts: &SearchOptions{ - Pattern: regexp.MustCompilePOSIX("foo"), - }, - stubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(query), - httpmock.StringResponse(fmt.Sprintf( - `{ "data": { "viewer": { "gists": { "nodes": [ - { - "name": "1234567890", - "files": [{ "name": "cool.txt" }], - "description": "foo example", - "updatedAt": "%[1]v", - "isPublic": true - }, - { - "name": "4567890123", - "files": [{ "name": "gistfile0.txt" }], - "description": "bar example", - "updatedAt": "%[1]v", - "isPublic": true - } - ] } } } }`, - sixHoursAgo.Format(time.RFC3339), - )), - ) - }, - wantOut: heredoc.Doc(` - ID DESCRIPTION FILES VISIBILITY UPDATED - 1234567890 foo example 1 file public about 6 hours ago - `), - }, - { - name: "no results with public filter", - opts: &SearchOptions{ - Pattern: regexp.MustCompilePOSIX("foo"), - Visibility: "public", - }, - stubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(query), - httpmock.StringResponse(fmt.Sprintf( - `{ "data": { "viewer": { "gists": { "nodes": [ - { - "name": "4567890123", - "files": [{ "name": "gistfile0.txt" }], - "description": "bar example", - "updatedAt": "%[1]v", - "isPublic": true - } - ] } } } }`, - sixHoursAgo.Format(time.RFC3339), - )), - ) - }, - wantErr: true, - }, - { - name: "description only", - opts: &SearchOptions{ - Pattern: regexp.MustCompilePOSIX("foo"), - }, - stubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(query), - httpmock.StringResponse(fmt.Sprintf( - `{ "data": { "viewer": { "gists": { "nodes": [ - { - "name": "1234567890", - "files": [{ "name": "cool.txt" }], - "description": "foo example", - "updatedAt": "%[1]v", - "isPublic": true - }, - { - "name": "4567890123", - "files": [{ "name": "gistfile0.txt" }], - "description": "bar example", - "updatedAt": "%[1]v", - "isPublic": true - } - ] } } } }`, - sixHoursAgo.Format(time.RFC3339), - )), - ) - }, - wantOut: heredoc.Doc(` - ID DESCRIPTION FILES VISIBILITY UPDATED - 1234567890 foo example 1 file public about 6 hours ago - `), - }, - { - name: "include filenames", - opts: &SearchOptions{ - Pattern: regexp.MustCompilePOSIX("gistfile7"), - Filename: true, - }, - stubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(query), - httpmock.StringResponse(fmt.Sprintf( - `{ "data": { "viewer": { "gists": { "nodes": [ - { - "name": "2345678901", - "files": [ - { "name": "gistfile0.txt" }, - { "name": "gistfile1.txt" } - ], - "description": "foo example", - "updatedAt": "%[1]v", - "isPublic": true - }, - { - "name": "3456789012", - "files": [ - { "name": "gistfile0.txt" }, - { "name": "gistfile1.txt" }, - { "name": "gistfile2.txt" }, - { "name": "gistfile3.txt" }, - { "name": "gistfile4.txt" }, - { "name": "gistfile5.txt" }, - { "name": "gistfile6.txt" }, - { "name": "gistfile7.txt" }, - { "name": "gistfile9.txt" }, - { "name": "gistfile10.txt" }, - { "name": "gistfile11.txt" } - ], - "description": "bar example", - "updatedAt": "%[1]v", - "isPublic": false - } - ] } } } }`, - sixHoursAgo.Format(time.RFC3339), - )), - ) - }, - wantOut: heredoc.Doc(` - ID DESCRIPTION FILES VISIBILITY UPDATED - 3456789012 bar example 11 files secret about 6 hours ago - `), - }, - { - name: "include code", - opts: &SearchOptions{ - Pattern: regexp.MustCompilePOSIX("octo"), - Code: true, - }, - stubs: func(reg *httpmock.Registry) { - reg.Register( - httpmock.GraphQL(query), - httpmock.StringResponse(fmt.Sprintf( - `{ "data": { "viewer": { "gists": { "nodes": [ - { - "name": "2345678901", - "files": [ - { - "name": "gistfile0.txt", - "text": "octo" - } - ], - "description": "foo example", - "updatedAt": "%[1]v", - "isPublic": true - } - ] } } } }`, - sixHoursAgo.Format(time.RFC3339), - )), - ) - }, - wantOut: heredoc.Doc(` - ID DESCRIPTION FILES VISIBILITY UPDATED - 2345678901 foo example 1 file public about 6 hours ago - `), - }, - } - - for _, tt := range tests { - reg := &httpmock.Registry{} - tt.stubs(reg) - - tt.opts.HttpClient = func() (*http.Client, error) { - return &http.Client{Transport: reg}, nil - } - - tt.opts.Config = func() (gh.Config, error) { - return config.NewBlankConfig(), nil - } - - ios, _, stdout, _ := iostreams.Test() - ios.SetStdoutTTY(!tt.nontty) - tt.opts.IO = ios - - if tt.opts.Limit == 0 { - tt.opts.Limit = 10 - } - - if tt.opts.Visibility == "" { - tt.opts.Visibility = "all" - } - t.Run(tt.name, func(t *testing.T) { - err := searchRun(tt.opts) - if tt.wantErr { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - - assert.Equal(t, tt.wantOut, stdout.String()) - reg.Verify(t) - }) - } -} diff --git a/pkg/cmd/gist/shared/shared.go b/pkg/cmd/gist/shared/shared.go index a616ca9b6f7..ee6dcf1e3f5 100644 --- a/pkg/cmd/gist/shared/shared.go +++ b/pkg/cmd/gist/shared/shared.go @@ -5,13 +5,13 @@ import ( "fmt" "net/http" "net/url" + "regexp" "sort" "strings" "time" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/prompter" - "github.com/cli/cli/v2/internal/tableprinter" "github.com/cli/cli/v2/internal/text" "github.com/cli/cli/v2/pkg/iostreams" "github.com/gabriel-vasile/mimetype" @@ -77,7 +77,7 @@ func GistIDFromURL(gistURL string) (string, error) { const maxPerPage = 100 -func ListGists(client *http.Client, hostname string, limit int, visibility string, includeText bool, filter func(*Gist) bool) ([]Gist, error) { +func ListGists(client *http.Client, hostname string, limit int, filter *regexp.Regexp, includeContent bool, visibility string) ([]Gist, error) { type response struct { Viewer struct { Gists struct { @@ -85,7 +85,7 @@ func ListGists(client *http.Client, hostname string, limit int, visibility strin Description string Files []struct { Name string - Text string `graphql:"text @include(if: $includeText)"` + Text string `graphql:"text @include(if: $includeContent)"` } IsPublic bool Name string @@ -105,10 +105,28 @@ func ListGists(client *http.Client, hostname string, limit int, visibility strin } variables := map[string]interface{}{ - "per_page": githubv4.Int(perPage), - "endCursor": (*githubv4.String)(nil), - "visibility": githubv4.GistPrivacy(strings.ToUpper(visibility)), - "includeText": githubv4.Boolean(includeText), + "per_page": githubv4.Int(perPage), + "endCursor": (*githubv4.String)(nil), + "visibility": githubv4.GistPrivacy(strings.ToUpper(visibility)), + "includeContent": githubv4.Boolean(includeContent), + } + + filterFunc := func(gist *Gist) bool { + if filter.MatchString(gist.Description) { + return true + } + + for _, file := range gist.Files { + if filter.MatchString(file.Filename) { + return true + } + + if includeContent && filter.MatchString(file.Content) { + return true + } + } + + return false } gql := api.NewClientFromHTTP(client) @@ -139,7 +157,7 @@ pagination: Public: gist.IsPublic, } - if filter == nil || filter(&gist) { + if filter == nil || filterFunc(&gist) { gists = append(gists, gist) } @@ -185,7 +203,7 @@ func IsBinaryContents(contents []byte) bool { } func PromptGists(prompter prompter.Prompter, client *http.Client, host string, cs *iostreams.ColorScheme) (gistID string, err error) { - gists, err := ListGists(client, host, 10, "all", false, nil) + gists, err := ListGists(client, host, 10, nil, false, "all") if err != nil { return "", err } @@ -228,47 +246,3 @@ func PromptGists(prompter prompter.Prompter, client *http.Client, host string, c return gistIDs[result], nil } - -func PrintGists(io *iostreams.IOStreams, gists []Gist) error { - if err := io.StartPager(); err == nil { - defer io.StopPager() - } else { - fmt.Fprintf(io.ErrOut, "failed to start pager: %v\n", err) - } - - cs := io.ColorScheme() - tp := tableprinter.New(io, tableprinter.WithHeader("ID", "DESCRIPTION", "FILES", "VISIBILITY", "UPDATED")) - - for _, gist := range gists { - fileCount := len(gist.Files) - - visibility := "public" - visColor := cs.Green - if !gist.Public { - visibility = "secret" - visColor = cs.Red - } - - description := gist.Description - if description == "" { - for filename := range gist.Files { - if !strings.HasPrefix(filename, "gistfile") { - description = filename - break - } - } - } - - tp.AddField(gist.ID) - tp.AddField( - text.RemoveExcessiveWhitespace(description), - tableprinter.WithColor(cs.Bold), - ) - tp.AddField(text.Pluralize(fileCount, "file")) - tp.AddField(visibility, tableprinter.WithColor(visColor)) - tp.AddTimeField(time.Now(), gist.UpdatedAt, cs.Gray) - tp.EndRow() - } - - return tp.Render() -}