-
Notifications
You must be signed in to change notification settings - Fork 7.3k
Add api command for direct API access
#909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Added some features and started to experiment with incorporating ideas from #759 and #770 when it comes to the
|
|
I'm excited about all of this! I think you showed me that custom output format that made json a little easier to grep (like, line mode or something?); are you considering bringing that over? I've been excited about that. |
|
@vilmibm So hub supports the I didn't port this over to here yet because even though the format works well for me, it was invented by me, it's not based on anything that exists elsewhere, I don't have any confirmation that others are benefitting from it, and to drill down to extract values from specific fields you still need to use regex: So I propose to address alternative formats in follow-up. Something that I'd like to explore is support a jq-like query filter that would allow you to extract specific fields without too much shell hackery β¨ |
|
I've wrapped this up as something shippable, so this is now ready for review. I plan to address advanced topics (e.g. custom input/output formats) in follow-up PRs. I've used this branch as a playground to experiment with how we might organize commands differently. The new I like this design for these reasons:
Drawbacks:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm βοΈ on the command itself; I like this approach for gh api and don't have any misgivings about it.
I do want us to be careful and thoughtful about adopting this new style for commands. I'm curious about @probablycorey 's opinion as well as the answers to the questions I left.
| HttpClient func() (*http.Client, error) | ||
| } | ||
|
|
||
| func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true that this is "more" boilerplate, but I much prefer it to the boilerplate we have now. I find this a lot easier to comprehend and reproduce than our current approach of top-level stuff and init()ing flags, which often confuses me when I'm adding a new command.
| }) | ||
|
|
||
| argv, err := shlex.Split(tt.cli) | ||
| assert.NoError(t, err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been opposed to adding testify but I'm begrudgingly ok with it seeing that it can be used in this light footprint, piecemeal fashion.
|
|
||
| func (fe FlagError) Unwrap() error { | ||
| return fe.Err | ||
| // TODO: iron out how a factory incorporates context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems important. I'm on board with this new approach to structuring our commands but dealing with context is an important part of almost every command. Do you have ideas or sketches about what this might look like? Without a clear plan it's going to be hard to motivate ourselves to apply this pattern to existing commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is indeed important, thanks for bringing it up. The api command was not really appropriate to explore more aspects of reading context in because it's so isolated, i.e. it doesn't need anything from the context other than the API token, and that part is covered. I agree that we should have a clear migration plan for other commands and, after merging this, what I plan to do is migrate a single older command over to the new approach and have that migration serve as a guidebook on how to apply this pattern.
| filename to read the value from. Pass "-" to read from standard input. | ||
| `, | ||
| Args: cobra.ExactArgs(1), | ||
| RunE: func(c *cobra.Command, args []string) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this clean separation of RunE being flag/argument processing and then the actual command logic going in its own function. It feels sustainable to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new api command lives under pkg/cmd/api/, with new commands to be added in this package pattern: pkg/cmd///. The main executable with its "root" command would import the individual packages and nest them under the gh command.
I'm π on this idea! I've been thinking we need it for more isolation and to make it easier to understand where a specific command code lives
The API stuff is super powerful, I really like the interface you've come up with. Its hard for me to say too much about it until I get to use it a bit more, but my first reaction is very cool!
| return FlagError{errors.New("issue number or URL required as argument")} | ||
| } | ||
| return nil | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π
| return httpClient(token), nil | ||
| }, | ||
| } | ||
| RootCmd.AddCommand(apiCmd.NewCmdApi(cmdFactory, nil)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming this would be the one place where we put all the top level commands? I think this will make it much more clear where commands are added. I like it.
| cmd.Flags().StringArrayVarP(&opts.RequestHeaders, "header", "H", nil, "Add an additional HTTP request header") | ||
| cmd.Flags().BoolVarP(&opts.ShowResponseHeaders, "include", "i", false, "Include HTTP response headers in the output") | ||
| return cmd | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love how each command can now have a dedicated function to set itself up π―
| method := opts.RequestMethod | ||
| if len(params) > 0 && !opts.RequestMethodPassed { | ||
| method = "POST" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little weird that opts has RequestMethod and RequestMethodPassed. Moving this logic into the RunE function and assuming RequestMethod is always set correctly is also a little weird, but I think it might be easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That can also work! I can swing both ways with this approach in particular; I just wanted to set some precedent to communicating via opts struct that a certain flag has been passed or not, since we use that information in some other commands as well. I agree that since NewCmdApi() is only concerned with setting up the cobra.Command object and defining flag parsing, it should also handle as much of the flag processing logic as it can before dispatching to the apiRun() command.
|
Not being able to do a massive number of api calls really killed the vibe on this feature... Cant even add a label to an issue since it requires an array ? |
|
@bradennapier We will be adding array support in a future release. In the meantime: echo '{"labels":["foo", "bar"]}' | gh api repos/:owner/:repo/issues/123/labels --input - |
|
Yep, thanks! Figured it out. |
|
hello from cli! |
Based on a subset of
hub apifeatures:The
-X,-F,-H, and-iflags were originally largely modelled aftercurl, since that's a tool that a lot of command-line users already have experience with. Thegh apicommand is basically curl for GitHub.Examples:
TODO:
query--method=GETshow helpful error when request failed due to insufficient OAuth scopeswaiting for feat: add command to create gist Β #543 to land firstRef. #332