diff --git a/pkg/cmd/gist/gist.go b/pkg/cmd/gist/gist.go index e47f055db..612ef0bcf 100644 --- a/pkg/cmd/gist/gist.go +++ b/pkg/cmd/gist/gist.go @@ -3,6 +3,7 @@ package gist import ( gistCreateCmd "github.com/cli/cli/pkg/cmd/gist/create" gistListCmd "github.com/cli/cli/pkg/cmd/gist/list" + gistViewCmd "github.com/cli/cli/pkg/cmd/gist/view" "github.com/cli/cli/pkg/cmdutil" "github.com/spf13/cobra" ) @@ -16,6 +17,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)) return cmd } diff --git a/pkg/cmd/gist/list/list_test.go b/pkg/cmd/gist/list/list_test.go index 03266cdf2..5541a51bc 100644 --- a/pkg/cmd/gist/list/list_test.go +++ b/pkg/cmd/gist/list/list_test.go @@ -82,3 +82,5 @@ func TestNewCmdList(t *testing.T) { }) } } + +// TODO execution tests diff --git a/pkg/cmd/gist/view/http.go b/pkg/cmd/gist/view/http.go new file mode 100644 index 000000000..617fa2ec5 --- /dev/null +++ b/pkg/cmd/gist/view/http.go @@ -0,0 +1,33 @@ +package view + +import ( + "fmt" + "net/http" + + "github.com/cli/cli/api" +) + +type GistFile struct { + Filename string + Type string + Language string + Content string +} + +type Gist struct { + Description string + Files map[string]GistFile +} + +func getGist(client *http.Client, hostname, gistID string) (*Gist, error) { + gist := Gist{} + path := fmt.Sprintf("gists/%s", gistID) + + apiClient := api.NewClientFromHTTP(client) + err := apiClient.REST(hostname, "GET", path, nil, &gist) + if err != nil { + return nil, err + } + + return &gist, nil +} diff --git a/pkg/cmd/gist/view/view.go b/pkg/cmd/gist/view/view.go new file mode 100644 index 000000000..be9ac4bde --- /dev/null +++ b/pkg/cmd/gist/view/view.go @@ -0,0 +1,94 @@ +package view + +import ( + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/cli/cli/internal/ghinstance" + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/iostreams" + "github.com/cli/cli/utils" + "github.com/spf13/cobra" +) + +type ViewOptions struct { + IO *iostreams.IOStreams + HttpClient func() (*http.Client, error) + + Selector string + Raw bool +} + +func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Command { + opts := &ViewOptions{ + IO: f.IOStreams, + HttpClient: f.HttpClient, + } + + cmd := &cobra.Command{ + Use: "view { | }", + Short: "View a gist", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + opts.Selector = args[0] + + if !opts.IO.IsStdoutTTY() { + opts.Raw = true + } + + if runF != nil { + return runF(opts) + } + return viewRun(opts) + }, + } + + cmd.Flags().BoolVarP(&opts.Raw, "raw", "r", false, "do not try and render markdown") + + return cmd +} + +func viewRun(opts *ViewOptions) 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 := getGist(client, ghinstance.OverridableDefault(), gistID) + if err != nil { + return err + } + + cs := opts.IO.ColorScheme() + if gist.Description != "" { + fmt.Fprintf(opts.IO.ErrOut, "%s\n", cs.Bold(gist.Description)) + } + + for filename, gistFile := range gist.Files { + fmt.Fprintf(opts.IO.ErrOut, "%s\n", cs.Gray(filename)) + fmt.Fprintln(opts.IO.ErrOut) + content := gistFile.Content + if strings.Contains(gistFile.Type, "markdown") && !opts.Raw { + rendered, err := utils.RenderMarkdown(gistFile.Content) + if err == nil { + content = rendered + } + } + fmt.Fprintf(opts.IO.Out, "%s\n", content) + fmt.Fprintln(opts.IO.Out) + } + + // TODO print gist files, possibly with rendered markdown + return nil +} diff --git a/pkg/cmd/gist/view/view_test.go b/pkg/cmd/gist/view/view_test.go new file mode 100644 index 000000000..d77ab946d --- /dev/null +++ b/pkg/cmd/gist/view/view_test.go @@ -0,0 +1,70 @@ +package view + +import ( + "bytes" + "testing" + + "github.com/cli/cli/pkg/cmdutil" + "github.com/cli/cli/pkg/iostreams" + "github.com/google/shlex" + "github.com/stretchr/testify/assert" +) + +func TestNewCmdView(t *testing.T) { + tests := []struct { + name string + cli string + wants ViewOptions + tty bool + }{ + { + name: "tty no arguments", + tty: true, + cli: "123", + wants: ViewOptions{ + Raw: false, + Selector: "123", + }, + }, + { + name: "nontty no arguments", + cli: "123", + wants: ViewOptions{ + Raw: true, + Selector: "123", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + io, _, _, _ := iostreams.Test() + io.SetStdoutTTY(tt.tty) + + f := &cmdutil.Factory{ + IOStreams: io, + } + + argv, err := shlex.Split(tt.cli) + assert.NoError(t, err) + + var gotOpts *ViewOptions + cmd := NewCmdView(f, func(opts *ViewOptions) error { + gotOpts = opts + return nil + }) + cmd.SetArgs(argv) + cmd.SetIn(&bytes.Buffer{}) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + + _, err = cmd.ExecuteC() + assert.NoError(t, err) + + assert.Equal(t, tt.wants.Raw, gotOpts.Raw) + assert.Equal(t, tt.wants.Selector, gotOpts.Selector) + }) + } +} + +// TODO execution tests