Skip to content

Commit 4b3ec15

Browse files
committed
Add search command
1 parent a7931a0 commit 4b3ec15

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

api/queries_search.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package api
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/shurcooL/githubv4"
8+
)
9+
10+
type SearchRepoResult struct {
11+
NameWithOwner string
12+
Description string
13+
Stargazers struct {
14+
TotalCount int
15+
}
16+
}
17+
18+
func SearchRepos(client *Client, q string, limit int) ([]SearchRepoResult, error) {
19+
var query struct {
20+
Search struct {
21+
Nodes []struct {
22+
Repository SearchRepoResult `graphql:"...on Repository"`
23+
}
24+
PageInfo struct {
25+
HasNextPage bool
26+
EndCursor string
27+
}
28+
} `graphql:"search(query: $query, type: REPOSITORY, first: $limit, after: $endCursor)"`
29+
}
30+
31+
perPage := limit
32+
if perPage > 100 {
33+
perPage = 100
34+
}
35+
variables := map[string]interface{}{
36+
"query": githubv4.String(q),
37+
"limit": githubv4.Int(perPage),
38+
"endCursor": (*githubv4.String)(nil),
39+
}
40+
41+
v4 := githubv4.NewClient(client.http)
42+
43+
var results []SearchRepoResult
44+
45+
pagination:
46+
for {
47+
err := v4.Query(context.Background(), &query, variables)
48+
if err != nil {
49+
return nil, err
50+
}
51+
for _, n := range query.Search.Nodes {
52+
results = append(results, n.Repository)
53+
if len(results) == limit {
54+
break pagination
55+
}
56+
}
57+
if !query.Search.PageInfo.HasNextPage {
58+
break
59+
}
60+
variables["endCursor"] = githubv4.String(query.Search.PageInfo.EndCursor)
61+
}
62+
63+
return results, nil
64+
}
65+
66+
type SearchIssueResult struct {
67+
Title string
68+
Number int
69+
State string
70+
CreatedAt time.Time
71+
Repository struct {
72+
NameWithOwner string
73+
}
74+
}
75+
76+
func SearchIssues(client *Client, q string, limit int) ([]SearchIssueResult, error) {
77+
var query struct {
78+
Search struct {
79+
Nodes []struct {
80+
Issue SearchIssueResult `graphql:"...on Issue"`
81+
PullRequest SearchIssueResult `graphql:"...on PullRequest"`
82+
}
83+
PageInfo struct {
84+
HasNextPage bool
85+
EndCursor string
86+
}
87+
} `graphql:"search(query: $query, type: ISSUE, first: $limit, after: $endCursor)"`
88+
}
89+
90+
perPage := limit
91+
if perPage > 100 {
92+
perPage = 100
93+
}
94+
variables := map[string]interface{}{
95+
"query": githubv4.String(q),
96+
"limit": githubv4.Int(perPage),
97+
"endCursor": (*githubv4.String)(nil),
98+
}
99+
100+
v4 := githubv4.NewClient(client.http)
101+
102+
var results []SearchIssueResult
103+
104+
pagination:
105+
for {
106+
err := v4.Query(context.Background(), &query, variables)
107+
if err != nil {
108+
return nil, err
109+
}
110+
for _, n := range query.Search.Nodes {
111+
if n.Issue.Number > 0 {
112+
results = append(results, n.Issue)
113+
} else {
114+
results = append(results, n.PullRequest)
115+
}
116+
if len(results) == limit {
117+
break pagination
118+
}
119+
}
120+
if !query.Search.PageInfo.HasNextPage {
121+
break
122+
}
123+
variables["endCursor"] = githubv4.String(query.Search.PageInfo.EndCursor)
124+
}
125+
126+
return results, nil
127+
}

command/search.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
"github.com/cli/cli/api"
9+
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/utils"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func init() {
15+
RootCmd.AddCommand(searchCmd)
16+
searchCmd.Flags().IntP("limit", "L", 10, "limit the number of results")
17+
}
18+
19+
var searchCmd = &cobra.Command{
20+
Use: "search {repos|issues} <query>",
21+
Args: cobra.ExactArgs(2),
22+
Short: "Search GitHub",
23+
Long: `Search GitHub repositories or issues using terms in a query string.
24+
25+
The literal string "{baseRepo}" inside the search string will be substituted with
26+
the full name of the current repository. Similarly, "{owner}" will be replaced with
27+
the handle of the owner for the current repository.
28+
29+
Examples:
30+
31+
$ gh search repos 'in:readme "GitHub CLI"'
32+
$ gh search repos 'org:microsoft stars:>=15000'
33+
34+
$ gh search issues 'repo:cli/cli editor'
35+
$ gh search issues 'repo:{baseRepo} is:open team:{owner}/robots'
36+
$ gh search issues 'repo:{baseRepo} is:pr is:open review-requested:@me'
37+
38+
See <https://help.github.com/en/github/searching-for-information-on-github>`,
39+
RunE: search,
40+
}
41+
42+
func search(cmd *cobra.Command, args []string) error {
43+
ctx := contextForCommand(cmd)
44+
45+
baseRepo, err := ctx.BaseRepo()
46+
if err != nil {
47+
return err
48+
}
49+
50+
client, err := apiClientForContext(ctx)
51+
if err != nil {
52+
return err
53+
}
54+
55+
searchType := args[0]
56+
searchTerm := strings.ReplaceAll(args[1], "{baseRepo}", ghrepo.FullName(baseRepo))
57+
searchTerm = strings.ReplaceAll(searchTerm, "{owner}", baseRepo.RepoOwner())
58+
59+
limit, err := cmd.Flags().GetInt("limit")
60+
if err != nil {
61+
return err
62+
}
63+
64+
switch searchType {
65+
case "repos":
66+
results, err := api.SearchRepos(client, searchTerm, limit)
67+
if err != nil {
68+
return err
69+
}
70+
71+
table := utils.NewTablePrinter(cmd.OutOrStdout())
72+
for _, r := range results {
73+
table.AddField(r.NameWithOwner, nil, nil)
74+
table.AddField(fmt.Sprintf("%d", r.Stargazers.TotalCount), nil, utils.Yellow)
75+
table.AddField(replaceExcessiveWhitespace(r.Description), nil, nil)
76+
table.EndRow()
77+
}
78+
_ = table.Render()
79+
case "issues":
80+
results, err := api.SearchIssues(client, searchTerm, limit)
81+
if err != nil {
82+
return err
83+
}
84+
85+
table := utils.NewTablePrinter(cmd.OutOrStdout())
86+
for _, i := range results {
87+
issueID := fmt.Sprintf("%s#%d", i.Repository.NameWithOwner, i.Number)
88+
createdAt := i.CreatedAt.Format(time.RFC3339)
89+
if table.IsTTY() {
90+
createdAt = utils.FuzzyAgo(time.Since(i.CreatedAt))
91+
}
92+
93+
table.AddField(issueID, nil, colorFuncForState(i.State))
94+
table.AddField(replaceExcessiveWhitespace(i.Title), nil, nil)
95+
table.AddField(createdAt, nil, utils.Gray)
96+
table.EndRow()
97+
}
98+
_ = table.Render()
99+
default:
100+
return fmt.Errorf("unrecognized search type: %q", searchType)
101+
}
102+
103+
return nil
104+
}

0 commit comments

Comments
 (0)