Merge pull request #4656 from tdakkota/feat/gist-edit
gist edit: add way to edit files from different sources
This commit is contained in:
commit
2037fa4279
4 changed files with 229 additions and 35 deletions
|
|
@ -1,7 +1,7 @@
|
|||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
|
|
@ -80,7 +80,7 @@ func deleteRun(opts *DeleteOptions) error {
|
|||
}
|
||||
|
||||
if username != gist.Owner.Login {
|
||||
return fmt.Errorf("You do not own this gist.")
|
||||
return errors.New("you do not own this gist")
|
||||
}
|
||||
|
||||
err = deleteGist(apiClient, host, gistID)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func Test_deleteRun(t *testing.T) {
|
|||
Owner: &shared.GistOwner{Login: "octocat2"},
|
||||
},
|
||||
wantErr: true,
|
||||
wantStderr: "You do not own this gist.",
|
||||
wantStderr: "you do not own this gist",
|
||||
}, {
|
||||
name: "successfully delete",
|
||||
gist: &shared.Gist{
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -32,6 +33,7 @@ type EditOptions struct {
|
|||
Selector string
|
||||
EditFilename string
|
||||
AddFilename string
|
||||
SourceFile string
|
||||
Description string
|
||||
}
|
||||
|
||||
|
|
@ -50,11 +52,22 @@ func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Comman
|
|||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit {<id> | <url>}",
|
||||
Use: "edit {<id> | <url>} [<filename>]",
|
||||
Short: "Edit one of your gists",
|
||||
Args: cmdutil.ExactArgs(1, "cannot edit: gist argument required"),
|
||||
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) > 1 {
|
||||
opts.SourceFile = args[1]
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(&opts)
|
||||
|
|
@ -113,7 +126,7 @@ func editRun(opts *EditOptions) error {
|
|||
}
|
||||
|
||||
if username != gist.Owner.Login {
|
||||
return fmt.Errorf("You do not own this gist.")
|
||||
return errors.New("you do not own this gist")
|
||||
}
|
||||
|
||||
shouldUpdate := false
|
||||
|
|
@ -123,7 +136,36 @@ func editRun(opts *EditOptions) error {
|
|||
}
|
||||
|
||||
if opts.AddFilename != "" {
|
||||
files, err := getFilesToAdd(opts.AddFilename)
|
||||
var input io.Reader
|
||||
switch src := opts.SourceFile; {
|
||||
case src == "-":
|
||||
input = opts.IO.In
|
||||
case src != "":
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
input = f
|
||||
default:
|
||||
f, err := os.Open(opts.AddFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
input = f
|
||||
}
|
||||
|
||||
content, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read content: %w", err)
|
||||
}
|
||||
|
||||
files, err := getFilesToAdd(opts.AddFilename, content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -169,13 +211,32 @@ func editRun(opts *EditOptions) error {
|
|||
return fmt.Errorf("editing binary files not supported")
|
||||
}
|
||||
|
||||
editorCommand, err := cmdutil.DetermineEditor(opts.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text, err := opts.Edit(editorCommand, filename, gistFile.Content, opts.IO)
|
||||
if err != nil {
|
||||
return err
|
||||
var text string
|
||||
if src := opts.SourceFile; src != "" {
|
||||
if src == "-" {
|
||||
data, err := io.ReadAll(opts.IO.In)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read from stdin: %w", err)
|
||||
}
|
||||
text = string(data)
|
||||
} else {
|
||||
data, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read %s: %w", src, err)
|
||||
}
|
||||
text = string(data)
|
||||
}
|
||||
} else {
|
||||
editorCommand, err := cmdutil.DetermineEditor(opts.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := opts.Edit(editorCommand, filename, gistFile.Content, opts.IO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
text = data
|
||||
}
|
||||
|
||||
if text != gistFile.Content {
|
||||
|
|
@ -259,20 +320,11 @@ func updateGist(apiClient *api.Client, hostname string, gist *shared.Gist) error
|
|||
return nil
|
||||
}
|
||||
|
||||
func getFilesToAdd(file string) (map[string]*shared.GistFile, error) {
|
||||
isBinary, err := shared.IsBinaryFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file %s: %w", file, err)
|
||||
}
|
||||
if isBinary {
|
||||
func getFilesToAdd(file string, content []byte) (map[string]*shared.GistFile, error) {
|
||||
if shared.IsBinaryContents(content) {
|
||||
return nil, fmt.Errorf("failed to upload %s: binary file not supported", file)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read file %s: %w", file, err)
|
||||
}
|
||||
|
||||
if len(content) == 0 {
|
||||
return nil, errors.New("file contents cannot be empty")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,11 @@ import (
|
|||
)
|
||||
|
||||
func Test_getFilesToAdd(t *testing.T) {
|
||||
fileToAdd := filepath.Join(t.TempDir(), "gist-test.txt")
|
||||
err := ioutil.WriteFile(fileToAdd, []byte("hello"), 0600)
|
||||
filename := "gist-test.txt"
|
||||
|
||||
gf, err := getFilesToAdd(filename, []byte("hello"))
|
||||
require.NoError(t, err)
|
||||
|
||||
gf, err := getFilesToAdd(fileToAdd)
|
||||
require.NoError(t, err)
|
||||
|
||||
filename := filepath.Base(fileToAdd)
|
||||
assert.Equal(t, map[string]*shared.GistFile{
|
||||
filename: {
|
||||
Filename: filename,
|
||||
|
|
@ -65,6 +62,15 @@ func TestNewCmdEdit(t *testing.T) {
|
|||
AddFilename: "cool.md",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add with source",
|
||||
cli: "123 --add cool.md -",
|
||||
wants: EditOptions{
|
||||
Selector: "123",
|
||||
AddFilename: "cool.md",
|
||||
SourceFile: "-",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
cli: `123 --desc "my new description"`,
|
||||
|
|
@ -114,6 +120,7 @@ func Test_editRun(t *testing.T) {
|
|||
httpStubs func(*httpmock.Registry)
|
||||
askStubs func(*prompt.AskStubber)
|
||||
nontty bool
|
||||
stdin string
|
||||
wantErr string
|
||||
wantParams map[string]interface{}
|
||||
}{
|
||||
|
|
@ -247,7 +254,7 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
Owner: &shared.GistOwner{Login: "octocat2"},
|
||||
},
|
||||
wantErr: "You do not own this gist.",
|
||||
wantErr: "you do not own this gist",
|
||||
},
|
||||
{
|
||||
name: "add file to existing gist",
|
||||
|
|
@ -272,6 +279,9 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "change description",
|
||||
opts: &EditOptions{
|
||||
Description: "my new description",
|
||||
},
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Description: "my old description",
|
||||
|
|
@ -299,8 +309,139 @@ func Test_editRun(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add file to existing gist from source parameter",
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"sample.txt": {
|
||||
Filename: "sample.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
Owner: &shared.GistOwner{Login: "octocat"},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
opts: &EditOptions{
|
||||
Description: "my new description",
|
||||
AddFilename: "from_source.txt",
|
||||
SourceFile: fileToAdd,
|
||||
},
|
||||
wantParams: map[string]interface{}{
|
||||
"description": "",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"public": false,
|
||||
"files": map[string]interface{}{
|
||||
"from_source.txt": map[string]interface{}{
|
||||
"content": "hello",
|
||||
"filename": "from_source.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add file to existing gist from stdin",
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"sample.txt": {
|
||||
Filename: "sample.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
Owner: &shared.GistOwner{Login: "octocat"},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
opts: &EditOptions{
|
||||
AddFilename: "from_source.txt",
|
||||
SourceFile: "-",
|
||||
},
|
||||
stdin: "data from stdin",
|
||||
wantParams: map[string]interface{}{
|
||||
"description": "",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"public": false,
|
||||
"files": map[string]interface{}{
|
||||
"from_source.txt": map[string]interface{}{
|
||||
"content": "data from stdin",
|
||||
"filename": "from_source.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "edit gist using file from source parameter",
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"sample.txt": {
|
||||
Filename: "sample.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
Owner: &shared.GistOwner{Login: "octocat"},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
opts: &EditOptions{
|
||||
SourceFile: fileToAdd,
|
||||
},
|
||||
wantParams: map[string]interface{}{
|
||||
"description": "",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"public": false,
|
||||
"files": map[string]interface{}{
|
||||
"sample.txt": map[string]interface{}{
|
||||
"content": "hello",
|
||||
"filename": "sample.txt",
|
||||
"type": "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "edit gist using stdin",
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"sample.txt": {
|
||||
Filename: "sample.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
Owner: &shared.GistOwner{Login: "octocat"},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
opts: &EditOptions{
|
||||
SourceFile: "-",
|
||||
},
|
||||
stdin: "data from stdin",
|
||||
wantParams: map[string]interface{}{
|
||||
"description": "",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"public": false,
|
||||
"files": map[string]interface{}{
|
||||
"sample.txt": map[string]interface{}{
|
||||
"content": "data from stdin",
|
||||
"filename": "sample.txt",
|
||||
"type": "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -332,7 +473,8 @@ func Test_editRun(t *testing.T) {
|
|||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
io, _, stdout, stderr := iostreams.Test()
|
||||
io, stdin, stdout, stderr := iostreams.Test()
|
||||
stdin.WriteString(tt.stdin)
|
||||
io.SetStdoutTTY(!tt.nontty)
|
||||
io.SetStdinTTY(!tt.nontty)
|
||||
tt.opts.IO = io
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue