Add gist selector option to gist edit command
This commit is contained in:
parent
420f63c3ec
commit
9ccd098eb0
5 changed files with 190 additions and 166 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue