Merge pull request #3536 from rneatherway/rneatherway/placeholder-syntax
Support standard path variable replacement syntax
This commit is contained in:
commit
011e455b73
2 changed files with 133 additions and 49 deletions
|
|
@ -71,8 +71,10 @@ 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
|
||||
get replaced with values from the repository of the current directory.
|
||||
Placeholder values "{owner}", "{repo}", and "{branch}" in the endpoint argument will
|
||||
get replaced with values from the repository of the current directory. Note that in
|
||||
some shells, for example PowerShell, you may need to enclose any value that contains
|
||||
"{...}" in quotes to prevent the shell from applying special meaning to curly braces.
|
||||
|
||||
The default HTTP request method is "GET" normally and "POST" if any parameters
|
||||
were added. Override the method with %[1]s--method%[1]s.
|
||||
|
|
@ -87,7 +89,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 +108,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 +123,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,41 +399,41 @@ 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|\{[a-z]+\})`)
|
||||
|
||||
// fillPlaceholders populates `:owner` and `:repo` placeholders with values from the current repository
|
||||
// fillPlaceholders replaces placeholders with values from the current repository
|
||||
func fillPlaceholders(value string, opts *ApiOptions) (string, error) {
|
||||
if !placeholderRE.MatchString(value) {
|
||||
return value, nil
|
||||
}
|
||||
var err error
|
||||
return placeholderRE.ReplaceAllStringFunc(value, func(m string) string {
|
||||
var name string
|
||||
if m[0] == ':' {
|
||||
name = m[1:]
|
||||
} else {
|
||||
name = m[1 : len(m)-1]
|
||||
}
|
||||
|
||||
baseRepo, err := opts.BaseRepo()
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
filled := placeholderRE.ReplaceAllStringFunc(value, func(m string) string {
|
||||
switch m {
|
||||
case ":owner":
|
||||
return baseRepo.RepoOwner()
|
||||
case ":repo":
|
||||
return baseRepo.RepoName()
|
||||
case ":branch":
|
||||
branch, e := opts.Branch()
|
||||
if e != nil {
|
||||
switch name {
|
||||
case "owner":
|
||||
if baseRepo, e := opts.BaseRepo(); e == nil {
|
||||
return baseRepo.RepoOwner()
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
case "repo":
|
||||
if baseRepo, e := opts.BaseRepo(); e == nil {
|
||||
return baseRepo.RepoName()
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
case "branch":
|
||||
if branch, e := opts.Branch(); e == nil {
|
||||
return branch
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
return branch
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid placeholder: %q", m))
|
||||
}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
return filled, nil
|
||||
return m
|
||||
}), err
|
||||
}
|
||||
|
||||
func printHeaders(w io.Writer, headers http.Header, colorize bool) {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
@ -964,7 +978,7 @@ func Test_fillPlaceholders(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "has substitutes",
|
||||
name: "has substitutes (colon)",
|
||||
args: args{
|
||||
value: "repos/:owner/:repo/releases",
|
||||
opts: &ApiOptions{
|
||||
|
|
@ -977,39 +991,96 @@ func Test_fillPlaceholders(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "has branch placeholder",
|
||||
name: "has branch placeholder (colon)",
|
||||
args: args{
|
||||
value: "repos/cli/cli/branches/:branch/protection/required_status_checks",
|
||||
value: "repos/owner/repo/branches/:branch/protection/required_status_checks",
|
||||
opts: &ApiOptions{
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("cli", "cli"), nil
|
||||
},
|
||||
BaseRepo: nil,
|
||||
Branch: func() (string, error) {
|
||||
return "trunk", nil
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "repos/cli/cli/branches/trunk/protection/required_status_checks",
|
||||
want: "repos/owner/repo/branches/trunk/protection/required_status_checks",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "has branch placeholder and git is in detached head",
|
||||
name: "has branch placeholder and git is in detached head (colon)",
|
||||
args: args{
|
||||
value: "repos/:owner/:repo/branches/:branch",
|
||||
opts: &ApiOptions{
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("cli", "cli"), nil
|
||||
return ghrepo.New("hubot", "robot-uprising"), nil
|
||||
},
|
||||
Branch: func() (string, error) {
|
||||
return "", git.ErrNotOnAnyBranch
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "repos/:owner/:repo/branches/:branch",
|
||||
want: "repos/hubot/robot-uprising/branches/:branch",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no greedy substitutes",
|
||||
name: "has substitutes",
|
||||
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",
|
||||
args: args{
|
||||
value: "repos/owner/repo/branches/{branch}/protection/required_status_checks",
|
||||
opts: &ApiOptions{
|
||||
BaseRepo: nil,
|
||||
Branch: func() (string, error) {
|
||||
return "trunk", nil
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "repos/owner/repo/branches/trunk/protection/required_status_checks",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "has branch placeholder and git is in detached head",
|
||||
args: args{
|
||||
value: "repos/{owner}/{repo}/branches/{branch}",
|
||||
opts: &ApiOptions{
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("hubot", "robot-uprising"), nil
|
||||
},
|
||||
Branch: func() (string, error) {
|
||||
return "", git.ErrNotOnAnyBranch
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "repos/hubot/robot-uprising/branches/{branch}",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "surfaces errors in earlier placeholders",
|
||||
args: args{
|
||||
value: "{branch}-{owner}",
|
||||
opts: &ApiOptions{
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("hubot", "robot-uprising"), nil
|
||||
},
|
||||
Branch: func() (string, error) {
|
||||
return "", git.ErrNotOnAnyBranch
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "{branch}-hubot",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no greedy substitutes (colon)",
|
||||
args: args{
|
||||
value: ":ownership/:repository",
|
||||
opts: &ApiOptions{
|
||||
|
|
@ -1019,6 +1090,17 @@ func Test_fillPlaceholders(t *testing.T) {
|
|||
want: ":ownership/:repository",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "non-placeholders are left intact",
|
||||
args: args{
|
||||
value: "{}{ownership}/{repository}",
|
||||
opts: &ApiOptions{
|
||||
BaseRepo: nil,
|
||||
},
|
||||
},
|
||||
want: "{}{ownership}/{repository}",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue