294 lines
12 KiB
Go
294 lines
12 KiB
Go
// Copyright 2013 The go-github AUTHORS. All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package github
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
qs "github.com/google/go-querystring/query"
|
|
)
|
|
|
|
// SearchService provides access to the search related functions
|
|
// in the GitHub API.
|
|
//
|
|
// Each method takes a query string defining the search keywords and any search qualifiers.
|
|
// For example, when searching issues, the query "gopher is:issue language:go" will search
|
|
// for issues containing the word "gopher" in Go repositories. The method call
|
|
// opts := &github.SearchOptions{Sort: "created", Order: "asc"}
|
|
// cl.Search.Issues(ctx, "gopher is:issue language:go", opts)
|
|
// will search for such issues, sorting by creation date in ascending order
|
|
// (i.e., oldest first).
|
|
//
|
|
// If query includes multiple conditions, it MUST NOT include "+" as the condition separator.
|
|
// You have to use " " as the separator instead.
|
|
// For example, querying with "language:c++" and "leveldb", then query should be
|
|
// "language:c++ leveldb" but not "language:c+++leveldb".
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/
|
|
type SearchService service
|
|
|
|
// SearchOptions specifies optional parameters to the SearchService methods.
|
|
type SearchOptions struct {
|
|
// How to sort the search results. Possible values are:
|
|
// - for repositories: stars, fork, updated
|
|
// - for commits: author-date, committer-date
|
|
// - for code: indexed
|
|
// - for issues: comments, created, updated
|
|
// - for users: followers, repositories, joined
|
|
//
|
|
// Default is to sort by best match.
|
|
Sort string `url:"sort,omitempty"`
|
|
|
|
// Sort order if sort parameter is provided. Possible values are: asc,
|
|
// desc. Default is desc.
|
|
Order string `url:"order,omitempty"`
|
|
|
|
// Whether to retrieve text match metadata with a query
|
|
TextMatch bool `url:"-"`
|
|
|
|
ListOptions
|
|
}
|
|
|
|
// Common search parameters.
|
|
type searchParameters struct {
|
|
Query string
|
|
RepositoryID *int64 // Sent if non-nil.
|
|
}
|
|
|
|
// RepositoriesSearchResult represents the result of a repositories search.
|
|
type RepositoriesSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
Repositories []*Repository `json:"items,omitempty"`
|
|
}
|
|
|
|
// Repositories searches repositories via various criteria.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-repositories
|
|
func (s *SearchService) Repositories(ctx context.Context, query string, opts *SearchOptions) (*RepositoriesSearchResult, *Response, error) {
|
|
result := new(RepositoriesSearchResult)
|
|
resp, err := s.search(ctx, "repositories", &searchParameters{Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// TopicsSearchResult represents the result of a topics search.
|
|
type TopicsSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
Topics []*TopicResult `json:"items,omitempty"`
|
|
}
|
|
|
|
type TopicResult struct {
|
|
Name *string `json:"name,omitempty"`
|
|
DisplayName *string `json:"display_name,omitempty"`
|
|
ShortDescription *string `json:"short_description,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
CreatedBy *string `json:"created_by,omitempty"`
|
|
CreatedAt *Timestamp `json:"created_at,omitempty"`
|
|
UpdatedAt *string `json:"updated_at,omitempty"`
|
|
Featured *bool `json:"featured,omitempty"`
|
|
Curated *bool `json:"curated,omitempty"`
|
|
Score *float64 `json:"score,omitempty"`
|
|
}
|
|
|
|
// Topics finds topics via various criteria. Results are sorted by best match.
|
|
// Please see https://help.github.com/en/articles/searching-topics for more
|
|
// information about search qualifiers.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-topics
|
|
func (s *SearchService) Topics(ctx context.Context, query string, opts *SearchOptions) (*TopicsSearchResult, *Response, error) {
|
|
result := new(TopicsSearchResult)
|
|
resp, err := s.search(ctx, "topics", &searchParameters{Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// CommitsSearchResult represents the result of a commits search.
|
|
type CommitsSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
Commits []*CommitResult `json:"items,omitempty"`
|
|
}
|
|
|
|
// CommitResult represents a commit object as returned in commit search endpoint response.
|
|
type CommitResult struct {
|
|
SHA *string `json:"sha,omitempty"`
|
|
Commit *Commit `json:"commit,omitempty"`
|
|
Author *User `json:"author,omitempty"`
|
|
Committer *User `json:"committer,omitempty"`
|
|
Parents []*Commit `json:"parents,omitempty"`
|
|
HTMLURL *string `json:"html_url,omitempty"`
|
|
URL *string `json:"url,omitempty"`
|
|
CommentsURL *string `json:"comments_url,omitempty"`
|
|
|
|
Repository *Repository `json:"repository,omitempty"`
|
|
Score *float64 `json:"score,omitempty"`
|
|
}
|
|
|
|
// Commits searches commits via various criteria.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-commits
|
|
func (s *SearchService) Commits(ctx context.Context, query string, opts *SearchOptions) (*CommitsSearchResult, *Response, error) {
|
|
result := new(CommitsSearchResult)
|
|
resp, err := s.search(ctx, "commits", &searchParameters{Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// IssuesSearchResult represents the result of an issues search.
|
|
type IssuesSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
Issues []*Issue `json:"items,omitempty"`
|
|
}
|
|
|
|
// Issues searches issues via various criteria.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-issues-and-pull-requests
|
|
func (s *SearchService) Issues(ctx context.Context, query string, opts *SearchOptions) (*IssuesSearchResult, *Response, error) {
|
|
result := new(IssuesSearchResult)
|
|
resp, err := s.search(ctx, "issues", &searchParameters{Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// UsersSearchResult represents the result of a users search.
|
|
type UsersSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
Users []*User `json:"items,omitempty"`
|
|
}
|
|
|
|
// Users searches users via various criteria.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-users
|
|
func (s *SearchService) Users(ctx context.Context, query string, opts *SearchOptions) (*UsersSearchResult, *Response, error) {
|
|
result := new(UsersSearchResult)
|
|
resp, err := s.search(ctx, "users", &searchParameters{Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// Match represents a single text match.
|
|
type Match struct {
|
|
Text *string `json:"text,omitempty"`
|
|
Indices []int `json:"indices,omitempty"`
|
|
}
|
|
|
|
// TextMatch represents a text match for a SearchResult
|
|
type TextMatch struct {
|
|
ObjectURL *string `json:"object_url,omitempty"`
|
|
ObjectType *string `json:"object_type,omitempty"`
|
|
Property *string `json:"property,omitempty"`
|
|
Fragment *string `json:"fragment,omitempty"`
|
|
Matches []*Match `json:"matches,omitempty"`
|
|
}
|
|
|
|
func (tm TextMatch) String() string {
|
|
return Stringify(tm)
|
|
}
|
|
|
|
// CodeSearchResult represents the result of a code search.
|
|
type CodeSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
CodeResults []*CodeResult `json:"items,omitempty"`
|
|
}
|
|
|
|
// CodeResult represents a single search result.
|
|
type CodeResult struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Path *string `json:"path,omitempty"`
|
|
SHA *string `json:"sha,omitempty"`
|
|
HTMLURL *string `json:"html_url,omitempty"`
|
|
Repository *Repository `json:"repository,omitempty"`
|
|
TextMatches []*TextMatch `json:"text_matches,omitempty"`
|
|
}
|
|
|
|
func (c CodeResult) String() string {
|
|
return Stringify(c)
|
|
}
|
|
|
|
// Code searches code via various criteria.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-code
|
|
func (s *SearchService) Code(ctx context.Context, query string, opts *SearchOptions) (*CodeSearchResult, *Response, error) {
|
|
result := new(CodeSearchResult)
|
|
resp, err := s.search(ctx, "code", &searchParameters{Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// LabelsSearchResult represents the result of a code search.
|
|
type LabelsSearchResult struct {
|
|
Total *int `json:"total_count,omitempty"`
|
|
IncompleteResults *bool `json:"incomplete_results,omitempty"`
|
|
Labels []*LabelResult `json:"items,omitempty"`
|
|
}
|
|
|
|
// LabelResult represents a single search result.
|
|
type LabelResult struct {
|
|
ID *int64 `json:"id,omitempty"`
|
|
URL *string `json:"url,omitempty"`
|
|
Name *string `json:"name,omitempty"`
|
|
Color *string `json:"color,omitempty"`
|
|
Default *bool `json:"default,omitempty"`
|
|
Description *string `json:"description,omitempty"`
|
|
Score *float64 `json:"score,omitempty"`
|
|
}
|
|
|
|
func (l LabelResult) String() string {
|
|
return Stringify(l)
|
|
}
|
|
|
|
// Labels searches labels in the repository with ID repoID via various criteria.
|
|
//
|
|
// GitHub API docs: https://developer.github.com/v3/search/#search-labels
|
|
func (s *SearchService) Labels(ctx context.Context, repoID int64, query string, opts *SearchOptions) (*LabelsSearchResult, *Response, error) {
|
|
result := new(LabelsSearchResult)
|
|
resp, err := s.search(ctx, "labels", &searchParameters{RepositoryID: &repoID, Query: query}, opts, result)
|
|
return result, resp, err
|
|
}
|
|
|
|
// Helper function that executes search queries against different
|
|
// GitHub search types (repositories, commits, code, issues, users, labels)
|
|
//
|
|
// If searchParameters.Query includes multiple condition, it MUST NOT include "+" as condition separator.
|
|
// For example, querying with "language:c++" and "leveldb", then searchParameters.Query should be "language:c++ leveldb" but not "language:c+++leveldb".
|
|
func (s *SearchService) search(ctx context.Context, searchType string, parameters *searchParameters, opts *SearchOptions, result interface{}) (*Response, error) {
|
|
params, err := qs.Values(opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if parameters.RepositoryID != nil {
|
|
params.Set("repository_id", strconv.FormatInt(*parameters.RepositoryID, 10))
|
|
}
|
|
params.Set("q", parameters.Query)
|
|
u := fmt.Sprintf("search/%s?%s", searchType, params.Encode())
|
|
|
|
req, err := s.client.NewRequest("GET", u, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch {
|
|
case searchType == "commits":
|
|
// Accept header for search commits preview endpoint
|
|
// TODO: remove custom Accept header when this API fully launches.
|
|
req.Header.Set("Accept", mediaTypeCommitSearchPreview)
|
|
case searchType == "topics":
|
|
// Accept header for search repositories based on topics preview endpoint
|
|
// TODO: remove custom Accept header when this API fully launches.
|
|
req.Header.Set("Accept", mediaTypeTopicsPreview)
|
|
case searchType == "repositories":
|
|
// Accept header for search repositories based on topics preview endpoint
|
|
// TODO: remove custom Accept header when this API fully launches.
|
|
req.Header.Set("Accept", mediaTypeTopicsPreview)
|
|
case opts != nil && opts.TextMatch:
|
|
// Accept header defaults to "application/vnd.github.v3+json"
|
|
// We change it here to fetch back text-match metadata
|
|
req.Header.Set("Accept", "application/vnd.github.v3.text-match+json")
|
|
}
|
|
|
|
return s.client.Do(ctx, req, result)
|
|
}
|