Merge pull request #4656 from tdakkota/feat/gist-edit

gist edit: add way to edit files from different sources
This commit is contained in:
Nate Smith 2022-01-24 17:42:12 -06:00 committed by GitHub
commit 2037fa4279
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 229 additions and 35 deletions

View file

@ -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)

View file

@ -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{

View file

@ -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")
}

View file

@ -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