Merge pull request #7212 from wingkwong/feat/gist-rename
feat: gist rename
This commit is contained in:
commit
9cf6f77a1f
3 changed files with 327 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
gistDeleteCmd "github.com/cli/cli/v2/pkg/cmd/gist/delete"
|
||||
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"
|
||||
gistViewCmd "github.com/cli/cli/v2/pkg/cmd/gist/view"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -33,6 +34,7 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command {
|
|||
cmd.AddCommand(gistViewCmd.NewCmdView(f, nil))
|
||||
cmd.AddCommand(gistEditCmd.NewCmdEdit(f, nil))
|
||||
cmd.AddCommand(gistDeleteCmd.NewCmdDelete(f, nil))
|
||||
cmd.AddCommand(gistRenameCmd.NewCmdRename(f, nil))
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
140
pkg/cmd/gist/rename/rename.go
Normal file
140
pkg/cmd/gist/rename/rename.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
package rename
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"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 RenameOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (config.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
|
||||
Selector string
|
||||
OldFileName string
|
||||
NewFileName string
|
||||
}
|
||||
|
||||
func NewCmdRename(f *cmdutil.Factory, runf func(*RenameOptions) error) *cobra.Command {
|
||||
opts := &RenameOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
Config: f.Config,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rename {<id> | <url>} <oldFilename> <newFilename>",
|
||||
Short: "Rename a file in a gist",
|
||||
Long: heredoc.Doc(`Rename a file in the given gist ID / URL.`),
|
||||
Args: cobra.ExactArgs(3),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.Selector = args[0]
|
||||
opts.OldFileName = args[1]
|
||||
opts.NewFileName = args[2]
|
||||
|
||||
if runf != nil {
|
||||
return runf(opts)
|
||||
}
|
||||
|
||||
return renameRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func renameRun(opts *RenameOptions) error {
|
||||
gistID := opts.Selector
|
||||
|
||||
if strings.Contains(gistID, "/") {
|
||||
id, err := shared.GistIDFromURL(gistID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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) {
|
||||
return fmt.Errorf("gist not found: %s", gistID)
|
||||
}
|
||||
return err
|
||||
}
|
||||
username, err := api.CurrentLoginName(apiClient, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if username != gist.Owner.Login {
|
||||
return errors.New("you do not own this gist")
|
||||
}
|
||||
|
||||
_, ok := gist.Files[opts.OldFileName]
|
||||
if !ok {
|
||||
return fmt.Errorf("File %s not found in gist", opts.OldFileName)
|
||||
}
|
||||
|
||||
_, ok = gist.Files[opts.NewFileName]
|
||||
if ok {
|
||||
return fmt.Errorf("File %s already exists in gist", opts.NewFileName)
|
||||
}
|
||||
|
||||
gist.Files[opts.NewFileName] = gist.Files[opts.OldFileName]
|
||||
gist.Files[opts.NewFileName].Filename = opts.NewFileName
|
||||
gist.Files[opts.OldFileName] = &shared.GistFile{}
|
||||
|
||||
return updateGist(apiClient, host, gist)
|
||||
}
|
||||
|
||||
func updateGist(apiClient *api.Client, hostname string, gist *shared.Gist) error {
|
||||
body := shared.Gist{
|
||||
Description: gist.Description,
|
||||
Files: gist.Files,
|
||||
}
|
||||
|
||||
path := "gists/" + gist.ID
|
||||
|
||||
requestByte, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestBody := bytes.NewReader(requestByte)
|
||||
|
||||
result := shared.Gist{}
|
||||
|
||||
err = apiClient.REST(hostname, "POST", path, requestBody, &result)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
185
pkg/cmd/gist/rename/rename_test.go
Normal file
185
pkg/cmd/gist/rename/rename_test.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
package rename
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/gist/shared"
|
||||
"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 TestNewCmdRename(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
output RenameOptions
|
||||
errMsg string
|
||||
tty bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
input: "",
|
||||
errMsg: "accepts 3 arg(s), received 0",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing old filename and new filename",
|
||||
input: "123",
|
||||
errMsg: "accepts 3 arg(s), received 1",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing new filename",
|
||||
input: "123 old.txt",
|
||||
errMsg: "accepts 3 arg(s), received 2",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "rename",
|
||||
input: "123 old.txt new.txt",
|
||||
output: RenameOptions{
|
||||
Selector: "123",
|
||||
OldFileName: "old.txt",
|
||||
NewFileName: "new.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
ios.SetStdinTTY(tt.tty)
|
||||
ios.SetStdoutTTY(tt.tty)
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
}
|
||||
|
||||
argv, err := shlex.Split(tt.input)
|
||||
assert.NoError(t, err)
|
||||
var gotOpts *RenameOptions
|
||||
cmd := NewCmdRename(f, func(opts *RenameOptions) 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.wantErr {
|
||||
assert.EqualError(t, err, tt.errMsg)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.output.Selector, gotOpts.Selector)
|
||||
assert.Equal(t, tt.output.OldFileName, gotOpts.OldFileName)
|
||||
assert.Equal(t, tt.output.NewFileName, gotOpts.NewFileName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *RenameOptions
|
||||
gist *shared.Gist
|
||||
httpStubs func(*httpmock.Registry)
|
||||
nontty bool
|
||||
stdin string
|
||||
wantOut string
|
||||
wantParams map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "no such gist",
|
||||
wantOut: "gist not found: 1234",
|
||||
},
|
||||
{
|
||||
name: "rename from old.txt to new.txt",
|
||||
opts: &RenameOptions{
|
||||
Selector: "1234",
|
||||
OldFileName: "old.txt",
|
||||
NewFileName: "new.txt",
|
||||
},
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"old.txt": {
|
||||
Filename: "old.txt",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
Owner: &shared.GistOwner{Login: "octocat"},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
wantParams: map[string]interface{}{
|
||||
"files": map[string]interface{}{
|
||||
"new.txt": map[string]interface{}{
|
||||
"filename": "new.txt",
|
||||
"type": "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
reg := &httpmock.Registry{}
|
||||
if tt.gist == nil {
|
||||
reg.Register(httpmock.REST("GET", "gists/1234"),
|
||||
httpmock.StatusStringResponse(404, "Not Found"))
|
||||
} else {
|
||||
reg.Register(httpmock.REST("GET", "gists/1234"),
|
||||
httpmock.JSONResponse(tt.gist))
|
||||
reg.Register(httpmock.GraphQL(`query UserCurrent\b`),
|
||||
httpmock.StringResponse(`{"data":{"viewer":{"login":"octocat"}}}`))
|
||||
}
|
||||
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
|
||||
if tt.opts == nil {
|
||||
tt.opts = &RenameOptions{}
|
||||
}
|
||||
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
ios, stdin, stdout, stderr := iostreams.Test()
|
||||
stdin.WriteString(tt.stdin)
|
||||
ios.SetStdoutTTY(!tt.nontty)
|
||||
ios.SetStdinTTY(!tt.nontty)
|
||||
|
||||
tt.opts.Selector = "1234"
|
||||
tt.opts.OldFileName = "old.txt"
|
||||
tt.opts.NewFileName = "new.txt"
|
||||
tt.opts.IO = ios
|
||||
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
}
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := renameRun(tt.opts)
|
||||
reg.Verify(t)
|
||||
if tt.wantOut != "" {
|
||||
assert.EqualError(t, err, tt.wantOut)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", stdout.String())
|
||||
assert.Equal(t, "", stderr.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue