@@ -13,6 +13,8 @@ import (
1313 "strconv"
1414 "strings"
1515
16+ "github.com/MakeNowJust/heredoc"
17+ "github.com/cli/cli/internal/ghrepo"
1618 "github.com/cli/cli/pkg/cmdutil"
1719 "github.com/cli/cli/pkg/iostreams"
1820 "github.com/cli/cli/pkg/jsoncolor"
@@ -32,40 +34,60 @@ type ApiOptions struct {
3234 ShowResponseHeaders bool
3335
3436 HttpClient func () (* http.Client , error )
37+ BaseRepo func () (ghrepo.Interface , error )
3538}
3639
3740func NewCmdApi (f * cmdutil.Factory , runF func (* ApiOptions ) error ) * cobra.Command {
3841 opts := ApiOptions {
3942 IO : f .IOStreams ,
4043 HttpClient : f .HttpClient ,
44+ BaseRepo : f .BaseRepo ,
4145 }
4246
4347 cmd := & cobra.Command {
4448 Use : "api <endpoint>" ,
4549 Short : "Make an authenticated GitHub API request" ,
4650 Long : `Makes an authenticated HTTP request to the GitHub API and prints the response.
4751
48- The < endpoint> argument should either be a path of a GitHub API v3 endpoint, or
52+ The endpoint argument should either be a path of a GitHub API v3 endpoint, or
4953"graphql" to access the GitHub API v4.
5054
55+ Placeholder values ":owner" and ":repo" in the endpoint argument will get replaced
56+ with values from the repository of the current directory.
57+
5158The default HTTP request method is "GET" normally and "POST" if any parameters
5259were added. Override the method with '--method'.
5360
54- Pass one or more '--raw-field' values in "< key>=< value> " format to add
61+ Pass one or more '--raw-field' values in "key= value" format to add
5562JSON-encoded string parameters to the POST body.
5663
5764The '--field' flag behaves like '--raw-field' with magic type conversion based
5865on the format of the value:
5966
6067- literal values "true", "false", "null", and integer numbers get converted to
6168 appropriate JSON types;
69+ - placeholder values ":owner" and ":repo" get populated with values from the
70+ repository of the current directory;
6271- if the value starts with "@", the rest of the value is interpreted as a
6372 filename to read the value from. Pass "-" to read from standard input.
6473
6574Raw request body may be passed from the outside via a file specified by '--input'.
6675Pass "-" to read from standard input. In this mode, parameters specified via
6776'--field' flags are serialized into URL query parameters.
6877` ,
78+ Example : heredoc .Doc (`
79+ $ gh api repos/:owner/:repo/releases
80+
81+ $ gh api graphql -F owner=':owner' -F name=':repo' -f query='
82+ query($name: String!, $owner: String!) {
83+ repository(owner: $owner, name: $name) {
84+ releases(last: 3) {
85+ nodes { tagName }
86+ }
87+ }
88+ }
89+ '
90+ ` ),
6991 Args : cobra .ExactArgs (1 ),
7092 RunE : func (c * cobra.Command , args []string ) error {
7193 opts .RequestPath = args [0 ]
@@ -93,8 +115,11 @@ func apiRun(opts *ApiOptions) error {
93115 return err
94116 }
95117
118+ requestPath , err := fillPlaceholders (opts .RequestPath , opts )
119+ if err != nil {
120+ return fmt .Errorf ("unable to expand placeholder in path: %w" , err )
121+ }
96122 method := opts .RequestMethod
97- requestPath := opts .RequestPath
98123 requestHeaders := opts .RequestHeaders
99124 var requestBody interface {} = params
100125
@@ -170,6 +195,33 @@ func apiRun(opts *ApiOptions) error {
170195 return nil
171196}
172197
198+ var placeholderRE = regexp .MustCompile (`\:(owner|repo)\b` )
199+
200+ // fillPlaceholders populates `:owner` and `:repo` placeholders with values from the current repository
201+ func fillPlaceholders (value string , opts * ApiOptions ) (string , error ) {
202+ if ! placeholderRE .MatchString (value ) {
203+ return value , nil
204+ }
205+
206+ baseRepo , err := opts .BaseRepo ()
207+ if err != nil {
208+ return value , err
209+ }
210+
211+ value = placeholderRE .ReplaceAllStringFunc (value , func (m string ) string {
212+ switch m {
213+ case ":owner" :
214+ return baseRepo .RepoOwner ()
215+ case ":repo" :
216+ return baseRepo .RepoName ()
217+ default :
218+ panic (fmt .Sprintf ("invalid placeholder: %q" , m ))
219+ }
220+ })
221+
222+ return value , nil
223+ }
224+
173225func printHeaders (w io.Writer , headers http.Header , colorize bool ) {
174226 var names []string
175227 for name := range headers {
@@ -204,7 +256,7 @@ func parseFields(opts *ApiOptions) (map[string]interface{}, error) {
204256 if err != nil {
205257 return params , err
206258 }
207- value , err := magicFieldValue (strValue , opts . IO . In )
259+ value , err := magicFieldValue (strValue , opts )
208260 if err != nil {
209261 return params , fmt .Errorf ("error parsing %q value: %w" , key , err )
210262 }
@@ -221,9 +273,9 @@ func parseField(f string) (string, string, error) {
221273 return f [0 :idx ], f [idx + 1 :], nil
222274}
223275
224- func magicFieldValue (v string , stdin io. ReadCloser ) (interface {}, error ) {
276+ func magicFieldValue (v string , opts * ApiOptions ) (interface {}, error ) {
225277 if strings .HasPrefix (v , "@" ) {
226- return readUserFile (v [1 :], stdin )
278+ return readUserFile (v [1 :], opts . IO . In )
227279 }
228280
229281 if n , err := strconv .Atoi (v ); err == nil {
@@ -238,7 +290,7 @@ func magicFieldValue(v string, stdin io.ReadCloser) (interface{}, error) {
238290 case "null" :
239291 return nil , nil
240292 default :
241- return v , nil
293+ return fillPlaceholders ( v , opts )
242294 }
243295}
244296
0 commit comments