Integrate latest go-gh packages (#6084)
This commit is contained in:
parent
eee1b1c36e
commit
6a8deb1f5a
51 changed files with 189 additions and 1008 deletions
12
go.mod
12
go.mod
|
|
@ -6,10 +6,9 @@ require (
|
|||
github.com/AlecAivazis/survey/v2 v2.3.5
|
||||
github.com/MakeNowJust/heredoc v1.0.0
|
||||
github.com/briandowns/spinner v1.18.1
|
||||
github.com/charmbracelet/glamour v0.4.0
|
||||
github.com/charmbracelet/glamour v0.5.0
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
github.com/cli/browser v1.1.0
|
||||
github.com/cli/go-gh v0.1.0
|
||||
github.com/cli/go-gh v0.1.1-0.20220817122932-3630ab390fe7
|
||||
github.com/cli/oauth v0.9.0
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cli/shurcooL-graphql v0.0.1
|
||||
|
|
@ -22,14 +21,13 @@ require (
|
|||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.3.0
|
||||
github.com/henvic/httpretty v0.0.6
|
||||
github.com/itchyny/gojq v0.12.8
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0
|
||||
github.com/muesli/termenv v0.12.0
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
github.com/shurcooL/githubv4 v0.0.0-20200928013246-d292edc3691b
|
||||
|
|
@ -48,6 +46,7 @@ require (
|
|||
require (
|
||||
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/cli/browser v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
|
|
@ -55,10 +54,11 @@ require (
|
|||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/itchyny/gojq v0.12.8 // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.3 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.17 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
|
|
|||
15
go.sum
15
go.sum
|
|
@ -46,8 +46,8 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
|
|||
github.com/briandowns/spinner v1.18.1 h1:yhQmQtM1zsqFsouh09Bk/jCjd50pC3EOGsh28gLVvwY=
|
||||
github.com/briandowns/spinner v1.18.1/go.mod h1:mQak9GHqbspjC/5iUx3qMlIho8xBS/ppAL/hX5SmPJU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/charmbracelet/glamour v0.4.0 h1:scR+smyB7WdmrlIaff6IVlm48P48JaNM7JypM/VGl4k=
|
||||
github.com/charmbracelet/glamour v0.4.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
||||
github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g=
|
||||
github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc=
|
||||
github.com/charmbracelet/lipgloss v0.5.0 h1:lulQHuVeodSgDez+3rGiuxlPVXSnhth442DATR2/8t8=
|
||||
github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
|
|
@ -58,8 +58,8 @@ github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY=
|
|||
github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03 h1:3f4uHLfWx4/WlnMPXGai03eoWAI+oGHJwr+5OXfxCr8=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
github.com/cli/go-gh v0.1.0 h1:kMqFmC3ECBrV2UKzlOHjNOTTchExVc5tjNHtCqk/zYk=
|
||||
github.com/cli/go-gh v0.1.0/go.mod h1:eTGWl99EMZ+3Iau5C6dHyGAJRRia65MtdBtuhWc+84o=
|
||||
github.com/cli/go-gh v0.1.1-0.20220817122932-3630ab390fe7 h1:yLnT5/sLlUcnr5njBRmf/d7pEKr0Cu2TpeecdlQl8AY=
|
||||
github.com/cli/go-gh v0.1.1-0.20220817122932-3630ab390fe7/go.mod h1:UKRuMl3ZaitTvO4LPWj5bVw7QwZHnLu0S0lI9WWbdpc=
|
||||
github.com/cli/oauth v0.9.0 h1:nxBC0Df4tUzMkqffAB+uZvisOwT3/N9FpkfdTDtafxc=
|
||||
github.com/cli/oauth v0.9.0/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
|
|
@ -199,14 +199,16 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
|
|||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc=
|
||||
github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38 h1:0FrBxrkJ0hVembTb/e4EU5Ml6vLcOusAqymmYISg5Uo=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
|
|
@ -368,6 +370,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/cli/oauth"
|
||||
|
|
@ -106,8 +106,8 @@ func authFlow(oauthHost string, IO *iostreams.IOStreams, notice string, addition
|
|||
fmt.Fprintf(w, "%s to open %s in your browser... ", cs.Bold("Press Enter"), oauthHost)
|
||||
_ = waitForEnter(IO.In)
|
||||
|
||||
browser := cmdutil.NewBrowser(browserLauncher, IO.Out, IO.ErrOut)
|
||||
if err := browser.Browse(authURL); err != nil {
|
||||
b := browser.New(browserLauncher, IO.Out, IO.ErrOut)
|
||||
if err := b.Browse(authURL); err != nil {
|
||||
fmt.Fprintf(w, "%s Failed opening a web browser at %s\n", cs.Red("!"), authURL)
|
||||
fmt.Fprintf(w, " %s\n", err)
|
||||
fmt.Fprint(w, " Please try entering the URL in your browser manually\n")
|
||||
|
|
|
|||
16
internal/browser/browser.go
Normal file
16
internal/browser/browser.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package browser
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
ghBrowser "github.com/cli/go-gh/pkg/browser"
|
||||
)
|
||||
|
||||
type Browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
func New(launcher string, stdout, stderr io.Writer) Browser {
|
||||
b := ghBrowser.New(launcher, stdout, stderr)
|
||||
return &b
|
||||
}
|
||||
40
internal/browser/stub.go
Normal file
40
internal/browser/stub.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package browser
|
||||
|
||||
type Stub struct {
|
||||
urls []string
|
||||
}
|
||||
|
||||
func (b *Stub) Browse(url string) error {
|
||||
b.urls = append(b.urls, url)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Stub) BrowsedURL() string {
|
||||
if len(b.urls) > 0 {
|
||||
return b.urls[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type _testing interface {
|
||||
Errorf(string, ...interface{})
|
||||
Helper()
|
||||
}
|
||||
|
||||
func (b *Stub) Verify(t _testing, url string) {
|
||||
t.Helper()
|
||||
if url != "" {
|
||||
switch len(b.urls) {
|
||||
case 0:
|
||||
t.Errorf("expected browser to open URL %q, but it was never invoked", url)
|
||||
case 1:
|
||||
if url != b.urls[0] {
|
||||
t.Errorf("expected browser to open URL %q, got %q", url, b.urls[0])
|
||||
}
|
||||
default:
|
||||
t.Errorf("expected browser to open one URL, but was invoked %d times", len(b.urls))
|
||||
}
|
||||
} else if len(b.urls) > 0 {
|
||||
t.Errorf("expected no browser to open, but was invoked %d times: %v", len(b.urls), b.urls)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,9 +20,10 @@ import (
|
|||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/factory"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/export"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/jsoncolor"
|
||||
"github.com/cli/go-gh/pkg/jq"
|
||||
"github.com/cli/go-gh/pkg/template"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
|
@ -297,7 +298,11 @@ func apiRun(opts *ApiOptions) error {
|
|||
host = opts.Hostname
|
||||
}
|
||||
|
||||
template := export.NewTemplate(opts.IO, opts.Template)
|
||||
tmpl := template.New(bodyWriter, opts.IO.TerminalWidth(), opts.IO.ColorEnabled())
|
||||
err = tmpl.Parse(opts.Template)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasNextPage := true
|
||||
for hasNextPage {
|
||||
|
|
@ -306,7 +311,7 @@ func apiRun(opts *ApiOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
endCursor, err := processResponse(resp, opts, bodyWriter, headersWriter, &template)
|
||||
endCursor, err := processResponse(resp, opts, bodyWriter, headersWriter, &tmpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -330,10 +335,10 @@ func apiRun(opts *ApiOptions) error {
|
|||
}
|
||||
}
|
||||
|
||||
return template.End()
|
||||
return tmpl.Flush()
|
||||
}
|
||||
|
||||
func processResponse(resp *http.Response, opts *ApiOptions, bodyWriter, headersWriter io.Writer, template *export.Template) (endCursor string, err error) {
|
||||
func processResponse(resp *http.Response, opts *ApiOptions, bodyWriter, headersWriter io.Writer, template *template.Template) (endCursor string, err error) {
|
||||
if opts.ShowResponseHeaders {
|
||||
fmt.Fprintln(headersWriter, resp.Proto, resp.Status)
|
||||
printHeaders(headersWriter, resp.Header, opts.IO.ColorEnabled())
|
||||
|
|
@ -365,13 +370,12 @@ func processResponse(resp *http.Response, opts *ApiOptions, bodyWriter, headersW
|
|||
|
||||
if opts.FilterOutput != "" && serverError == "" {
|
||||
// TODO: reuse parsed query across pagination invocations
|
||||
err = export.FilterJSON(bodyWriter, responseBody, opts.FilterOutput)
|
||||
err = jq.Evaluate(responseBody, bodyWriter, opts.FilterOutput)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if opts.Template != "" && serverError == "" {
|
||||
// TODO: reuse parsed template across pagination invocations
|
||||
err = template.Execute(bodyWriter, responseBody)
|
||||
err = template.Execute(responseBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ import (
|
|||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/export"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/go-gh/pkg/template"
|
||||
"github.com/google/shlex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
|
@ -1298,11 +1298,13 @@ func Test_processResponse_template(t *testing.T) {
|
|||
IO: ios,
|
||||
Template: `{{range .}}{{.title}} ({{.labels | pluck "name" | join ", " }}){{"\n"}}{{end}}`,
|
||||
}
|
||||
template := export.NewTemplate(ios, opts.Template)
|
||||
_, err := processResponse(&resp, &opts, ios.Out, io.Discard, &template)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = template.End()
|
||||
tmpl := template.New(ios.Out, ios.TerminalWidth(), ios.ColorEnabled())
|
||||
err := tmpl.Parse(opts.Template)
|
||||
require.NoError(t, err)
|
||||
_, err = processResponse(&resp, &opts, ios.Out, io.Discard, &tmpl)
|
||||
require.NoError(t, err)
|
||||
err = tmpl.Flush()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, heredoc.Doc(`
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -20,13 +21,9 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type BrowseOptions struct {
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
PathFromRepoRoot func() string
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
|
|
@ -448,7 +449,7 @@ func Test_runBrowse(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
browser := cmdutil.TestBrowser{}
|
||||
browser := browser.Stub{}
|
||||
|
||||
reg := httpmock.Registry{}
|
||||
defer reg.Verify(t)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ func TestApp_VSCode(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b := &cmdutil.TestBrowser{}
|
||||
b := &browser.Stub{}
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
a := &App{
|
||||
browser: b,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/codespaces"
|
||||
"github.com/cli/cli/v2/internal/codespaces/api"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -22,10 +23,6 @@ import (
|
|||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type executable interface {
|
||||
Executable() string
|
||||
}
|
||||
|
|
@ -35,10 +32,10 @@ type App struct {
|
|||
apiClient apiClient
|
||||
errLogger *log.Logger
|
||||
executable executable
|
||||
browser browser
|
||||
browser browser.Browser
|
||||
}
|
||||
|
||||
func NewApp(io *iostreams.IOStreams, exe executable, apiClient apiClient, browser browser) *App {
|
||||
func NewApp(io *iostreams.IOStreams, exe executable, apiClient apiClient, browser browser.Browser) *App {
|
||||
errLogger := log.New(io.ErrOut, "", 0)
|
||||
|
||||
return &App{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/context"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/prompter"
|
||||
|
|
@ -33,7 +34,7 @@ func New(appVersion string) *cmdutil.Factory {
|
|||
f.Remotes = remotesFunc(f) // Depends on Config
|
||||
f.BaseRepo = BaseRepoFunc(f) // Depends on Remotes
|
||||
f.Prompter = newPrompter(f) // Depends on Config and IOStreams
|
||||
f.Browser = browser(f) // Depends on Config, and IOStreams
|
||||
f.Browser = newBrowser(f) // Depends on Config, and IOStreams
|
||||
f.ExtensionManager = extensionManager(f) // Depends on Config, HttpClient, and IOStreams
|
||||
|
||||
return f
|
||||
|
|
@ -105,9 +106,9 @@ func httpClientFunc(f *cmdutil.Factory, appVersion string) func() (*http.Client,
|
|||
}
|
||||
}
|
||||
|
||||
func browser(f *cmdutil.Factory) cmdutil.Browser {
|
||||
func newBrowser(f *cmdutil.Factory) browser.Browser {
|
||||
io := f.IOStreams
|
||||
return cmdutil.NewBrowser(browserLauncher(f), io.Out, io.ErrOut)
|
||||
return browser.New(browserLauncher(f), io.Out, io.ErrOut)
|
||||
}
|
||||
|
||||
func newPrompter(f *cmdutil.Factory) prompter.Prompter {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/pkg/cmd/gist/shared"
|
||||
|
|
@ -24,10 +25,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
|
|
@ -39,7 +36,7 @@ type CreateOptions struct {
|
|||
|
||||
Config func() (config.Config, error)
|
||||
HttpClient func() (*http.Client, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
}
|
||||
|
||||
func NewCmdCreate(f *cmdutil.Factory, runF func(*CreateOptions) error) *cobra.Command {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/cmd/gist/shared"
|
||||
|
|
@ -337,7 +338,7 @@ func Test_createRun(t *testing.T) {
|
|||
ios, stdin, stdout, stderr := iostreams.Test()
|
||||
tt.opts.IO = ios
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
tt.opts.Browser = browser
|
||||
|
||||
_, teardown := run.Stub()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -152,7 +153,7 @@ func TestNewCmdComment(t *testing.T) {
|
|||
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
Browser: &cmdutil.TestBrowser{},
|
||||
Browser: &browser.Stub{},
|
||||
}
|
||||
|
||||
argv, err := shlex.Split(tt.input)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
|
|
@ -16,16 +17,12 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
|
||||
RootDirOverride string
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
|
|
@ -267,7 +268,7 @@ func Test_createRun(t *testing.T) {
|
|||
opts.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
}
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
opts.Browser = browser
|
||||
|
||||
err := createRun(opts)
|
||||
|
|
@ -297,7 +298,7 @@ func runCommandWithRootDirOverridden(rt http.RoundTripper, isTTY bool, cli strin
|
|||
ios.SetStdinTTY(isTTY)
|
||||
ios.SetStderrTTY(isTTY)
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
|
|
@ -23,16 +24,12 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type ListOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
Config func() (config.Config, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
|
||||
WebMode bool
|
||||
Exporter cmdutil.Exporter
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
|
|
@ -185,7 +186,7 @@ func TestIssueList_web(t *testing.T) {
|
|||
ios, _, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdoutTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
|
||||
reg := &httpmock.Registry{}
|
||||
defer reg.Verify(t)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
issueShared "github.com/cli/cli/v2/pkg/cmd/issue/shared"
|
||||
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
|
|
@ -21,15 +22,11 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type ViewOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
|
||||
SelectorArg string
|
||||
WebMode bool
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
|
|
@ -61,7 +62,7 @@ func TestIssueView_web(t *testing.T) {
|
|||
ios, _, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdoutTTY(true)
|
||||
ios.SetStderrTTY(true)
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
|
||||
reg := &httpmock.Registry{}
|
||||
defer reg.Verify(t)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -13,13 +14,9 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type listOptions struct {
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/httpmock"
|
||||
|
|
@ -362,7 +363,7 @@ func TestListRun(t *testing.T) {
|
|||
ios.SetStdinTTY(tt.tty)
|
||||
ios.SetStderrTTY(tt.tty)
|
||||
tt.opts.IO = ios
|
||||
tt.opts.Browser = &cmdutil.TestBrowser{}
|
||||
tt.opts.Browser = &browser.Stub{}
|
||||
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -16,13 +17,9 @@ import (
|
|||
|
||||
const defaultInterval time.Duration = 10 * time.Second
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type ChecksOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
|
||||
Finder shared.PRFinder
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
|
|
@ -260,7 +261,7 @@ func TestChecksRun_web(t *testing.T) {
|
|||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdoutTTY(tc.isTTY)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -173,7 +174,7 @@ func TestNewCmdComment(t *testing.T) {
|
|||
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
Browser: &cmdutil.TestBrowser{},
|
||||
Browser: &browser.Stub{},
|
||||
}
|
||||
|
||||
argv, err := shlex.Split(tt.input)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/context"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
|
|
@ -24,10 +25,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type CreateOptions struct {
|
||||
// This struct stores user input and factory functions
|
||||
HttpClient func() (*http.Client, error)
|
||||
|
|
@ -35,7 +32,7 @@ type CreateOptions struct {
|
|||
IO *iostreams.IOStreams
|
||||
Remotes func() (context.Remotes, error)
|
||||
Branch func() (string, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
Finder shared.PRFinder
|
||||
|
||||
TitleProvided bool
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/context"
|
||||
"github.com/cli/cli/v2/git"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
|
|
@ -844,7 +845,7 @@ func Test_createRun(t *testing.T) {
|
|||
ios.SetStdoutTTY(tt.tty)
|
||||
ios.SetStdinTTY(tt.tty)
|
||||
ios.SetStderrTTY(tt.tty)
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
opts.IO = ios
|
||||
opts.Browser = browser
|
||||
opts.HttpClient = func() (*http.Client, error) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -17,15 +18,11 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type ListOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
|
||||
WebMode bool
|
||||
LimitResults int
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -24,7 +25,7 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, err
|
|||
ios.SetStdinTTY(isTTY)
|
||||
ios.SetStderrTTY(isTTY)
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
Browser: browser,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -17,13 +18,9 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type ViewOptions struct {
|
||||
IO *iostreams.IOStreams
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
|
||||
Finder shared.PRFinder
|
||||
Exporter cmdutil.Exporter
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
|
|
@ -118,7 +119,7 @@ func runCommand(rt http.RoundTripper, branch string, isTTY bool, cli string) (*t
|
|||
ios.SetStdinTTY(isTTY)
|
||||
ios.SetStderrTTY(isTTY)
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
factory := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
Browser: browser,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -19,15 +20,11 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type ViewOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
Exporter cmdutil.Exporter
|
||||
Config func() (config.Config, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/run"
|
||||
|
|
@ -121,7 +122,7 @@ func Test_RepoView_Web(t *testing.T) {
|
|||
reg := &httpmock.Registry{}
|
||||
reg.StubRepoInfoResponse("OWNER", "REPO", "main")
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
opts := &ViewOptions{
|
||||
Web: true,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghinstance"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
|
|
@ -27,10 +28,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type runLogCache interface {
|
||||
Exists(string) bool
|
||||
Create(string, io.ReadCloser) error
|
||||
|
|
@ -67,7 +64,7 @@ type ViewOptions struct {
|
|||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser browser
|
||||
Browser browser.Browser
|
||||
RunLogCache runLogCache
|
||||
|
||||
RunID string
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -849,7 +850,7 @@ func TestViewRun(t *testing.T) {
|
|||
tt.askStubs(as)
|
||||
}
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
tt.opts.Browser = browser
|
||||
rlc := testRunLogCache{}
|
||||
tt.opts.RunLogCache = rlc
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/pkg/cmd/search/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -16,7 +17,7 @@ import (
|
|||
)
|
||||
|
||||
type ReposOptions struct {
|
||||
Browser cmdutil.Browser
|
||||
Browser browser.Browser
|
||||
Exporter cmdutil.Exporter
|
||||
IO *iostreams.IOStreams
|
||||
Query search.Query
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/search"
|
||||
|
|
@ -237,7 +238,7 @@ func TestReposRun(t *testing.T) {
|
|||
{
|
||||
name: "opens browser for web mode tty",
|
||||
opts: &ReposOptions{
|
||||
Browser: &cmdutil.TestBrowser{},
|
||||
Browser: &browser.Stub{},
|
||||
Query: query,
|
||||
Searcher: &search.SearcherMock{
|
||||
URLFunc: func(query search.Query) string {
|
||||
|
|
@ -252,7 +253,7 @@ func TestReposRun(t *testing.T) {
|
|||
{
|
||||
name: "opens browser for web mode notty",
|
||||
opts: &ReposOptions{
|
||||
Browser: &cmdutil.TestBrowser{},
|
||||
Browser: &browser.Stub{},
|
||||
Query: query,
|
||||
Searcher: &search.SearcherMock{
|
||||
URLFunc: func(query search.Query) string {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/search"
|
||||
|
|
@ -26,7 +27,7 @@ const (
|
|||
)
|
||||
|
||||
type IssuesOptions struct {
|
||||
Browser cmdutil.Browser
|
||||
Browser browser.Browser
|
||||
Entity EntityType
|
||||
Exporter cmdutil.Exporter
|
||||
IO *iostreams.IOStreams
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/pkg/cmd/factory"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/search"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -159,7 +159,7 @@ func TestSearchIssues(t *testing.T) {
|
|||
{
|
||||
name: "opens browser for web mode tty",
|
||||
opts: &IssuesOptions{
|
||||
Browser: &cmdutil.TestBrowser{},
|
||||
Browser: &browser.Stub{},
|
||||
Entity: Issues,
|
||||
Query: query,
|
||||
Searcher: &search.SearcherMock{
|
||||
|
|
@ -175,7 +175,7 @@ func TestSearchIssues(t *testing.T) {
|
|||
{
|
||||
name: "opens browser for web mode notty",
|
||||
opts: &IssuesOptions{
|
||||
Browser: &cmdutil.TestBrowser{},
|
||||
Browser: &browser.Stub{},
|
||||
Entity: Issues,
|
||||
Query: query,
|
||||
Searcher: &search.SearcherMock{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
runShared "github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
|
|
@ -23,7 +24,7 @@ type ViewOptions struct {
|
|||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
Browser cmdutil.Browser
|
||||
Browser browser.Browser
|
||||
|
||||
Selector string
|
||||
Ref string
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
runShared "github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
|
|
@ -412,7 +413,7 @@ func TestViewRun(t *testing.T) {
|
|||
return ghrepo.FromFullName("OWNER/REPO")
|
||||
}
|
||||
|
||||
browser := &cmdutil.TestBrowser{}
|
||||
browser := &browser.Stub{}
|
||||
tt.opts.Browser = browser
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/context"
|
||||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/internal/prompter"
|
||||
|
|
@ -14,14 +15,10 @@ import (
|
|||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
type Browser interface {
|
||||
Browse(string) error
|
||||
}
|
||||
|
||||
type Factory struct {
|
||||
IOStreams *iostreams.IOStreams
|
||||
Browser Browser
|
||||
Prompter prompter.Prompter
|
||||
Browser browser.Browser
|
||||
|
||||
HttpClient func() (*http.Client, error)
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
|
|
|
|||
|
|
@ -10,10 +10,11 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/export"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/jsoncolor"
|
||||
"github.com/cli/cli/v2/pkg/set"
|
||||
"github.com/cli/go-gh/pkg/jq"
|
||||
"github.com/cli/go-gh/pkg/template"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
|
@ -137,9 +138,16 @@ func (e *exportFormat) Write(ios *iostreams.IOStreams, data interface{}) error {
|
|||
|
||||
w := ios.Out
|
||||
if e.filter != "" {
|
||||
return export.FilterJSON(w, &buf, e.filter)
|
||||
return jq.Evaluate(&buf, w, e.filter)
|
||||
} else if e.template != "" {
|
||||
return export.ExecuteTemplate(ios, &buf, e.template)
|
||||
t := template.New(w, ios.TerminalWidth(), ios.ColorEnabled())
|
||||
if err := t.Parse(e.template); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := t.Execute(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.Flush()
|
||||
} else if ios.ColorEnabled() {
|
||||
return jsoncolor.Write(w, &buf, " ")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
package cmdutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"github.com/cli/browser"
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
func NewBrowser(launcher string, stdout, stderr io.Writer) Browser {
|
||||
return &webBrowser{
|
||||
launcher: launcher,
|
||||
stdout: stdout,
|
||||
stderr: stderr,
|
||||
}
|
||||
}
|
||||
|
||||
type webBrowser struct {
|
||||
launcher string
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
}
|
||||
|
||||
func (b *webBrowser) Browse(url string) error {
|
||||
if b.launcher != "" {
|
||||
launcherArgs, err := shlex.Split(b.launcher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
launcherExe, err := safeexec.LookPath(launcherArgs[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args := append(launcherArgs[1:], url)
|
||||
cmd := exec.Command(launcherExe, args...)
|
||||
cmd.Stdout = b.stdout
|
||||
cmd.Stderr = b.stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
return browser.OpenURL(url)
|
||||
}
|
||||
|
||||
type TestBrowser struct {
|
||||
urls []string
|
||||
}
|
||||
|
||||
func (b *TestBrowser) Browse(url string) error {
|
||||
b.urls = append(b.urls, url)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *TestBrowser) BrowsedURL() string {
|
||||
if len(b.urls) > 0 {
|
||||
return b.urls[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type _testing interface {
|
||||
Errorf(string, ...interface{})
|
||||
Helper()
|
||||
}
|
||||
|
||||
func (b *TestBrowser) Verify(t _testing, url string) {
|
||||
t.Helper()
|
||||
if url != "" {
|
||||
switch len(b.urls) {
|
||||
case 0:
|
||||
t.Errorf("expected browser to open URL %q, but it was never invoked", url)
|
||||
case 1:
|
||||
if url != b.urls[0] {
|
||||
t.Errorf("expected browser to open URL %q, got %q", url, b.urls[0])
|
||||
}
|
||||
default:
|
||||
t.Errorf("expected browser to open one URL, but was invoked %d times", len(b.urls))
|
||||
}
|
||||
} else if len(b.urls) > 0 {
|
||||
t.Errorf("expected no browser to open, but was invoked %d times: %v", len(b.urls), b.urls)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
package export
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/itchyny/gojq"
|
||||
)
|
||||
|
||||
func FilterJSON(w io.Writer, input io.Reader, queryStr string) error {
|
||||
query, err := gojq.Parse(queryStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
code, err := gojq.Compile(
|
||||
query,
|
||||
gojq.WithEnvironLoader(func() []string {
|
||||
return os.Environ()
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
jsonData, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var responseData interface{}
|
||||
err = json.Unmarshal(jsonData, &responseData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iter := code.Run(responseData)
|
||||
for {
|
||||
v, ok := iter.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if err, isErr := v.(error); isErr {
|
||||
return err
|
||||
}
|
||||
if text, e := jsonScalarToString(v); e == nil {
|
||||
_, err := fmt.Fprintln(w, text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
var jsonFragment []byte
|
||||
jsonFragment, err = json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(jsonFragment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprint(w, "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
package export
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
)
|
||||
|
||||
func Test_filterJSON(t *testing.T) {
|
||||
t.Setenv("CODE", "code_c")
|
||||
|
||||
type args struct {
|
||||
json io.Reader
|
||||
query string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
args: args{
|
||||
json: strings.NewReader(`{"name":"Mona", "arms":8}`),
|
||||
query: `.name`,
|
||||
},
|
||||
wantW: "Mona\n",
|
||||
},
|
||||
{
|
||||
name: "multiple queries",
|
||||
args: args{
|
||||
json: strings.NewReader(`{"name":"Mona", "arms":8}`),
|
||||
query: `.name,.arms`,
|
||||
},
|
||||
wantW: "Mona\n8\n",
|
||||
},
|
||||
{
|
||||
name: "object as JSON",
|
||||
args: args{
|
||||
json: strings.NewReader(`{"user":{"login":"monalisa"}}`),
|
||||
query: `.user`,
|
||||
},
|
||||
wantW: "{\"login\":\"monalisa\"}\n",
|
||||
},
|
||||
{
|
||||
name: "complex",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{
|
||||
"title": "First title",
|
||||
"labels": [{"name":"bug"}, {"name":"help wanted"}]
|
||||
},
|
||||
{
|
||||
"title": "Second but not last",
|
||||
"labels": []
|
||||
},
|
||||
{
|
||||
"title": "Alas, tis' the end",
|
||||
"labels": [{}, {"name":"feature"}]
|
||||
}
|
||||
]`)),
|
||||
query: `.[] | [.title,(.labels | map(.name) | join(","))] | @tsv`,
|
||||
},
|
||||
wantW: heredoc.Doc(`
|
||||
First title bug,help wanted
|
||||
Second but not last
|
||||
Alas, tis' the end ,feature
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "with env var",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{
|
||||
"title": "code_a",
|
||||
"labels": [{"name":"bug"}, {"name":"help wanted"}]
|
||||
},
|
||||
{
|
||||
"title": "code_b",
|
||||
"labels": []
|
||||
},
|
||||
{
|
||||
"title": "code_c",
|
||||
"labels": [{}, {"name":"feature"}]
|
||||
}
|
||||
]`)),
|
||||
query: `.[]| select(.title == env.CODE) | .labels`,
|
||||
},
|
||||
wantW: "[{},{\"name\":\"feature\"}]\n",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
if err := FilterJSON(w, tt.args.json, tt.args.query); (err != nil) != tt.wantErr {
|
||||
t.Errorf("filterJSON() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("filterJSON() = %q, want %q", gotW, tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
package export
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/text"
|
||||
"github.com/cli/cli/v2/utils"
|
||||
"github.com/mgutz/ansi"
|
||||
)
|
||||
|
||||
type Template struct {
|
||||
io *iostreams.IOStreams
|
||||
tablePrinter utils.TablePrinter
|
||||
template *template.Template
|
||||
templateStr string
|
||||
}
|
||||
|
||||
func NewTemplate(io *iostreams.IOStreams, template string) Template {
|
||||
return Template{
|
||||
io: io,
|
||||
templateStr: template,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Template) parseTemplate(tpl string) (*template.Template, error) {
|
||||
now := time.Now()
|
||||
|
||||
templateFuncs := map[string]interface{}{
|
||||
"color": t.color,
|
||||
"autocolor": t.color,
|
||||
|
||||
"timefmt": func(format, input string) (string, error) {
|
||||
t, err := time.Parse(time.RFC3339, input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return t.Format(format), nil
|
||||
},
|
||||
"timeago": func(input string) (string, error) {
|
||||
t, err := time.Parse(time.RFC3339, input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return timeAgo(now.Sub(t)), nil
|
||||
},
|
||||
|
||||
"pluck": templatePluck,
|
||||
"join": templateJoin,
|
||||
"tablerow": t.tableRow,
|
||||
"tablerender": t.tableRender,
|
||||
"truncate": func(maxWidth int, v interface{}) (string, error) {
|
||||
if v == nil {
|
||||
return "", nil
|
||||
}
|
||||
if s, ok := v.(string); ok {
|
||||
return text.Truncate(maxWidth, s), nil
|
||||
}
|
||||
return "", fmt.Errorf("invalid value; expected string, got %T", v)
|
||||
},
|
||||
}
|
||||
|
||||
if !t.io.ColorEnabled() {
|
||||
templateFuncs["autocolor"] = func(colorName string, input interface{}) (string, error) {
|
||||
return jsonScalarToString(input)
|
||||
}
|
||||
}
|
||||
|
||||
return template.New("").Funcs(templateFuncs).Parse(tpl)
|
||||
}
|
||||
|
||||
func (t *Template) Execute(w io.Writer, input io.Reader) error {
|
||||
if t.template == nil {
|
||||
template, err := t.parseTemplate(t.templateStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.template = template
|
||||
}
|
||||
|
||||
jsonData, err := io.ReadAll(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
if err := json.Unmarshal(jsonData, &data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return t.template.Execute(w, data)
|
||||
}
|
||||
|
||||
func ExecuteTemplate(io *iostreams.IOStreams, input io.Reader, template string) error {
|
||||
t := NewTemplate(io, template)
|
||||
if err := t.Execute(io.Out, input); err != nil {
|
||||
return err
|
||||
}
|
||||
return t.End()
|
||||
}
|
||||
|
||||
func jsonScalarToString(input interface{}) (string, error) {
|
||||
switch tt := input.(type) {
|
||||
case string:
|
||||
return tt, nil
|
||||
case float64:
|
||||
if math.Trunc(tt) == tt {
|
||||
return strconv.FormatFloat(tt, 'f', 0, 64), nil
|
||||
} else {
|
||||
return strconv.FormatFloat(tt, 'f', 2, 64), nil
|
||||
}
|
||||
case nil:
|
||||
return "", nil
|
||||
case bool:
|
||||
return fmt.Sprintf("%v", tt), nil
|
||||
default:
|
||||
return "", fmt.Errorf("cannot convert type to string: %v", tt)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Template) color(colorName string, input interface{}) (string, error) {
|
||||
text, err := jsonScalarToString(input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ansi.Color(text, colorName), nil
|
||||
}
|
||||
|
||||
func templatePluck(field string, input []interface{}) []interface{} {
|
||||
var results []interface{}
|
||||
for _, item := range input {
|
||||
obj := item.(map[string]interface{})
|
||||
results = append(results, obj[field])
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func templateJoin(sep string, input []interface{}) (string, error) {
|
||||
var results []string
|
||||
for _, item := range input {
|
||||
text, err := jsonScalarToString(item)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
results = append(results, text)
|
||||
}
|
||||
return strings.Join(results, sep), nil
|
||||
}
|
||||
|
||||
func (t *Template) tableRow(fields ...interface{}) (string, error) {
|
||||
if t.tablePrinter == nil {
|
||||
t.tablePrinter = utils.NewTablePrinterWithOptions(t.io, utils.TablePrinterOptions{IsTTY: true})
|
||||
}
|
||||
for _, e := range fields {
|
||||
s, err := jsonScalarToString(e)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to write table row: %v", err)
|
||||
}
|
||||
t.tablePrinter.AddField(s, text.TruncateColumn, nil)
|
||||
}
|
||||
t.tablePrinter.EndRow()
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (t *Template) tableRender() (string, error) {
|
||||
if t.tablePrinter != nil {
|
||||
err := t.tablePrinter.Render()
|
||||
t.tablePrinter = nil
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to render table: %v", err)
|
||||
}
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (t *Template) End() error {
|
||||
// Finalize any template actions.
|
||||
if _, err := t.tableRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func timeAgo(ago time.Duration) string {
|
||||
if ago < time.Minute {
|
||||
return "just now"
|
||||
}
|
||||
if ago < time.Hour {
|
||||
return utils.Pluralize(int(ago.Minutes()), "minute") + " ago"
|
||||
}
|
||||
if ago < 24*time.Hour {
|
||||
return utils.Pluralize(int(ago.Hours()), "hour") + " ago"
|
||||
}
|
||||
if ago < 30*24*time.Hour {
|
||||
return utils.Pluralize(int(ago.Hours())/24, "day") + " ago"
|
||||
}
|
||||
if ago < 365*24*time.Hour {
|
||||
return utils.Pluralize(int(ago.Hours())/24/30, "month") + " ago"
|
||||
}
|
||||
return utils.Pluralize(int(ago.Hours()/24/365), "year") + " ago"
|
||||
}
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
package export
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
)
|
||||
|
||||
func Test_jsonScalarToString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
input: "hello",
|
||||
want: "hello",
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
input: float64(1234),
|
||||
want: "1234",
|
||||
},
|
||||
{
|
||||
name: "float",
|
||||
input: float64(12.34),
|
||||
want: "12.34",
|
||||
},
|
||||
{
|
||||
name: "null",
|
||||
input: nil,
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "true",
|
||||
input: true,
|
||||
want: "true",
|
||||
},
|
||||
{
|
||||
name: "false",
|
||||
input: false,
|
||||
want: "false",
|
||||
},
|
||||
{
|
||||
name: "object",
|
||||
input: map[string]interface{}{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := jsonScalarToString(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("jsonScalarToString() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("jsonScalarToString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_executeTemplate(t *testing.T) {
|
||||
type args struct {
|
||||
json io.Reader
|
||||
template string
|
||||
colorize bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantW string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "color",
|
||||
args: args{
|
||||
json: strings.NewReader(`{}`),
|
||||
template: `{{color "blue+h" "songs are like tattoos"}}`,
|
||||
},
|
||||
wantW: "\x1b[0;94msongs are like tattoos\x1b[0m",
|
||||
},
|
||||
{
|
||||
name: "autocolor enabled",
|
||||
args: args{
|
||||
json: strings.NewReader(`{}`),
|
||||
template: `{{autocolor "red" "stop"}}`,
|
||||
colorize: true,
|
||||
},
|
||||
wantW: "\x1b[0;31mstop\x1b[0m",
|
||||
},
|
||||
{
|
||||
name: "autocolor disabled",
|
||||
args: args{
|
||||
json: strings.NewReader(`{}`),
|
||||
template: `{{autocolor "red" "go"}}`,
|
||||
},
|
||||
wantW: "go",
|
||||
},
|
||||
{
|
||||
name: "timefmt",
|
||||
args: args{
|
||||
json: strings.NewReader(`{"created_at":"2008-02-25T20:18:33Z"}`),
|
||||
template: `{{.created_at | timefmt "Mon Jan 2, 2006"}}`,
|
||||
},
|
||||
wantW: "Mon Feb 25, 2008",
|
||||
},
|
||||
{
|
||||
name: "timeago",
|
||||
args: args{
|
||||
json: strings.NewReader(fmt.Sprintf(`{"created_at":"%s"}`, time.Now().Add(-5*time.Minute).Format(time.RFC3339))),
|
||||
template: `{{.created_at | timeago}}`,
|
||||
},
|
||||
wantW: "5 minutes ago",
|
||||
},
|
||||
{
|
||||
name: "pluck",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"name": "bug"},
|
||||
{"name": "feature request"},
|
||||
{"name": "chore"}
|
||||
]`)),
|
||||
template: `{{range(pluck "name" .)}}{{. | printf "%s\n"}}{{end}}`,
|
||||
},
|
||||
wantW: "bug\nfeature request\nchore\n",
|
||||
},
|
||||
{
|
||||
name: "join",
|
||||
args: args{
|
||||
json: strings.NewReader(`[ "bug", "feature request", "chore" ]`),
|
||||
template: `{{join "\t" .}}`,
|
||||
},
|
||||
wantW: "bug\tfeature request\tchore",
|
||||
},
|
||||
{
|
||||
name: "table",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"number": 1, "title": "One"},
|
||||
{"number": 20, "title": "Twenty"},
|
||||
{"number": 3000, "title": "Three thousand"}
|
||||
]`)),
|
||||
template: `{{range .}}{{tablerow (.number | printf "#%v") .title}}{{end}}`,
|
||||
},
|
||||
wantW: heredoc.Doc(`#1 One
|
||||
#20 Twenty
|
||||
#3000 Three thousand
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "table with multiline text",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"number": 1, "title": "One\ranother line of text"},
|
||||
{"number": 20, "title": "Twenty\nanother line of text"},
|
||||
{"number": 3000, "title": "Three thousand\r\nanother line of text"}
|
||||
]`)),
|
||||
template: `{{range .}}{{tablerow (.number | printf "#%v") .title}}{{end}}`,
|
||||
},
|
||||
wantW: heredoc.Doc(`#1 One...
|
||||
#20 Twenty...
|
||||
#3000 Three thousand...
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "table with mixed value types",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"number": 1, "title": null, "float": false},
|
||||
{"number": 20.1, "title": "Twenty-ish", "float": true},
|
||||
{"number": 3000, "title": "Three thousand", "float": false}
|
||||
]`)),
|
||||
template: `{{range .}}{{tablerow .number .title .float}}{{end}}`,
|
||||
},
|
||||
wantW: heredoc.Doc(`1 false
|
||||
20.10 Twenty-ish true
|
||||
3000 Three thousand false
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "table with color",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"number": 1, "title": "One"}
|
||||
]`)),
|
||||
template: `{{range .}}{{tablerow (.number | color "green") .title}}{{end}}`,
|
||||
},
|
||||
wantW: "\x1b[0;32m1\x1b[0m One\n",
|
||||
},
|
||||
{
|
||||
name: "table with header and footer",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"number": 1, "title": "One"},
|
||||
{"number": 2, "title": "Two"}
|
||||
]`)),
|
||||
template: heredoc.Doc(`HEADER
|
||||
{{range .}}{{tablerow .number .title}}{{end}}FOOTER
|
||||
`),
|
||||
},
|
||||
wantW: heredoc.Doc(`HEADER
|
||||
FOOTER
|
||||
1 One
|
||||
2 Two
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "table with header and footer using endtable",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`[
|
||||
{"number": 1, "title": "One"},
|
||||
{"number": 2, "title": "Two"}
|
||||
]`)),
|
||||
template: heredoc.Doc(`HEADER
|
||||
{{range .}}{{tablerow .number .title}}{{end}}{{tablerender}}FOOTER
|
||||
`),
|
||||
},
|
||||
wantW: heredoc.Doc(`HEADER
|
||||
1 One
|
||||
2 Two
|
||||
FOOTER
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "multiple tables with different columns",
|
||||
args: args{
|
||||
json: strings.NewReader(heredoc.Doc(`{
|
||||
"issues": [
|
||||
{"number": 1, "title": "One"},
|
||||
{"number": 2, "title": "Two"}
|
||||
],
|
||||
"prs": [
|
||||
{"number": 3, "title": "Three", "reviewDecision": "REVIEW_REQUESTED"},
|
||||
{"number": 4, "title": "Four", "reviewDecision": "CHANGES_REQUESTED"}
|
||||
]
|
||||
}`)),
|
||||
template: heredoc.Doc(`{{tablerow "ISSUE" "TITLE"}}{{range .issues}}{{tablerow .number .title}}{{end}}{{tablerender}}
|
||||
{{tablerow "PR" "TITLE" "DECISION"}}{{range .prs}}{{tablerow .number .title .reviewDecision}}{{end}}`),
|
||||
},
|
||||
wantW: heredoc.Docf(`ISSUE TITLE
|
||||
1 One
|
||||
2 Two
|
||||
|
||||
PR TITLE DECISION
|
||||
3 Three REVIEW_REQUESTED
|
||||
4 Four CHANGES_REQUESTED
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "truncate",
|
||||
args: args{
|
||||
json: strings.NewReader(`{"title": "This is a long title"}`),
|
||||
template: `{{truncate 13 .title}}`,
|
||||
},
|
||||
wantW: "This is a ...",
|
||||
},
|
||||
{
|
||||
name: "truncate with JSON null",
|
||||
args: args{
|
||||
json: strings.NewReader(`{}`),
|
||||
template: `{{ truncate 13 .title }}`,
|
||||
},
|
||||
wantW: "",
|
||||
},
|
||||
{
|
||||
name: "truncate with piped JSON null",
|
||||
args: args{
|
||||
json: strings.NewReader(`{}`),
|
||||
template: `{{ .title | truncate 13 }}`,
|
||||
},
|
||||
wantW: "",
|
||||
},
|
||||
{
|
||||
name: "truncate with piped JSON null in parenthetical",
|
||||
args: args{
|
||||
json: strings.NewReader(`{}`),
|
||||
template: `{{ (.title | truncate 13) }}`,
|
||||
},
|
||||
wantW: "",
|
||||
},
|
||||
{
|
||||
name: "truncate invalid type",
|
||||
args: args{
|
||||
json: strings.NewReader(`{"title": 42}`),
|
||||
template: `{{ (.title | truncate 13) }}`,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
io, _, w, _ := iostreams.Test()
|
||||
io.SetColorEnabled(tt.args.colorize)
|
||||
if err := ExecuteTemplate(io, tt.args.json, tt.args.template); (err != nil) != tt.wantErr {
|
||||
t.Errorf("executeTemplate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("executeTemplate() = %q, want %q", gotW, tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,16 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
ghMarkdown "github.com/cli/go-gh/pkg/markdown"
|
||||
)
|
||||
|
||||
func WithoutIndentation() glamour.TermRendererOption {
|
||||
overrides := []byte(`
|
||||
{
|
||||
"document": {
|
||||
"margin": 0
|
||||
},
|
||||
"code_block": {
|
||||
"margin": 0
|
||||
}
|
||||
}`)
|
||||
|
||||
return glamour.WithStylesFromJSONBytes(overrides)
|
||||
return ghMarkdown.WithoutIndentation()
|
||||
}
|
||||
|
||||
func WithWrap(w int) glamour.TermRendererOption {
|
||||
return glamour.WithWordWrap(w)
|
||||
return ghMarkdown.WithWrap(w)
|
||||
}
|
||||
|
||||
type IOStreams interface {
|
||||
|
|
@ -30,33 +18,14 @@ type IOStreams interface {
|
|||
}
|
||||
|
||||
func WithIO(io IOStreams) glamour.TermRendererOption {
|
||||
style := os.Getenv("GLAMOUR_STYLE")
|
||||
if style == "" || style == "auto" {
|
||||
theme := io.TerminalTheme()
|
||||
switch theme {
|
||||
case "light", "dark":
|
||||
style = theme
|
||||
default:
|
||||
style = "notty"
|
||||
}
|
||||
}
|
||||
return glamour.WithStylePath(style)
|
||||
theme := io.TerminalTheme()
|
||||
return ghMarkdown.WithTheme(theme)
|
||||
}
|
||||
|
||||
func WithBaseURL(u string) glamour.TermRendererOption {
|
||||
return glamour.WithBaseURL(u)
|
||||
return ghMarkdown.WithBaseURL(u)
|
||||
}
|
||||
|
||||
func Render(text string, opts ...glamour.TermRendererOption) (string, error) {
|
||||
// Glamour rendering preserves carriage return characters in code blocks, but
|
||||
// we need to ensure that no such characters are present in the output.
|
||||
text = strings.ReplaceAll(text, "\r\n", "\n")
|
||||
|
||||
opts = append(opts, glamour.WithEmoji(), glamour.WithPreservedNewLines())
|
||||
tr, err := glamour.NewTermRenderer(opts...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tr.Render(text)
|
||||
return ghMarkdown.Render(text, opts...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
package markdown
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Render(t *testing.T) {
|
||||
os.Unsetenv("GLAMOUR_STYLE")
|
||||
|
||||
type input struct {
|
||||
text string
|
||||
theme string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
input input
|
||||
wantsErr bool
|
||||
}{
|
||||
{
|
||||
name: "light theme",
|
||||
input: input{
|
||||
text: "some text",
|
||||
theme: "light",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
{
|
||||
name: "dark theme",
|
||||
input: input{
|
||||
text: "some text",
|
||||
theme: "dark",
|
||||
},
|
||||
wantsErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := Render(tt.input.text, WithIO(terminalThemer(tt.input.theme)))
|
||||
if tt.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type terminalThemer string
|
||||
|
||||
func (tt terminalThemer) TerminalTheme() string {
|
||||
return string(tt)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue