@@ -13,6 +13,7 @@ import (
1313 "strconv"
1414 "strings"
1515
16+ "github.com/MakeNowJust/heredoc"
1617 "github.com/cli/cli/internal/ghrepo"
1718 "github.com/cli/cli/pkg/cmdutil"
1819 "github.com/cli/cli/pkg/iostreams"
@@ -48,27 +49,45 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
4849 Short : "Make an authenticated GitHub API request" ,
4950 Long : `Makes an authenticated HTTP request to the GitHub API and prints the response.
5051
51- 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
5253"graphql" to access the GitHub API v4.
5354
55+ Placeholder values ":owner" and ":repo" in the endpoint argument will get replaced
56+ with values from the repository of the current directory.
57+
5458The default HTTP request method is "GET" normally and "POST" if any parameters
5559were added. Override the method with '--method'.
5660
57- 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
5862JSON-encoded string parameters to the POST body.
5963
6064The '--field' flag behaves like '--raw-field' with magic type conversion based
6165on the format of the value:
6266
6367- literal values "true", "false", "null", and integer numbers get converted to
6468 appropriate JSON types;
69+ - placeholder values ":owner" and ":repo" get populated with values from the
70+ repository of the current directory;
6571- if the value starts with "@", the rest of the value is interpreted as a
6672 filename to read the value from. Pass "-" to read from standard input.
6773
6874Raw request body may be passed from the outside via a file specified by '--input'.
6975Pass "-" to read from standard input. In this mode, parameters specified via
7076'--field' flags are serialized into URL query parameters.
7177` ,
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+ ` ),
7291 Args : cobra .ExactArgs (1 ),
7392 RunE : func (c * cobra.Command , args []string ) error {
7493 opts .RequestPath = args [0 ]
@@ -96,9 +115,9 @@ func apiRun(opts *ApiOptions) error {
96115 return err
97116 }
98117
99- requestPath , params , err := fillPlaceholders (opts , params )
118+ requestPath , err := fillPlaceholders (opts . RequestPath , opts )
100119 if err != nil {
101- return fmt .Errorf ("unable to expand `{...}` placeholders in query : %w" , err )
120+ return fmt .Errorf ("unable to expand placeholder in path : %w" , err )
102121 }
103122 method := opts .RequestMethod
104123 requestHeaders := opts .RequestHeaders
@@ -176,35 +195,31 @@ func apiRun(opts *ApiOptions) error {
176195 return nil
177196}
178197
179- // fillPlaceholders replaces `{owner}` and `{repo}` placeholders with values from the current repository
180- func fillPlaceholders (opts * ApiOptions , params map [string ]interface {}) (string , map [string ]interface {}, error ) {
181- query := opts .RequestPath
182- isGraphQL := opts .RequestPath == "graphql"
183-
184- if isGraphQL {
185- if q , ok := params ["query" ].(string ); ok {
186- query = q
187- }
188- }
198+ var placeholderRE = regexp .MustCompile (`\:(owner|repo)\b` )
189199
190- if ! strings .Contains (query , "{owner}" ) && ! strings .Contains (query , "{repo}" ) {
191- return opts .RequestPath , params , nil
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
192204 }
193205
194206 baseRepo , err := opts .BaseRepo ()
195207 if err != nil {
196- return opts . RequestPath , params , err
208+ return value , err
197209 }
198210
199- query = strings .ReplaceAll (query , "{owner}" , baseRepo .RepoOwner ())
200- query = strings .ReplaceAll (query , "{repo}" , baseRepo .RepoName ())
201-
202- if isGraphQL {
203- params ["query" ] = query
204- return opts .RequestPath , params , nil
205- }
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+ })
206221
207- return query , params , nil
222+ return value , nil
208223}
209224
210225func printHeaders (w io.Writer , headers http.Header , colorize bool ) {
@@ -241,7 +256,7 @@ func parseFields(opts *ApiOptions) (map[string]interface{}, error) {
241256 if err != nil {
242257 return params , err
243258 }
244- value , err := magicFieldValue (strValue , opts . IO . In )
259+ value , err := magicFieldValue (strValue , opts )
245260 if err != nil {
246261 return params , fmt .Errorf ("error parsing %q value: %w" , key , err )
247262 }
@@ -258,9 +273,9 @@ func parseField(f string) (string, string, error) {
258273 return f [0 :idx ], f [idx + 1 :], nil
259274}
260275
261- func magicFieldValue (v string , stdin io. ReadCloser ) (interface {}, error ) {
276+ func magicFieldValue (v string , opts * ApiOptions ) (interface {}, error ) {
262277 if strings .HasPrefix (v , "@" ) {
263- return readUserFile (v [1 :], stdin )
278+ return readUserFile (v [1 :], opts . IO . In )
264279 }
265280
266281 if n , err := strconv .Atoi (v ); err == nil {
@@ -275,7 +290,7 @@ func magicFieldValue(v string, stdin io.ReadCloser) (interface{}, error) {
275290 case "null" :
276291 return nil , nil
277292 default :
278- return v , nil
293+ return fillPlaceholders ( v , opts )
279294 }
280295}
281296
0 commit comments