commit
bdadb3058b
14 changed files with 1268 additions and 13 deletions
|
|
@ -18,6 +18,7 @@ We'd love to hear your feedback about `gh`. If you spot bugs or have features th
|
|||
- `gh pr [status, list, view, checkout, create]`
|
||||
- `gh issue [status, list, view, create]`
|
||||
- `gh repo [view, create, clone, fork]`
|
||||
- `gh gist [create, list, view, edit]`
|
||||
- `gh auth [login, logout, refresh, status]`
|
||||
- `gh config [get, set]`
|
||||
- `gh help`
|
||||
|
|
|
|||
|
|
@ -55,3 +55,10 @@ func RESTPrefix(hostname string) string {
|
|||
}
|
||||
return "https://api.github.com/"
|
||||
}
|
||||
|
||||
func GistPrefix(hostname string) string {
|
||||
if IsEnterprise(hostname) {
|
||||
return fmt.Sprintf("https://%s/gist/", hostname)
|
||||
}
|
||||
return fmt.Sprintf("https://gist.%s/", hostname)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,12 +69,7 @@ func listRun(opts *ListOptions) error {
|
|||
sort.Strings(keys)
|
||||
|
||||
for _, alias := range keys {
|
||||
if tp.IsTTY() {
|
||||
// ensure that screen readers pause
|
||||
tp.AddField(alias+":", nil, nil)
|
||||
} else {
|
||||
tp.AddField(alias, nil, nil)
|
||||
}
|
||||
tp.AddField(alias+":", nil, nil)
|
||||
tp.AddField(aliasMap[alias], nil, nil)
|
||||
tp.EndRow()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,10 @@ import (
|
|||
type CreateOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
Description string
|
||||
Public bool
|
||||
Filenames []string
|
||||
Description string
|
||||
Public bool
|
||||
Filenames []string
|
||||
FilenameOverride string
|
||||
|
||||
HttpClient func() (*http.Client, error)
|
||||
}
|
||||
|
|
@ -84,6 +85,7 @@ func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Co
|
|||
|
||||
cmd.Flags().StringVarP(&opts.Description, "desc", "d", "", "A description for this gist")
|
||||
cmd.Flags().BoolVarP(&opts.Public, "public", "p", false, "List the gist publicly (default: private)")
|
||||
cmd.Flags().StringVarP(&opts.FilenameOverride, "filename", "f", "", "Provide a filename to be used when reading from STDIN")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +95,7 @@ func createRun(opts *CreateOptions) error {
|
|||
fileArgs = []string{"-"}
|
||||
}
|
||||
|
||||
files, err := processFiles(opts.IO.In, fileArgs)
|
||||
files, err := processFiles(opts.IO.In, opts.FilenameOverride, fileArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to collect files for posting: %w", err)
|
||||
}
|
||||
|
|
@ -137,7 +139,7 @@ func createRun(opts *CreateOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func processFiles(stdin io.ReadCloser, filenames []string) (map[string]string, error) {
|
||||
func processFiles(stdin io.ReadCloser, filenameOverride string, filenames []string) (map[string]string, error) {
|
||||
fs := map[string]string{}
|
||||
|
||||
if len(filenames) == 0 {
|
||||
|
|
@ -149,7 +151,11 @@ func processFiles(stdin io.ReadCloser, filenames []string) (map[string]string, e
|
|||
var content []byte
|
||||
var err error
|
||||
if f == "-" {
|
||||
filename = fmt.Sprintf("gistfile%d.txt", i)
|
||||
if filenameOverride != "" {
|
||||
filename = filenameOverride
|
||||
} else {
|
||||
filename = fmt.Sprintf("gistfile%d.txt", i)
|
||||
}
|
||||
content, err = ioutil.ReadAll(stdin)
|
||||
if err != nil {
|
||||
return fs, fmt.Errorf("failed to read from stdin: %w", err)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ const (
|
|||
|
||||
func Test_processFiles(t *testing.T) {
|
||||
fakeStdin := strings.NewReader("hey cool how is it going")
|
||||
files, err := processFiles(ioutil.NopCloser(fakeStdin), []string{"-"})
|
||||
files, err := processFiles(ioutil.NopCloser(fakeStdin), "", []string{"-"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error processing files: %s", err)
|
||||
}
|
||||
|
|
|
|||
210
pkg/cmd/gist/edit/edit.go
Normal file
210
pkg/cmd/gist/edit/edit.go
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
package edit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"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)
|
||||
|
||||
Edit func(string, string, string, *iostreams.IOStreams) (string, 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,
|
||||
Edit: func(editorCmd, filename, defaultContent string, io *iostreams.IOStreams) (string, error) {
|
||||
return surveyext.Edit(
|
||||
editorCmd,
|
||||
"*."+filename,
|
||||
defaultContent,
|
||||
io.In, io.Out, io.ErrOut, nil)
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "edit {<gist ID> | <gist URL>}",
|
||||
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 {
|
||||
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 err != nil {
|
||||
return fmt.Errorf("could not prompt: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 := opts.Edit(editorCommand, filename, gist.Files[filename].Content, opts.IO)
|
||||
|
||||
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 fmt.Errorf("could not prompt: %w", 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
|
||||
}
|
||||
244
pkg/cmd/gist/edit/edit_test.go
Normal file
244
pkg/cmd/gist/edit/edit_test.go
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
package edit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/config"
|
||||
"github.com/cli/cli/pkg/cmd/gist/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/prompt"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewCmdEdit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants EditOptions
|
||||
}{
|
||||
{
|
||||
name: "no flags",
|
||||
cli: "123",
|
||||
wants: EditOptions{
|
||||
Selector: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filename",
|
||||
cli: "123 --filename cool.md",
|
||||
wants: EditOptions{
|
||||
Selector: "123",
|
||||
Filename: "cool.md",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := &cmdutil.Factory{}
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gotOpts *EditOptions
|
||||
cmd := NewCmdEdit(f, func(opts *EditOptions) 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.Filename, gotOpts.Filename)
|
||||
assert.Equal(t, tt.wants.Selector, gotOpts.Selector)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_editRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *EditOptions
|
||||
gist *shared.Gist
|
||||
httpStubs func(*httpmock.Registry)
|
||||
askStubs func(*prompt.AskStubber)
|
||||
nontty bool
|
||||
wantErr bool
|
||||
wantParams map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "no such gist",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "one file",
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Filename: "cicada.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
wantParams: map[string]interface{}{
|
||||
"description": "",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"public": false,
|
||||
"files": map[string]interface{}{
|
||||
"cicada.txt": map[string]interface{}{
|
||||
"content": "new file content",
|
||||
"filename": "cicada.txt",
|
||||
"type": "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple files, submit",
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubOne("unix.md")
|
||||
as.StubOne("Submit")
|
||||
},
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Description: "catbug",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Filename: "cicada.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
"unix.md": {
|
||||
Filename: "unix.md",
|
||||
Content: "meow",
|
||||
Type: "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("POST", "gists/1234"),
|
||||
httpmock.StatusStringResponse(201, "{}"))
|
||||
},
|
||||
wantParams: map[string]interface{}{
|
||||
"description": "catbug",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"public": false,
|
||||
"files": map[string]interface{}{
|
||||
"cicada.txt": map[string]interface{}{
|
||||
"content": "bwhiizzzbwhuiiizzzz",
|
||||
"filename": "cicada.txt",
|
||||
"type": "text/plain",
|
||||
},
|
||||
"unix.md": map[string]interface{}{
|
||||
"content": "new file content",
|
||||
"filename": "unix.md",
|
||||
"type": "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple files, cancel",
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubOne("unix.md")
|
||||
as.StubOne("Cancel")
|
||||
},
|
||||
wantErr: true,
|
||||
gist: &shared.Gist{
|
||||
ID: "1234",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Filename: "cicada.txt",
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
"unix.md": {
|
||||
Filename: "unix.md",
|
||||
Content: "meow",
|
||||
Type: "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
if tt.httpStubs != nil {
|
||||
tt.httpStubs(reg)
|
||||
}
|
||||
|
||||
as, teardown := prompt.InitAskStubber()
|
||||
defer teardown()
|
||||
if tt.askStubs != nil {
|
||||
tt.askStubs(as)
|
||||
}
|
||||
|
||||
if tt.opts == nil {
|
||||
tt.opts = &EditOptions{}
|
||||
}
|
||||
|
||||
tt.opts.Edit = func(_, _, _ string, _ *iostreams.IOStreams) (string, error) {
|
||||
return "new file content", nil
|
||||
}
|
||||
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
io, _, _, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(!tt.nontty)
|
||||
io.SetStdinTTY(!tt.nontty)
|
||||
tt.opts.IO = io
|
||||
|
||||
tt.opts.Selector = "1234"
|
||||
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
}
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := editRun(tt.opts)
|
||||
reg.Verify(t)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
if tt.wantParams != nil {
|
||||
bodyBytes, _ := ioutil.ReadAll(reg.Requests[1].Body)
|
||||
reqBody := make(map[string]interface{})
|
||||
err = json.Unmarshal(bodyBytes, &reqBody)
|
||||
if err != nil {
|
||||
t.Fatalf("error decoding JSON: %v", err)
|
||||
}
|
||||
assert.Equal(t, tt.wantParams, reqBody)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package gist
|
||||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
|
@ -11,9 +15,20 @@ func NewCmdGist(f *cmdutil.Factory) *cobra.Command {
|
|||
Use: "gist",
|
||||
Short: "Create gists",
|
||||
Long: `Work with GitHub gists.`,
|
||||
Annotations: map[string]string{
|
||||
"IsCore": "true",
|
||||
"help:arguments": heredoc.Doc(`
|
||||
A gist can be supplied as argument in either of the following formats:
|
||||
- by ID, e.g. 5b0e0062eb8e9654adad7bb1d81cc75f
|
||||
- by URL, e.g. "https://gist.github.com/OWNER/5b0e0062eb8e9654adad7bb1d81cc75f"
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
46
pkg/cmd/gist/list/http.go
Normal file
46
pkg/cmd/gist/list/http.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
"github.com/cli/cli/pkg/cmd/gist/shared"
|
||||
)
|
||||
|
||||
func listGists(client *http.Client, hostname string, limit int, visibility string) ([]shared.Gist, error) {
|
||||
result := []shared.Gist{}
|
||||
|
||||
query := url.Values{}
|
||||
if visibility == "all" {
|
||||
query.Add("per_page", fmt.Sprintf("%d", limit))
|
||||
} else {
|
||||
query.Add("per_page", "100")
|
||||
}
|
||||
|
||||
// TODO switch to graphql
|
||||
apiClient := api.NewClientFromHTTP(client)
|
||||
err := apiClient.REST(hostname, "GET", "gists?"+query.Encode(), nil, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gists := []shared.Gist{}
|
||||
|
||||
for _, gist := range result {
|
||||
if len(gists) == limit {
|
||||
break
|
||||
}
|
||||
if visibility == "all" || (visibility == "secret" && !gist.Public) || (visibility == "public" && gist.Public) {
|
||||
gists = append(gists, gist)
|
||||
}
|
||||
}
|
||||
|
||||
sort.SliceStable(gists, func(i, j int) bool {
|
||||
return gists[i].UpdatedAt.After(gists[j].UpdatedAt)
|
||||
})
|
||||
|
||||
return gists, nil
|
||||
}
|
||||
116
pkg/cmd/gist/list/list.go
Normal file
116
pkg/cmd/gist/list/list.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 ListOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
HttpClient func() (*http.Client, error)
|
||||
|
||||
Limit int
|
||||
Visibility string // all, secret, public
|
||||
}
|
||||
|
||||
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
||||
opts := &ListOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List your gists",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if opts.Limit < 1 {
|
||||
return &cmdutil.FlagError{Err: fmt.Errorf("invalid limit: %v", opts.Limit)}
|
||||
}
|
||||
|
||||
pub := cmd.Flags().Changed("public")
|
||||
secret := cmd.Flags().Changed("secret")
|
||||
|
||||
opts.Visibility = "all"
|
||||
if pub && !secret {
|
||||
opts.Visibility = "public"
|
||||
} else if secret && !pub {
|
||||
opts.Visibility = "secret"
|
||||
}
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return listRun(opts)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().IntVarP(&opts.Limit, "limit", "L", 10, "Maximum number of gists to fetch")
|
||||
cmd.Flags().Bool("public", false, "Show only public gists")
|
||||
cmd.Flags().Bool("secret", false, "Show only secret gists")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func listRun(opts *ListOptions) error {
|
||||
client, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gists, err := listGists(client, ghinstance.OverridableDefault(), opts.Limit, opts.Visibility)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
tp := utils.NewTablePrinter(opts.IO)
|
||||
|
||||
for _, gist := range gists {
|
||||
fileCount := 0
|
||||
for range gist.Files {
|
||||
fileCount++
|
||||
}
|
||||
|
||||
visibility := "public"
|
||||
visColor := cs.Green
|
||||
if !gist.Public {
|
||||
visibility = "secret"
|
||||
visColor = cs.Red
|
||||
}
|
||||
|
||||
description := gist.Description
|
||||
if description == "" {
|
||||
for filename := range gist.Files {
|
||||
if !strings.HasPrefix(filename, "gistfile") {
|
||||
description = filename
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tp.AddField(gist.ID, nil, nil)
|
||||
tp.AddField(description, nil, cs.Bold)
|
||||
tp.AddField(utils.Pluralize(fileCount, "file"), nil, nil)
|
||||
tp.AddField(visibility, nil, visColor)
|
||||
if tp.IsTTY() {
|
||||
updatedAt := utils.FuzzyAgo(time.Since(gist.UpdatedAt))
|
||||
tp.AddField(updatedAt, nil, cs.Gray)
|
||||
} else {
|
||||
tp.AddField(gist.UpdatedAt.String(), nil, nil)
|
||||
}
|
||||
tp.EndRow()
|
||||
}
|
||||
|
||||
return tp.Render()
|
||||
}
|
||||
224
pkg/cmd/gist/list/list_test.go
Normal file
224
pkg/cmd/gist/list/list_test.go
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package list
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/pkg/cmd/gist/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewCmdList(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants ListOptions
|
||||
}{
|
||||
{
|
||||
name: "no arguments",
|
||||
wants: ListOptions{
|
||||
Limit: 10,
|
||||
Visibility: "all",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "public",
|
||||
cli: "--public",
|
||||
wants: ListOptions{
|
||||
Limit: 10,
|
||||
Visibility: "public",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "secret",
|
||||
cli: "--secret",
|
||||
wants: ListOptions{
|
||||
Limit: 10,
|
||||
Visibility: "secret",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "public and secret",
|
||||
cli: "--secret --public",
|
||||
wants: ListOptions{
|
||||
Limit: 10,
|
||||
Visibility: "all",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "limit",
|
||||
cli: "--limit 30",
|
||||
wants: ListOptions{
|
||||
Limit: 30,
|
||||
Visibility: "all",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := &cmdutil.Factory{}
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gotOpts *ListOptions
|
||||
cmd := NewCmdList(f, func(opts *ListOptions) 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.Visibility, gotOpts.Visibility)
|
||||
assert.Equal(t, tt.wants.Limit, gotOpts.Limit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_listRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *ListOptions
|
||||
wantOut string
|
||||
stubs func(*httpmock.Registry)
|
||||
nontty bool
|
||||
updatedAt *time.Time
|
||||
}{
|
||||
{
|
||||
name: "no gists",
|
||||
opts: &ListOptions{},
|
||||
stubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(httpmock.REST("GET", "gists"),
|
||||
httpmock.JSONResponse([]shared.Gist{}))
|
||||
|
||||
},
|
||||
wantOut: "",
|
||||
},
|
||||
{
|
||||
name: "default behavior",
|
||||
opts: &ListOptions{},
|
||||
wantOut: "1234567890 cool.txt 1 file public about 6 hours ago\n4567890123 1 file public about 6 hours ago\n2345678901 tea leaves thwart... 2 files secret about 6 hours ago\n3456789012 short desc 11 files secret about 6 hours ago\n",
|
||||
},
|
||||
{
|
||||
name: "with public filter",
|
||||
opts: &ListOptions{Visibility: "public"},
|
||||
wantOut: "1234567890 cool.txt 1 file public about 6 hours ago\n4567890123 1 file public about 6 hours ago\n",
|
||||
},
|
||||
{
|
||||
name: "with secret filter",
|
||||
opts: &ListOptions{Visibility: "secret"},
|
||||
wantOut: "2345678901 tea leaves thwart... 2 files secret about 6 hours ago\n3456789012 short desc 11 files secret about 6 hours ago\n",
|
||||
},
|
||||
{
|
||||
name: "with limit",
|
||||
opts: &ListOptions{Limit: 1},
|
||||
wantOut: "1234567890 cool.txt 1 file public about 6 hours ago\n",
|
||||
},
|
||||
{
|
||||
name: "nontty output",
|
||||
opts: &ListOptions{},
|
||||
updatedAt: &time.Time{},
|
||||
wantOut: "1234567890\tcool.txt\t1 file\tpublic\t0001-01-01 00:00:00 +0000 UTC\n4567890123\t\t1 file\tpublic\t0001-01-01 00:00:00 +0000 UTC\n2345678901\ttea leaves thwart those who court catastrophe\t2 files\tsecret\t0001-01-01 00:00:00 +0000 UTC\n3456789012\tshort desc\t11 files\tsecret\t0001-01-01 00:00:00 +0000 UTC\n",
|
||||
nontty: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
sixHoursAgo, _ := time.ParseDuration("-6h")
|
||||
updatedAt := time.Now().Add(sixHoursAgo)
|
||||
if tt.updatedAt != nil {
|
||||
updatedAt = *tt.updatedAt
|
||||
}
|
||||
|
||||
reg := &httpmock.Registry{}
|
||||
if tt.stubs == nil {
|
||||
reg.Register(httpmock.REST("GET", "gists"),
|
||||
httpmock.JSONResponse([]shared.Gist{
|
||||
{
|
||||
ID: "1234567890",
|
||||
UpdatedAt: updatedAt,
|
||||
Description: "",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cool.txt": {},
|
||||
},
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
ID: "4567890123",
|
||||
UpdatedAt: updatedAt,
|
||||
Description: "",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"gistfile0.txt": {},
|
||||
},
|
||||
Public: true,
|
||||
},
|
||||
{
|
||||
ID: "2345678901",
|
||||
UpdatedAt: updatedAt,
|
||||
Description: "tea leaves thwart those who court catastrophe",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"gistfile0.txt": {},
|
||||
"gistfile1.txt": {},
|
||||
},
|
||||
Public: false,
|
||||
},
|
||||
{
|
||||
ID: "3456789012",
|
||||
UpdatedAt: updatedAt,
|
||||
Description: "short desc",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"gistfile0.txt": {},
|
||||
"gistfile1.txt": {},
|
||||
"gistfile2.txt": {},
|
||||
"gistfile3.txt": {},
|
||||
"gistfile4.txt": {},
|
||||
"gistfile5.txt": {},
|
||||
"gistfile6.txt": {},
|
||||
"gistfile7.txt": {},
|
||||
"gistfile8.txt": {},
|
||||
"gistfile9.txt": {},
|
||||
"gistfile10.txt": {},
|
||||
},
|
||||
Public: false,
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
tt.stubs(reg)
|
||||
}
|
||||
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
|
||||
io, _, stdout, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(!tt.nontty)
|
||||
tt.opts.IO = io
|
||||
|
||||
if tt.opts.Limit == 0 {
|
||||
tt.opts.Limit = 10
|
||||
}
|
||||
|
||||
if tt.opts.Visibility == "" {
|
||||
tt.opts.Visibility = "all"
|
||||
}
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := listRun(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wantOut, stdout.String())
|
||||
reg.Verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
39
pkg/cmd/gist/shared/shared.go
Normal file
39
pkg/cmd/gist/shared/shared.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package shared
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/api"
|
||||
)
|
||||
|
||||
// TODO make gist create use this file
|
||||
|
||||
type GistFile struct {
|
||||
Filename string `json:"filename"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type Gist struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Files map[string]*GistFile `json:"files"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Public bool `json:"public"`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
135
pkg/cmd/gist/view/view.go
Normal file
135
pkg/cmd/gist/view/view.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"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"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type ViewOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
HttpClient func() (*http.Client, error)
|
||||
|
||||
Selector string
|
||||
Filename string
|
||||
Raw bool
|
||||
Web 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 {<gist id> | <gist url>}",
|
||||
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")
|
||||
cmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "open gist in browser")
|
||||
cmd.Flags().StringVarP(&opts.Filename, "filename", "f", "", "display a single file of the gist")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func viewRun(opts *ViewOptions) error {
|
||||
gistID := opts.Selector
|
||||
|
||||
if opts.Web {
|
||||
gistURL := gistID
|
||||
if !strings.Contains(gistURL, "/") {
|
||||
hostname := ghinstance.OverridableDefault()
|
||||
gistURL = ghinstance.GistPrefix(hostname) + gistID
|
||||
}
|
||||
if opts.IO.IsStderrTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(gistURL))
|
||||
}
|
||||
return utils.OpenInBrowser(gistURL)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
if gist.Description != "" {
|
||||
fmt.Fprintf(opts.IO.Out, "%s\n", cs.Bold(gist.Description))
|
||||
}
|
||||
|
||||
if opts.Filename != "" {
|
||||
gistFile, ok := gist.Files[opts.Filename]
|
||||
if !ok {
|
||||
return fmt.Errorf("gist has no such file %q", opts.Filename)
|
||||
}
|
||||
|
||||
gist.Files = map[string]*shared.GistFile{
|
||||
opts.Filename: gistFile,
|
||||
}
|
||||
}
|
||||
|
||||
showFilenames := len(gist.Files) > 1
|
||||
|
||||
outs := []string{} // to ensure consistent ordering
|
||||
|
||||
for filename, gistFile := range gist.Files {
|
||||
out := ""
|
||||
if showFilenames {
|
||||
out += fmt.Sprintf("%s\n\n", cs.Gray(filename))
|
||||
}
|
||||
content := gistFile.Content
|
||||
if strings.Contains(gistFile.Type, "markdown") && !opts.Raw {
|
||||
rendered, err := utils.RenderMarkdown(gistFile.Content)
|
||||
if err == nil {
|
||||
content = rendered
|
||||
}
|
||||
}
|
||||
out += fmt.Sprintf("%s\n\n", content)
|
||||
|
||||
outs = append(outs, out)
|
||||
}
|
||||
|
||||
sort.Strings(outs)
|
||||
|
||||
for _, out := range outs {
|
||||
fmt.Fprint(opts.IO.Out, out)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
217
pkg/cmd/gist/view/view_test.go
Normal file
217
pkg/cmd/gist/view/view_test.go
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/pkg/cmd/gist/shared"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/httpmock"
|
||||
"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",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filename passed",
|
||||
cli: "-fcool.txt 123",
|
||||
tty: true,
|
||||
wants: ViewOptions{
|
||||
Raw: false,
|
||||
Selector: "123",
|
||||
Filename: "cool.txt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
assert.Equal(t, tt.wants.Filename, gotOpts.Filename)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_viewRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *ViewOptions
|
||||
wantOut string
|
||||
gist *shared.Gist
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no such gist",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "one file",
|
||||
gist: &shared.Gist{
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOut: "bwhiizzzbwhuiiizzzz\n\n",
|
||||
},
|
||||
{
|
||||
name: "filename selected",
|
||||
opts: &ViewOptions{
|
||||
Filename: "cicada.txt",
|
||||
},
|
||||
gist: &shared.Gist{
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
"foo.md": {
|
||||
Content: "# foo",
|
||||
Type: "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOut: "bwhiizzzbwhuiiizzzz\n\n",
|
||||
},
|
||||
{
|
||||
name: "multiple files, no description",
|
||||
gist: &shared.Gist{
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
"foo.md": {
|
||||
Content: "# foo",
|
||||
Type: "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOut: "cicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n # foo \n\n\n\n",
|
||||
},
|
||||
{
|
||||
name: "multiple files, description",
|
||||
gist: &shared.Gist{
|
||||
Description: "some files",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
"foo.md": {
|
||||
Content: "- foo",
|
||||
Type: "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOut: "some files\ncicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n\n \n • foo \n\n\n\n",
|
||||
},
|
||||
{
|
||||
name: "raw",
|
||||
opts: &ViewOptions{
|
||||
Raw: true,
|
||||
},
|
||||
gist: &shared.Gist{
|
||||
Description: "some files",
|
||||
Files: map[string]*shared.GistFile{
|
||||
"cicada.txt": {
|
||||
Content: "bwhiizzzbwhuiiizzzz",
|
||||
Type: "text/plain",
|
||||
},
|
||||
"foo.md": {
|
||||
Content: "- foo",
|
||||
Type: "application/markdown",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOut: "some files\ncicada.txt\n\nbwhiizzzbwhuiiizzzz\n\nfoo.md\n\n- foo\n\n",
|
||||
},
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
if tt.opts == nil {
|
||||
tt.opts = &ViewOptions{}
|
||||
}
|
||||
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
io, _, stdout, _ := iostreams.Test()
|
||||
io.SetStdoutTTY(true)
|
||||
tt.opts.IO = io
|
||||
|
||||
tt.opts.Selector = "1234"
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := viewRun(tt.opts)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wantOut, stdout.String())
|
||||
reg.Verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue