Add gist selector option to gist edit command

This commit is contained in:
Kousik Mitra 2023-06-05 22:07:04 +05:30
parent 420f63c3ec
commit 9ccd098eb0
No known key found for this signature in database
5 changed files with 190 additions and 166 deletions

View file

@ -55,16 +55,15 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
Use: "edit {<id> | <url>} [<filename>]",
Short: "Edit one of your gists",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return cmdutil.FlagErrorf("cannot edit: gist argument required")
}
if len(args) > 2 {
return cmdutil.FlagErrorf("too many arguments")
}
return nil
},
RunE: func(c *cobra.Command, args []string) error {
opts.Selector = args[0]
if len(args) > 0 {
opts.Selector = args[0]
}
if len(args) > 1 {
opts.SourceFile = args[1]
}
@ -85,7 +84,33 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
}
func editRun(opts *EditOptions) error {
client, err := opts.HttpClient()
if err != nil {
return err
}
cfg, err := opts.Config()
if err != nil {
return err
}
host, _ := cfg.Authentication().DefaultHost()
gistID := opts.Selector
if gistID == "" {
cs := opts.IO.ColorScheme()
if gistID == "" {
gistID, err = shared.PromptGists(client, host, cs)
if err != nil {
return err
}
if gistID == "" {
fmt.Fprintln(opts.IO.Out, "No gists found.")
return nil
}
}
}
if strings.Contains(gistID, "/") {
id, err := shared.GistIDFromURL(gistID)
@ -95,20 +120,8 @@ func editRun(opts *EditOptions) error {
gistID = id
}
client, err := opts.HttpClient()
if err != nil {
return err
}
apiClient := api.NewClientFromHTTP(client)
cfg, err := opts.Config()
if err != nil {
return err
}
host, _ := cfg.Authentication().DefaultHost()
gist, err := shared.GetGist(client, host, gistID)
if err != nil {
if errors.Is(err, shared.NotFoundErr) {

View file

@ -5,10 +5,15 @@ import (
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/text"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/gabriel-vasile/mimetype"
"github.com/shurcooL/githubv4"
)
@ -171,3 +176,55 @@ func IsBinaryContents(contents []byte) bool {
}
return isBinary
}
func PromptGists(client *http.Client, host string, cs *iostreams.ColorScheme) (gistID string, err error) {
gists, err := ListGists(client, host, 10, "all")
if err != nil {
return "", err
}
if len(gists) == 0 {
return "", nil
}
var opts []string
var result int
var gistIDs = make([]string, len(gists))
for i, gist := range gists {
gistIDs[i] = gist.ID
description := ""
gistName := ""
if gist.Description != "" {
description = gist.Description
}
filenames := make([]string, 0, len(gist.Files))
for fn := range gist.Files {
filenames = append(filenames, fn)
}
sort.Strings(filenames)
gistName = filenames[0]
gistTime := text.FuzzyAgo(time.Now(), gist.UpdatedAt)
// TODO: support dynamic maxWidth
description = text.Truncate(100, text.RemoveExcessiveWhitespace(description))
opt := fmt.Sprintf("%s %s %s", cs.Bold(gistName), description, cs.Gray(gistTime))
opts = append(opts, opt)
}
questions := &survey.Select{
Message: "Select a gist",
Options: opts,
}
//nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter
err = prompt.SurveyAskOne(questions, &result)
if err != nil {
return "", err
}
return gistIDs[result], nil
}

View file

@ -1,8 +1,14 @@
package shared
import (
"fmt"
"net/http"
"testing"
"time"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/stretchr/testify/assert"
)
@ -85,3 +91,97 @@ func TestIsBinaryContents(t *testing.T) {
assert.Equal(t, tt.want, IsBinaryContents(tt.fileContent))
}
}
func TestPromptGists(t *testing.T) {
tests := []struct {
name string
askStubs func(as *prompt.AskStubber)
response string
wantOut string
gist *Gist
wantErr bool
}{
{
name: "multiple files, select first gist",
askStubs: func(as *prompt.AskStubber) {
as.StubPrompt("Select a gist").AnswerWith("cool.txt about 6 hours ago")
},
response: `{ "data": { "viewer": { "gists": { "nodes": [
{
"name": "gistid1",
"files": [{ "name": "cool.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
},
{
"name": "gistid2",
"files": [{ "name": "gistfile0.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
}
] } } } }`,
wantOut: "gistid1",
},
{
name: "multiple files, select second gist",
askStubs: func(as *prompt.AskStubber) {
as.StubPrompt("Select a gist").AnswerWith("gistfile0.txt about 6 hours ago")
},
response: `{ "data": { "viewer": { "gists": { "nodes": [
{
"name": "gistid1",
"files": [{ "name": "cool.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
},
{
"name": "gistid2",
"files": [{ "name": "gistfile0.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
}
] } } } }`,
wantOut: "gistid2",
},
{
name: "no files",
response: `{ "data": { "viewer": { "gists": { "nodes": [] } } } }`,
wantOut: "",
},
}
ios, _, _, _ := iostreams.Test()
for _, tt := range tests {
reg := &httpmock.Registry{}
const query = `query GistList\b`
sixHours, _ := time.ParseDuration("6h")
sixHoursAgo := time.Now().Add(-sixHours)
reg.Register(
httpmock.GraphQL(query),
httpmock.StringResponse(fmt.Sprintf(
tt.response,
sixHoursAgo.Format(time.RFC3339),
)),
)
client := &http.Client{Transport: reg}
t.Run(tt.name, func(t *testing.T) {
//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
gistID, err := PromptGists(client, "github.com", ios.ColorScheme())
assert.NoError(t, err)
assert.Equal(t, tt.wantOut, gistID)
reg.Verify(t)
})
}
}

View file

@ -5,17 +5,15 @@ import (
"net/http"
"sort"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/internal/config"
"github.com/cli/cli/v2/internal/ghinstance"
"github.com/cli/cli/v2/internal/prompter"
"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"
"github.com/cli/cli/v2/pkg/markdown"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/spf13/cobra"
)
@ -28,6 +26,7 @@ type ViewOptions struct {
Config func() (config.Config, error)
HttpClient func() (*http.Client, error)
Browser browser
Prompter prompter.Prompter
Selector string
Filename string
@ -42,6 +41,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
Config: f.Config,
HttpClient: f.HttpClient,
Browser: f.Browser,
Prompter: f.Prompter,
}
cmd := &cobra.Command{
@ -89,7 +89,7 @@ func viewRun(opts *ViewOptions) error {
cs := opts.IO.ColorScheme()
if gistID == "" {
gistID, err = promptGists(client, hostname, cs)
gistID, err = shared.PromptGists(client, hostname, cs)
if err != nil {
return err
}
@ -204,55 +204,3 @@ func viewRun(opts *ViewOptions) error {
return nil
}
func promptGists(client *http.Client, host string, cs *iostreams.ColorScheme) (gistID string, err error) {
gists, err := shared.ListGists(client, host, 10, "all")
if err != nil {
return "", err
}
if len(gists) == 0 {
return "", nil
}
var opts []string
var result int
var gistIDs = make([]string, len(gists))
for i, gist := range gists {
gistIDs[i] = gist.ID
description := ""
gistName := ""
if gist.Description != "" {
description = gist.Description
}
filenames := make([]string, 0, len(gist.Files))
for fn := range gist.Files {
filenames = append(filenames, fn)
}
sort.Strings(filenames)
gistName = filenames[0]
gistTime := text.FuzzyAgo(time.Now(), gist.UpdatedAt)
// TODO: support dynamic maxWidth
description = text.Truncate(100, text.RemoveExcessiveWhitespace(description))
opt := fmt.Sprintf("%s %s %s", cs.Bold(gistName), description, cs.Gray(gistTime))
opts = append(opts, opt)
}
questions := &survey.Select{
Message: "Select a gist",
Options: opts,
}
//nolint:staticcheck // SA1019: prompt.SurveyAskOne is deprecated: use Prompter
err = prompt.SurveyAskOne(questions, &result)
if err != nil {
return "", err
}
return gistIDs[result], nil
}

View file

@ -389,97 +389,3 @@ func Test_viewRun(t *testing.T) {
})
}
}
func Test_promptGists(t *testing.T) {
tests := []struct {
name string
askStubs func(as *prompt.AskStubber)
response string
wantOut string
gist *shared.Gist
wantErr bool
}{
{
name: "multiple files, select first gist",
askStubs: func(as *prompt.AskStubber) {
as.StubPrompt("Select a gist").AnswerWith("cool.txt about 6 hours ago")
},
response: `{ "data": { "viewer": { "gists": { "nodes": [
{
"name": "gistid1",
"files": [{ "name": "cool.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
},
{
"name": "gistid2",
"files": [{ "name": "gistfile0.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
}
] } } } }`,
wantOut: "gistid1",
},
{
name: "multiple files, select second gist",
askStubs: func(as *prompt.AskStubber) {
as.StubPrompt("Select a gist").AnswerWith("gistfile0.txt about 6 hours ago")
},
response: `{ "data": { "viewer": { "gists": { "nodes": [
{
"name": "gistid1",
"files": [{ "name": "cool.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
},
{
"name": "gistid2",
"files": [{ "name": "gistfile0.txt" }],
"description": "",
"updatedAt": "%[1]v",
"isPublic": true
}
] } } } }`,
wantOut: "gistid2",
},
{
name: "no files",
response: `{ "data": { "viewer": { "gists": { "nodes": [] } } } }`,
wantOut: "",
},
}
ios, _, _, _ := iostreams.Test()
for _, tt := range tests {
reg := &httpmock.Registry{}
const query = `query GistList\b`
sixHours, _ := time.ParseDuration("6h")
sixHoursAgo := time.Now().Add(-sixHours)
reg.Register(
httpmock.GraphQL(query),
httpmock.StringResponse(fmt.Sprintf(
tt.response,
sixHoursAgo.Format(time.RFC3339),
)),
)
client := &http.Client{Transport: reg}
t.Run(tt.name, func(t *testing.T) {
//nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock
as := prompt.NewAskStubber(t)
if tt.askStubs != nil {
tt.askStubs(as)
}
gistID, err := promptGists(client, "github.com", ios.ColorScheme())
assert.NoError(t, err)
assert.Equal(t, tt.wantOut, gistID)
reg.Verify(t)
})
}
}