Integrate latest go-gh packages (#6084)

This commit is contained in:
Sam Coe 2022-08-18 09:04:13 +03:00 committed by GitHub
parent eee1b1c36e
commit 6a8deb1f5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 189 additions and 1008 deletions

12
go.mod
View file

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

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

View file

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

View 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
View 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)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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