diff --git a/pkg/cmd/gist/edit/edit.go b/pkg/cmd/gist/edit/edit.go new file mode 100644 index 000000000..097cfad6b --- /dev/null +++ b/pkg/cmd/gist/edit/edit.go @@ -0,0 +1,203 @@ +package edit + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "os" + "sort" + "strings" + + "github.com/AlecAivazis/survey/v2" + "github.com/cli/cli/api" + "github.com/cli/cli/internal/config" + "github.com/cli/cli/internal/ghinstance" + "github.com/cli/cli/pkg/cmd/gist/shared" + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/iostreams" + "github.com/cli/cli/pkg/prompt" + "github.com/cli/cli/pkg/surveyext" + "github.com/spf13/cobra" +) + +type EditOptions struct { + IO *iostreams.IOStreams + HttpClient func() (*http.Client, error) + Config func() (config.Config, error) + + Selector string + Filename string +} + +func NewCmdEdit(f *cmdutil.Factory, runF func(*EditOptions) error) *cobra.Command { + opts := EditOptions{ + IO: f.IOStreams, + HttpClient: f.HttpClient, + Config: f.Config, + } + + cmd := &cobra.Command{ + Use: "edit { | }", + Short: "Edit one of your gists", + Args: cobra.ExactArgs(1), + RunE: func(c *cobra.Command, args []string) error { + opts.Selector = args[0] + + if runF != nil { + return runF(&opts) + } + + return editRun(&opts) + }, + } + cmd.Flags().StringVarP(&opts.Filename, "filename", "f", "", "a specific file to edit") + + return cmd +} + +func editRun(opts *EditOptions) error { + gistID := opts.Selector + + u, err := url.Parse(opts.Selector) + if err == nil { + if strings.HasPrefix(u.Path, "/") { + gistID = u.Path[1:] + } + } + + client, err := opts.HttpClient() + if err != nil { + return err + } + + gist, err := shared.GetGist(client, ghinstance.OverridableDefault(), gistID) + if err != nil { + return err + } + + filesToUpdate := map[string]string{} + + for true { + filename := opts.Filename + candidates := []string{} + for filename, _ := range gist.Files { + candidates = append(candidates, filename) + } + + sort.Strings(candidates) + + if filename == "" { + if len(candidates) == 1 { + filename = candidates[0] + } else { + if !opts.IO.CanPrompt() { + return errors.New("unsure what file to edit; either specify --filename or run interactively") + } + err = prompt.SurveyAskOne(&survey.Select{ + Message: "Edit which file?", + Options: candidates, + }, &filename) + } + } + + if _, ok := gist.Files[filename]; !ok { + return fmt.Errorf("gist has no file %q", filename) + } + + editorCommand, err := cmdutil.DetermineEditor(opts.Config) + if err != nil { + return err + } + text, err := surveyext.Edit( + editorCommand, + "*."+filename, + gist.Files[filename].Content, + // TODO: consider using iostreams here + os.Stdin, os.Stdout, os.Stderr, nil) + + if err != nil { + return err + } + + if text != gist.Files[filename].Content { + gistFile := gist.Files[filename] + gistFile.Content = text // so it appears if they re-edit + filesToUpdate[filename] = text + } + + if !opts.IO.CanPrompt() { + break + } + + if len(candidates) == 1 { + break + } + + choice := "" + + err = prompt.SurveyAskOne(&survey.Select{ + Message: "What next?", + Options: []string{ + "Edit another file", + "Submit", + "Cancel", + }, + }, &choice) + + if err != nil { + return err + } + + stop := false + + switch choice { + case "Edit another file": + continue + case "Submit": + stop = true + case "Cancel": + return cmdutil.SilentError + } + + if stop { + break + } + } + + err = updateGist(client, ghinstance.OverridableDefault(), gist) + if err != nil { + return err + } + + return nil +} + +func updateGist(client *http.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{} + + apiClient := api.NewClientFromHTTP(client) + err = apiClient.REST(hostname, "POST", path, requestBody, &result) + + if err != nil { + return err + } + + return nil +} diff --git a/pkg/cmd/gist/gist.go b/pkg/cmd/gist/gist.go index 612ef0bcf..7496a72f0 100644 --- a/pkg/cmd/gist/gist.go +++ b/pkg/cmd/gist/gist.go @@ -2,6 +2,7 @@ package gist import ( gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create" + gistEditCmd "github.com/cli/cli/pkg/cmd/gist/edit" gistListCmd "github.com/cli/cli/pkg/cmd/gist/list" gistViewCmd "github.com/cli/cli/pkg/cmd/gist/view" "github.com/cli/cli/pkg/cmdutil" @@ -18,6 +19,7 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(gistCreateCmd.NewCmdCreate(f, nil)) cmd.AddCommand(gistListCmd.NewCmdList(f, nil)) cmd.AddCommand(gistViewCmd.NewCmdView(f, nil)) + cmd.AddCommand(gistEditCmd.NewCmdEdit(f, nil)) return cmd } diff --git a/pkg/cmd/gist/list/http.go b/pkg/cmd/gist/list/http.go index e64243977..4fcde88f8 100644 --- a/pkg/cmd/gist/list/http.go +++ b/pkg/cmd/gist/list/http.go @@ -11,7 +11,7 @@ import ( ) type Gist struct { - Description string `json:"description"` + Description string HTMLURL string `json:"html_url"` UpdatedAt time.Time `json:"updated_at"` Public bool diff --git a/pkg/cmd/gist/shared/shared.go b/pkg/cmd/gist/shared/shared.go index 7cfbc5866..af67e25af 100644 --- a/pkg/cmd/gist/shared/shared.go +++ b/pkg/cmd/gist/shared/shared.go @@ -7,16 +7,19 @@ import ( "github.com/cli/cli/api" ) +// TODO make gist create use this file + type GistFile struct { - Filename string - Type string - Language string - Content string + Filename string `json:"filename"` + Type string `json:"type,omitempty"` + Language string `json:"language,omitempty"` + Content string `json:"content"` } type Gist struct { - Description string - Files map[string]GistFile + ID string `json:"id,omitempty"` + Description string `json:"description"` + Files map[string]*GistFile `json:"files"` } func GetGist(client *http.Client, hostname, gistID string) (*Gist, error) { diff --git a/pkg/cmd/gist/view/view.go b/pkg/cmd/gist/view/view.go index be9ac4bde..313529d20 100644 --- a/pkg/cmd/gist/view/view.go +++ b/pkg/cmd/gist/view/view.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/cli/cli/internal/ghinstance" + "github.com/cli/cli/pkg/cmd/gist/shared" "github.com/cli/cli/pkg/cmdutil" "github.com/cli/cli/pkg/iostreams" "github.com/cli/cli/utils" @@ -65,7 +66,7 @@ func viewRun(opts *ViewOptions) error { return err } - gist, err := getGist(client, ghinstance.OverridableDefault(), gistID) + gist, err := shared.GetGist(client, ghinstance.OverridableDefault(), gistID) if err != nil { return err }