diff --git a/pkg/search/query.go b/pkg/search/query.go index f6e7fc05d..a0300124b 100644 --- a/pkg/search/query.go +++ b/pkg/search/query.go @@ -9,12 +9,17 @@ import ( ) const ( + // KindRepositories is the search kind for repository searches. KindRepositories = "repositories" - KindCode = "code" - KindIssues = "issues" - KindCommits = "commits" + // KindCode is the search kind for code searches. + KindCode = "code" + // KindIssues is the search kind for issue and pull request searches. + KindIssues = "issues" + // KindCommits is the search kind for commit searches. + KindCommits = "commits" ) +// Query represents a GitHub search query with keywords, qualifiers, and pagination options. type Query struct { // Keywords holds the list of keywords to search for. These keywords are // treated as individual components of a search query, and will get quoted @@ -39,6 +44,7 @@ type Query struct { Sort string } +// Qualifiers represents the set of search qualifier key-value pairs used to filter search results. type Qualifiers struct { Archived *bool Assignee string @@ -234,6 +240,7 @@ func groupWithOR(qualifier string, vs []string) string { return fmt.Sprintf("(%s)", strings.Join(all, " OR ")) } +// Map returns the qualifiers as a map of kebab-case keys to their string values. func (q Qualifiers) Map() map[string][]string { m := map[string][]string{} v := reflect.ValueOf(q) diff --git a/pkg/search/result.go b/pkg/search/result.go index 0b9d1ab16..5529442d0 100644 --- a/pkg/search/result.go +++ b/pkg/search/result.go @@ -9,6 +9,7 @@ import ( "github.com/cli/cli/v2/pkg/cmdutil" ) +// CodeFields is the list of supported field names for code search result export. var CodeFields = []string{ "path", "repository", @@ -24,6 +25,7 @@ var textMatchFields = []string{ "property", } +// CommitFields is the list of supported field names for commit search result export. var CommitFields = []string{ "author", "commit", @@ -35,6 +37,7 @@ var CommitFields = []string{ "url", } +// RepositoryFields is the list of supported field names for repository search result export. var RepositoryFields = []string{ "createdAt", "defaultBranch", @@ -66,6 +69,7 @@ var RepositoryFields = []string{ "watchersCount", } +// IssueFields is the list of supported field names for issue search result export. var IssueFields = []string{ "assignees", "author", @@ -86,10 +90,12 @@ var IssueFields = []string{ "url", } +// PullRequestFields is the list of supported field names for pull request search result export. var PullRequestFields = append(IssueFields, "isDraft", ) +// CodeResult represents the response from a code search query. type CodeResult struct { IncompleteResults bool `json:"incomplete_results"` Items []Code `json:"items"` @@ -97,6 +103,7 @@ type CodeResult struct { Total int `json:"total_count"` } +// CommitsResult represents the response from a commits search query. type CommitsResult struct { IncompleteResults bool `json:"incomplete_results"` Items []Commit `json:"items"` @@ -104,6 +111,7 @@ type CommitsResult struct { Total int `json:"total_count"` } +// RepositoriesResult represents the response from a repositories search query. type RepositoriesResult struct { IncompleteResults bool `json:"incomplete_results"` Items []Repository `json:"items"` @@ -111,6 +119,7 @@ type RepositoriesResult struct { Total int `json:"total_count"` } +// IssuesResult represents the response from an issues search query. type IssuesResult struct { IncompleteResults bool `json:"incomplete_results"` Items []Issue `json:"items"` @@ -118,6 +127,7 @@ type IssuesResult struct { Total int `json:"total_count"` } +// Code represents a single code search result item. type Code struct { Name string `json:"name"` Path string `json:"path"` @@ -127,6 +137,7 @@ type Code struct { URL string `json:"html_url"` } +// TextMatch represents a text fragment with highlighted matches in a code search result. type TextMatch struct { Fragment string `json:"fragment"` Matches []Match `json:"matches"` @@ -134,11 +145,13 @@ type TextMatch struct { Property string `json:"property"` } +// Match represents an individual matched substring within a TextMatch fragment. type Match struct { Indices []int `json:"indices"` Text string `json:"text"` } +// Commit represents a single commit search result item. type Commit struct { Author User `json:"author"` Committer User `json:"committer"` @@ -150,6 +163,7 @@ type Commit struct { URL string `json:"html_url"` } +// CommitInfo contains the detailed commit metadata such as author, committer, and message. type CommitInfo struct { Author CommitUser `json:"author"` CommentCount int `json:"comment_count"` @@ -158,21 +172,25 @@ type CommitInfo struct { Tree Tree `json:"tree"` } +// CommitUser represents the author or committer identity within a commit. type CommitUser struct { Date time.Time `json:"date"` Email string `json:"email"` Name string `json:"name"` } +// Tree represents a Git tree object referenced by a commit. type Tree struct { Sha string `json:"sha"` } +// Parent represents a parent commit reference in a commit search result. type Parent struct { Sha string `json:"sha"` URL string `json:"html_url"` } +// Repository represents a GitHub repository returned by a search query. type Repository struct { CreatedAt time.Time `json:"created_at"` DefaultBranch string `json:"default_branch"` @@ -205,12 +223,14 @@ type Repository struct { WatchersCount int `json:"watchers_count"` } +// License represents a repository's license information. type License struct { Key string `json:"key"` Name string `json:"name"` URL string `json:"url"` } +// User represents a GitHub user or bot account. type User struct { GravatarID string `json:"gravatar_id"` ID string `json:"node_id"` @@ -220,6 +240,7 @@ type User struct { URL string `json:"html_url"` } +// Issue represents a single issue or pull request search result item. type Issue struct { Assignees []User `json:"assignees"` Author User `json:"user"` @@ -245,11 +266,13 @@ type Issue struct { UpdatedAt time.Time `json:"updated_at"` } +// PullRequest contains pull request-specific fields embedded in an Issue result. type PullRequest struct { URL string `json:"html_url"` MergedAt time.Time `json:"merged_at"` } +// Label represents a label attached to an issue or pull request. type Label struct { Color string `json:"color"` Description string `json:"description"` @@ -257,6 +280,7 @@ type Label struct { Name string `json:"name"` } +// IsBot reports whether the user is a bot account (identified by having no node ID). func (u User) IsBot() bool { // copied from api/queries_issue.go // would ideally be shared, but it would require coordinating a "user" @@ -264,6 +288,7 @@ func (u User) IsBot() bool { return u.ID == "" } +// ExportData returns the user's fields as a map for serialization. func (u User) ExportData() map[string]interface{} { isBot := u.IsBot() login := u.Login @@ -279,6 +304,7 @@ func (u User) ExportData() map[string]interface{} { } } +// ExportData returns the selected fields of a code result as a map for serialization. func (code Code) ExportData(fields []string) map[string]interface{} { v := reflect.ValueOf(code) data := map[string]interface{}{} @@ -298,10 +324,12 @@ func (code Code) ExportData(fields []string) map[string]interface{} { return data } +// ExportData returns the selected fields of a text match as a map for serialization. func (textMatch TextMatch) ExportData(fields []string) map[string]interface{} { return cmdutil.StructExportData(textMatch, fields) } +// ExportData returns the selected fields of a commit result as a map for serialization. func (commit Commit) ExportData(fields []string) map[string]interface{} { v := reflect.ValueOf(commit) data := map[string]interface{}{} @@ -357,6 +385,7 @@ func (commit Commit) ExportData(fields []string) map[string]interface{} { return data } +// ExportData returns the selected fields of a repository result as a map for serialization. func (repo Repository) ExportData(fields []string) map[string]interface{} { v := reflect.ValueOf(repo) data := map[string]interface{}{} @@ -378,6 +407,7 @@ func (repo Repository) ExportData(fields []string) map[string]interface{} { return data } +// MarshalJSON implements custom JSON marshaling for Repository with a subset of fields. func (repo Repository) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ "id": repo.ID, @@ -398,10 +428,12 @@ func (issue Issue) State() string { return issue.StateInternal } +// IsPullRequest reports whether the issue is actually a pull request. func (issue Issue) IsPullRequest() bool { return issue.PullRequest.URL != "" } +// ExportData returns the selected fields of an issue result as a map for serialization. func (issue Issue) ExportData(fields []string) map[string]interface{} { v := reflect.ValueOf(issue) data := map[string]interface{}{} diff --git a/pkg/search/searcher.go b/pkg/search/searcher.go index 5b05e1619..36a0368bb 100644 --- a/pkg/search/searcher.go +++ b/pkg/search/searcher.go @@ -25,6 +25,8 @@ var linkRE = regexp.MustCompile(`<([^>]+)>;\s*rel="([^"]+)"`) var pageRE = regexp.MustCompile(`(\?|&)page=(\d*)`) var jsonTypeRE = regexp.MustCompile(`[/+]json($|;)`) +// Searcher defines the interface for performing GitHub search queries. +// //go:generate moq -rm -out searcher_mock.go . Searcher type Searcher interface { Code(Query) (CodeResult, error) @@ -54,6 +56,7 @@ type httpErrorItem struct { Resource string } +// NewSearcher creates a new Searcher backed by the given HTTP client and host. func NewSearcher(client *http.Client, host string, detector fd.Detector) Searcher { return &searcher{ client: client, @@ -62,6 +65,7 @@ func NewSearcher(client *http.Client, host string, detector fd.Detector) Searche } } +// Code performs a code search and returns matching results. func (s searcher) Code(query Query) (CodeResult, error) { result := CodeResult{} @@ -102,6 +106,7 @@ func (s searcher) Code(query Query) (CodeResult, error) { return result, nil } +// Commits performs a commit search and returns matching results. func (s searcher) Commits(query Query) (CommitsResult, error) { result := CommitsResult{} @@ -130,6 +135,7 @@ func (s searcher) Commits(query Query) (CommitsResult, error) { return result, nil } +// Repositories performs a repository search and returns matching results. func (s searcher) Repositories(query Query) (RepositoriesResult, error) { result := RepositoriesResult{} @@ -158,6 +164,7 @@ func (s searcher) Repositories(query Query) (RepositoriesResult, error) { return result, nil } +// Issues performs an issue and pull request search and returns matching results. func (s searcher) Issues(query Query) (IssuesResult, error) { result := IssuesResult{} @@ -287,6 +294,7 @@ func (s searcher) URL(query Query) string { return url } +// Error returns a human-readable description of the HTTP error. func (err httpError) Error() string { if err.StatusCode != 422 || len(err.Errors) == 0 { return fmt.Sprintf("HTTP %d: %s (%s)", err.StatusCode, err.Message, err.RequestURL)