api command: support {owner} and {repo} placeholders
When `{owner}` and `{repo}` strings are found in request path (for REST
requests) or `query` (for GraphQL), they are replaced with values from
the repository of the current working directory.
This commit is contained in:
parent
bf9aca834a
commit
acf0046718
4 changed files with 151 additions and 1 deletions
|
|
@ -75,8 +75,10 @@ func init() {
|
|||
HttpClient: func() (*http.Client, error) {
|
||||
token := os.Getenv("GITHUB_TOKEN")
|
||||
if len(token) == 0 {
|
||||
// TODO: decouple from `context`
|
||||
ctx := context.New()
|
||||
var err error
|
||||
// TODO: pass IOStreams to this so that the auth flow knows if it's interactive or not
|
||||
token, err = ctx.AuthToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -84,6 +86,11 @@ func init() {
|
|||
}
|
||||
return httpClient(token), nil
|
||||
},
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
// TODO: decouple from `context`
|
||||
ctx := context.New()
|
||||
return ctx.BaseRepo()
|
||||
},
|
||||
}
|
||||
RootCmd.AddCommand(apiCmd.NewCmdApi(cmdFactory, nil))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/cli/cli/pkg/jsoncolor"
|
||||
|
|
@ -32,12 +33,14 @@ type ApiOptions struct {
|
|||
ShowResponseHeaders bool
|
||||
|
||||
HttpClient func() (*http.Client, error)
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
}
|
||||
|
||||
func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command {
|
||||
opts := ApiOptions{
|
||||
IO: f.IOStreams,
|
||||
HttpClient: f.HttpClient,
|
||||
BaseRepo: f.BaseRepo,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
|
@ -93,8 +96,11 @@ func apiRun(opts *ApiOptions) error {
|
|||
return err
|
||||
}
|
||||
|
||||
requestPath, params, err := fillPlaceholders(opts, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to expand `{...}` placeholders in query: %w", err)
|
||||
}
|
||||
method := opts.RequestMethod
|
||||
requestPath := opts.RequestPath
|
||||
requestHeaders := opts.RequestHeaders
|
||||
var requestBody interface{} = params
|
||||
|
||||
|
|
@ -170,6 +176,37 @@ func apiRun(opts *ApiOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// fillPlaceholders replaces `{owner}` and `{repo}` placeholders with values from the current repository
|
||||
func fillPlaceholders(opts *ApiOptions, params map[string]interface{}) (string, map[string]interface{}, error) {
|
||||
query := opts.RequestPath
|
||||
isGraphQL := opts.RequestPath == "graphql"
|
||||
|
||||
if isGraphQL {
|
||||
if q, ok := params["query"].(string); ok {
|
||||
query = q
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(query, "{owner}") && !strings.Contains(query, "{repo}") {
|
||||
return opts.RequestPath, params, nil
|
||||
}
|
||||
|
||||
baseRepo, err := opts.BaseRepo()
|
||||
if err != nil {
|
||||
return opts.RequestPath, params, err
|
||||
}
|
||||
|
||||
query = strings.ReplaceAll(query, "{owner}", baseRepo.RepoOwner())
|
||||
query = strings.ReplaceAll(query, "{repo}", baseRepo.RepoName())
|
||||
|
||||
if isGraphQL {
|
||||
params["query"] = query
|
||||
return opts.RequestPath, params, nil
|
||||
}
|
||||
|
||||
return query, params, nil
|
||||
}
|
||||
|
||||
func printHeaders(w io.Writer, headers http.Header, colorize bool) {
|
||||
var names []string
|
||||
for name := range headers {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/cmdutil"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
"github.com/google/shlex"
|
||||
|
|
@ -451,3 +453,105 @@ func Test_openUserFile(t *testing.T) {
|
|||
assert.Equal(t, int64(13), length)
|
||||
assert.Equal(t, "file contents", string(fb))
|
||||
}
|
||||
|
||||
func Test_fillPlaceholders(t *testing.T) {
|
||||
type args struct {
|
||||
opts *ApiOptions
|
||||
params map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantPath string
|
||||
wantParams map[string]interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no changes",
|
||||
args: args{
|
||||
opts: &ApiOptions{
|
||||
RequestPath: "repos/owner/repo/releases",
|
||||
BaseRepo: nil,
|
||||
},
|
||||
params: map[string]interface{}{
|
||||
"query": "{owner}/{repo}",
|
||||
},
|
||||
},
|
||||
wantPath: "repos/owner/repo/releases",
|
||||
wantParams: map[string]interface{}{
|
||||
"query": "{owner}/{repo}",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "REST path substitute",
|
||||
args: args{
|
||||
opts: &ApiOptions{
|
||||
RequestPath: "repos/{owner}/{repo}/releases",
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("hubot", "robot-uprising"), nil
|
||||
},
|
||||
},
|
||||
params: map[string]interface{}{
|
||||
"query": "{owner}/{repo}",
|
||||
},
|
||||
},
|
||||
wantPath: "repos/hubot/robot-uprising/releases",
|
||||
wantParams: map[string]interface{}{
|
||||
"query": "{owner}/{repo}",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "GraphQL query substitute",
|
||||
args: args{
|
||||
opts: &ApiOptions{
|
||||
RequestPath: "graphql",
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("hubot", "robot-uprising"), nil
|
||||
},
|
||||
},
|
||||
params: map[string]interface{}{
|
||||
"query": "{owner}/{repo}/pulls/{owner}",
|
||||
},
|
||||
},
|
||||
wantPath: "graphql",
|
||||
wantParams: map[string]interface{}{
|
||||
"query": "hubot/robot-uprising/pulls/hubot",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "GraphQL no query",
|
||||
args: args{
|
||||
opts: &ApiOptions{
|
||||
RequestPath: "graphql",
|
||||
BaseRepo: nil,
|
||||
},
|
||||
params: map[string]interface{}{
|
||||
"foo": "{owner}/{repo}",
|
||||
},
|
||||
},
|
||||
wantPath: "graphql",
|
||||
wantParams: map[string]interface{}{
|
||||
"foo": "{owner}/{repo}",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1, err := fillPlaceholders(tt.args.opts, tt.args.params)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("fillPlaceholders() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.wantPath {
|
||||
t.Errorf("fillPlaceholders() got = %v, want %v", got, tt.wantPath)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.wantParams) {
|
||||
t.Errorf("fillPlaceholders() got1 = %v, want %v", got1, tt.wantParams)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@ package cmdutil
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cli/cli/internal/ghrepo"
|
||||
"github.com/cli/cli/pkg/iostreams"
|
||||
)
|
||||
|
||||
type Factory struct {
|
||||
IOStreams *iostreams.IOStreams
|
||||
HttpClient func() (*http.Client, error)
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue