Support standard path variable replacement syntax

Add support for the following synonyms:

{owner} for :owner
{repo} for :repo
{branch} for :branch
This commit is contained in:
Robin Neatherway 2021-04-29 16:31:31 +01:00 committed by Mislav Marohnić
parent d478a65254
commit 59b4d5cb7c
2 changed files with 72 additions and 13 deletions

View file

@ -71,7 +71,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
The endpoint argument should either be a path of a GitHub API v3 endpoint, or
"graphql" to access the GitHub API v4.
Placeholder values ":owner", ":repo", and ":branch" in the endpoint argument will
Placeholder values "{owner}", "{repo}", and "{branch}" in the endpoint argument will
get replaced with values from the repository of the current directory.
The default HTTP request method is "GET" normally and "POST" if any parameters
@ -87,7 +87,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
- literal values "true", "false", "null", and integer numbers get converted to
appropriate JSON types;
- placeholder values ":owner", ":repo", and ":branch" get populated with values
- placeholder values "{owner}", "{repo}", and "{branch}" get populated with values
from the repository of the current directory;
- if the value starts with "@", the rest of the value is interpreted as a
filename to read the value from. Pass "-" to read from standard input.
@ -106,10 +106,10 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
`, "`"),
Example: heredoc.Doc(`
# list releases in the current repository
$ gh api repos/:owner/:repo/releases
$ gh api repos/{owner}/{repo}/releases
# post an issue comment
$ gh api repos/:owner/:repo/issues/123/comments -f body='Hi from CLI'
$ gh api repos/{owner}/{repo}/issues/123/comments -f body='Hi from CLI'
# add parameters to a GET request
$ gh api -X GET search/issues -f q='repo:cli/cli is:open remote'
@ -121,14 +121,14 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
$ gh api --preview baptiste,nebula ...
# print only specific fields from the response
$ gh api repos/:owner/:repo/issues --jq '.[].title'
$ gh api repos/{owner}/{repo}/issues --jq '.[].title'
# use a template for the output
$ gh api repos/:owner/:repo/issues --template \
$ gh api repos/{owner}/{repo}/issues --template \
'{{range .}}{{.title}} ({{.labels | pluck "name" | join ", " | color "yellow"}}){{"\n"}}{{end}}'
# list releases with GraphQL
$ gh api graphql -F owner=':owner' -F name=':repo' -f query='
$ gh api graphql -F owner='{owner}' -F name='{repo}' -f query='
query($name: String!, $owner: String!) {
repository(owner: $owner, name: $name) {
releases(last: 3) {
@ -397,9 +397,9 @@ func processResponse(resp *http.Response, opts *ApiOptions, headersOutputStream
return
}
var placeholderRE = regexp.MustCompile(`\:(owner|repo|branch)\b`)
var placeholderRE = regexp.MustCompile(`(\:(owner|repo|branch)\b|\{(owner|repo|branch)\})`)
// fillPlaceholders populates `:owner` and `:repo` placeholders with values from the current repository
// fillPlaceholders populates `{owner}` and `{repo}` placeholders with values from the current repository
func fillPlaceholders(value string, opts *ApiOptions) (string, error) {
if !placeholderRE.MatchString(value) {
return value, nil
@ -412,11 +412,11 @@ func fillPlaceholders(value string, opts *ApiOptions) (string, error) {
filled := placeholderRE.ReplaceAllStringFunc(value, func(m string) string {
switch m {
case ":owner":
case ":owner", "{owner}":
return baseRepo.RepoOwner()
case ":repo":
case ":repo", "{repo}":
return baseRepo.RepoName()
case ":branch":
case ":branch", "{branch}":
branch, e := opts.Branch()
if e != nil {
err = e

View file

@ -870,7 +870,7 @@ func Test_magicFieldValue(t *testing.T) {
wantErr: false,
},
{
name: "placeholder",
name: "placeholder colon",
args: args{
v: ":owner",
opts: &ApiOptions{
@ -883,6 +883,20 @@ func Test_magicFieldValue(t *testing.T) {
want: "hubot",
wantErr: false,
},
{
name: "placeholder braces",
args: args{
v: "{owner}",
opts: &ApiOptions{
IO: io,
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.New("hubot", "robot-uprising"), nil
},
},
},
want: "hubot",
wantErr: false,
},
{
name: "file",
args: args{
@ -1008,6 +1022,51 @@ func Test_fillPlaceholders(t *testing.T) {
want: "repos/:owner/:repo/branches/:branch",
wantErr: true,
},
{
name: "has substitutes (braces)",
args: args{
value: "repos/{owner}/{repo}/releases",
opts: &ApiOptions{
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.New("hubot", "robot-uprising"), nil
},
},
},
want: "repos/hubot/robot-uprising/releases",
wantErr: false,
},
{
name: "has branch placeholder (braces)",
args: args{
value: "repos/cli/cli/branches/{branch}/protection/required_status_checks",
opts: &ApiOptions{
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.New("cli", "cli"), nil
},
Branch: func() (string, error) {
return "trunk", nil
},
},
},
want: "repos/cli/cli/branches/trunk/protection/required_status_checks",
wantErr: false,
},
{
name: "has branch placeholder and git is in detached head (braces)",
args: args{
value: "repos/{owner}/{repo}/branches/{branch}",
opts: &ApiOptions{
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.New("cli", "cli"), nil
},
Branch: func() (string, error) {
return "", git.ErrNotOnAnyBranch
},
},
},
want: "repos/{owner}/{repo}/branches/{branch}",
wantErr: true,
},
{
name: "no greedy substitutes",
args: args{