453 lines
11 KiB
Go
453 lines
11 KiB
Go
package search
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
)
|
|
|
|
var CodeFields = []string{
|
|
"path",
|
|
"repository",
|
|
"sha",
|
|
"textMatches",
|
|
"url",
|
|
}
|
|
|
|
var textMatchFields = []string{
|
|
"fragment",
|
|
"matches",
|
|
"type",
|
|
"property",
|
|
}
|
|
|
|
var CommitFields = []string{
|
|
"author",
|
|
"commit",
|
|
"committer",
|
|
"sha",
|
|
"id",
|
|
"parents",
|
|
"repository",
|
|
"url",
|
|
}
|
|
|
|
var RepositoryFields = []string{
|
|
"createdAt",
|
|
"defaultBranch",
|
|
"description",
|
|
"forksCount",
|
|
"fullName",
|
|
"hasDownloads",
|
|
"hasIssues",
|
|
"hasPages",
|
|
"hasProjects",
|
|
"hasWiki",
|
|
"homepage",
|
|
"id",
|
|
"isArchived",
|
|
"isDisabled",
|
|
"isFork",
|
|
"isPrivate",
|
|
"language",
|
|
"license",
|
|
"name",
|
|
"openIssuesCount",
|
|
"owner",
|
|
"pushedAt",
|
|
"size",
|
|
"stargazersCount",
|
|
"updatedAt",
|
|
"url",
|
|
"visibility",
|
|
"watchersCount",
|
|
}
|
|
|
|
var IssueFields = []string{
|
|
"assignees",
|
|
"author",
|
|
"authorAssociation",
|
|
"body",
|
|
"closedAt",
|
|
"commentsCount",
|
|
"createdAt",
|
|
"id",
|
|
"isLocked",
|
|
"isPullRequest",
|
|
"labels",
|
|
"number",
|
|
"repository",
|
|
"state",
|
|
"title",
|
|
"updatedAt",
|
|
"url",
|
|
}
|
|
|
|
var PullRequestFields = append(IssueFields,
|
|
"isDraft",
|
|
)
|
|
|
|
type CodeResult struct {
|
|
IncompleteResults bool `json:"incomplete_results"`
|
|
Items []Code `json:"items"`
|
|
// Number of code search results matching the query on the server. Ignoring limit.
|
|
Total int `json:"total_count"`
|
|
}
|
|
|
|
type CommitsResult struct {
|
|
IncompleteResults bool `json:"incomplete_results"`
|
|
Items []Commit `json:"items"`
|
|
// Number of commits matching the query on the server. Ignoring limit.
|
|
Total int `json:"total_count"`
|
|
}
|
|
|
|
type RepositoriesResult struct {
|
|
IncompleteResults bool `json:"incomplete_results"`
|
|
Items []Repository `json:"items"`
|
|
// Number of repositories matching the query on the server. Ignoring limit.
|
|
Total int `json:"total_count"`
|
|
}
|
|
|
|
type IssuesResult struct {
|
|
IncompleteResults bool `json:"incomplete_results"`
|
|
Items []Issue `json:"items"`
|
|
// Number of isssues matching the query on the server. Ignoring limit.
|
|
Total int `json:"total_count"`
|
|
}
|
|
|
|
type Code struct {
|
|
Name string `json:"name"`
|
|
Path string `json:"path"`
|
|
Repository Repository `json:"repository"`
|
|
Sha string `json:"sha"`
|
|
TextMatches []TextMatch `json:"text_matches"`
|
|
URL string `json:"html_url"`
|
|
}
|
|
|
|
type TextMatch struct {
|
|
Fragment string `json:"fragment"`
|
|
Matches []Match `json:"matches"`
|
|
Type string `json:"object_type"`
|
|
Property string `json:"property"`
|
|
}
|
|
|
|
type Match struct {
|
|
Indices []int `json:"indices"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type Commit struct {
|
|
Author User `json:"author"`
|
|
Committer User `json:"committer"`
|
|
ID string `json:"node_id"`
|
|
Info CommitInfo `json:"commit"`
|
|
Parents []Parent `json:"parents"`
|
|
Repo Repository `json:"repository"`
|
|
Sha string `json:"sha"`
|
|
URL string `json:"html_url"`
|
|
}
|
|
|
|
type CommitInfo struct {
|
|
Author CommitUser `json:"author"`
|
|
CommentCount int `json:"comment_count"`
|
|
Committer CommitUser `json:"committer"`
|
|
Message string `json:"message"`
|
|
Tree Tree `json:"tree"`
|
|
}
|
|
|
|
type CommitUser struct {
|
|
Date time.Time `json:"date"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
type Tree struct {
|
|
Sha string `json:"sha"`
|
|
}
|
|
|
|
type Parent struct {
|
|
Sha string `json:"sha"`
|
|
URL string `json:"html_url"`
|
|
}
|
|
|
|
type Repository struct {
|
|
CreatedAt time.Time `json:"created_at"`
|
|
DefaultBranch string `json:"default_branch"`
|
|
Description string `json:"description"`
|
|
ForksCount int `json:"forks_count"`
|
|
FullName string `json:"full_name"`
|
|
HasDownloads bool `json:"has_downloads"`
|
|
HasIssues bool `json:"has_issues"`
|
|
HasPages bool `json:"has_pages"`
|
|
HasProjects bool `json:"has_projects"`
|
|
HasWiki bool `json:"has_wiki"`
|
|
Homepage string `json:"homepage"`
|
|
ID string `json:"node_id"`
|
|
IsArchived bool `json:"archived"`
|
|
IsDisabled bool `json:"disabled"`
|
|
IsFork bool `json:"fork"`
|
|
IsPrivate bool `json:"private"`
|
|
Language string `json:"language"`
|
|
License License `json:"license"`
|
|
MasterBranch string `json:"master_branch"`
|
|
Name string `json:"name"`
|
|
OpenIssuesCount int `json:"open_issues_count"`
|
|
Owner User `json:"owner"`
|
|
PushedAt time.Time `json:"pushed_at"`
|
|
Size int `json:"size"`
|
|
StargazersCount int `json:"stargazers_count"`
|
|
URL string `json:"html_url"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
Visibility string `json:"visibility"`
|
|
WatchersCount int `json:"watchers_count"`
|
|
}
|
|
|
|
type License struct {
|
|
Key string `json:"key"`
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type User struct {
|
|
GravatarID string `json:"gravatar_id"`
|
|
ID string `json:"node_id"`
|
|
Login string `json:"login"`
|
|
SiteAdmin bool `json:"site_admin"`
|
|
Type string `json:"type"`
|
|
URL string `json:"html_url"`
|
|
}
|
|
|
|
type Issue struct {
|
|
Assignees []User `json:"assignees"`
|
|
Author User `json:"user"`
|
|
AuthorAssociation string `json:"author_association"`
|
|
Body string `json:"body"`
|
|
ClosedAt time.Time `json:"closed_at"`
|
|
CommentsCount int `json:"comments"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ID string `json:"node_id"`
|
|
Labels []Label `json:"labels"`
|
|
// This is a PullRequest field which does not appear in issue results,
|
|
// but lives outside the PullRequest object.
|
|
IsDraft *bool `json:"draft,omitempty"`
|
|
IsLocked bool `json:"locked"`
|
|
Number int `json:"number"`
|
|
PullRequest PullRequest `json:"pull_request"`
|
|
RepositoryURL string `json:"repository_url"`
|
|
// StateInternal should not be used directly. Use State() instead.
|
|
StateInternal string `json:"state"`
|
|
StateReason string `json:"state_reason"`
|
|
Title string `json:"title"`
|
|
URL string `json:"html_url"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type PullRequest struct {
|
|
URL string `json:"html_url"`
|
|
MergedAt time.Time `json:"merged_at"`
|
|
}
|
|
|
|
type Label struct {
|
|
Color string `json:"color"`
|
|
Description string `json:"description"`
|
|
ID string `json:"node_id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (u User) IsBot() bool {
|
|
// copied from api/queries_issue.go
|
|
// would ideally be shared, but it would require coordinating a "user"
|
|
// abstraction in a bunch of places.
|
|
return u.ID == ""
|
|
}
|
|
|
|
func (u User) ExportData() map[string]interface{} {
|
|
isBot := u.IsBot()
|
|
login := u.Login
|
|
if isBot {
|
|
login = "app/" + login
|
|
}
|
|
return map[string]interface{}{
|
|
"id": u.ID,
|
|
"login": login,
|
|
"type": u.Type,
|
|
"url": u.URL,
|
|
"is_bot": isBot,
|
|
}
|
|
}
|
|
|
|
func (code Code) ExportData(fields []string) map[string]interface{} {
|
|
v := reflect.ValueOf(code)
|
|
data := map[string]interface{}{}
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "textMatches":
|
|
matches := make([]interface{}, 0, len(code.TextMatches))
|
|
for _, match := range code.TextMatches {
|
|
matches = append(matches, match.ExportData(textMatchFields))
|
|
}
|
|
data[f] = matches
|
|
default:
|
|
sf := fieldByName(v, f)
|
|
data[f] = sf.Interface()
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
func (textMatch TextMatch) ExportData(fields []string) map[string]interface{} {
|
|
return cmdutil.StructExportData(textMatch, fields)
|
|
}
|
|
|
|
func (commit Commit) ExportData(fields []string) map[string]interface{} {
|
|
v := reflect.ValueOf(commit)
|
|
data := map[string]interface{}{}
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "author":
|
|
data[f] = commit.Author.ExportData()
|
|
case "commit":
|
|
info := commit.Info
|
|
data[f] = map[string]interface{}{
|
|
"author": map[string]interface{}{
|
|
"date": info.Author.Date,
|
|
"email": info.Author.Email,
|
|
"name": info.Author.Name,
|
|
},
|
|
"committer": map[string]interface{}{
|
|
"date": info.Committer.Date,
|
|
"email": info.Committer.Email,
|
|
"name": info.Committer.Name,
|
|
},
|
|
"comment_count": info.CommentCount,
|
|
"message": info.Message,
|
|
"tree": map[string]interface{}{"sha": info.Tree.Sha},
|
|
}
|
|
case "committer":
|
|
data[f] = commit.Committer.ExportData()
|
|
case "parents":
|
|
parents := make([]interface{}, 0, len(commit.Parents))
|
|
for _, parent := range commit.Parents {
|
|
parents = append(parents, map[string]interface{}{
|
|
"sha": parent.Sha,
|
|
"url": parent.URL,
|
|
})
|
|
}
|
|
data[f] = parents
|
|
case "repository":
|
|
repo := commit.Repo
|
|
data[f] = map[string]interface{}{
|
|
"description": repo.Description,
|
|
"fullName": repo.FullName,
|
|
"name": repo.Name,
|
|
"id": repo.ID,
|
|
"isFork": repo.IsFork,
|
|
"isPrivate": repo.IsPrivate,
|
|
"owner": repo.Owner.ExportData(),
|
|
"url": repo.URL,
|
|
}
|
|
default:
|
|
sf := fieldByName(v, f)
|
|
data[f] = sf.Interface()
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
func (repo Repository) ExportData(fields []string) map[string]interface{} {
|
|
v := reflect.ValueOf(repo)
|
|
data := map[string]interface{}{}
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "license":
|
|
data[f] = map[string]interface{}{
|
|
"key": repo.License.Key,
|
|
"name": repo.License.Name,
|
|
"url": repo.License.URL,
|
|
}
|
|
case "owner":
|
|
data[f] = repo.Owner.ExportData()
|
|
default:
|
|
sf := fieldByName(v, f)
|
|
data[f] = sf.Interface()
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
func (repo Repository) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(map[string]interface{}{
|
|
"id": repo.ID,
|
|
"nameWithOwner": repo.FullName,
|
|
"url": repo.URL,
|
|
"isPrivate": repo.IsPrivate,
|
|
"isFork": repo.IsFork,
|
|
})
|
|
}
|
|
|
|
// The state of an issue or a pull request, may be either open or closed.
|
|
// For a pull request, the "merged" state is inferred from a value for merged_at and
|
|
// which we take return instead of the "closed" state.
|
|
func (issue Issue) State() string {
|
|
if !issue.PullRequest.MergedAt.IsZero() {
|
|
return "merged"
|
|
}
|
|
return issue.StateInternal
|
|
}
|
|
|
|
func (issue Issue) IsPullRequest() bool {
|
|
return issue.PullRequest.URL != ""
|
|
}
|
|
|
|
func (issue Issue) ExportData(fields []string) map[string]interface{} {
|
|
v := reflect.ValueOf(issue)
|
|
data := map[string]interface{}{}
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "assignees":
|
|
assignees := make([]interface{}, 0, len(issue.Assignees))
|
|
for _, assignee := range issue.Assignees {
|
|
assignees = append(assignees, assignee.ExportData())
|
|
}
|
|
data[f] = assignees
|
|
case "author":
|
|
data[f] = issue.Author.ExportData()
|
|
case "isPullRequest":
|
|
data[f] = issue.IsPullRequest()
|
|
case "labels":
|
|
labels := make([]interface{}, 0, len(issue.Labels))
|
|
for _, label := range issue.Labels {
|
|
labels = append(labels, map[string]interface{}{
|
|
"color": label.Color,
|
|
"description": label.Description,
|
|
"id": label.ID,
|
|
"name": label.Name,
|
|
})
|
|
}
|
|
data[f] = labels
|
|
case "repository":
|
|
comp := strings.Split(issue.RepositoryURL, "/")
|
|
name := comp[len(comp)-1]
|
|
nameWithOwner := strings.Join(comp[len(comp)-2:], "/")
|
|
data[f] = map[string]interface{}{
|
|
"name": name,
|
|
"nameWithOwner": nameWithOwner,
|
|
}
|
|
case "state":
|
|
data[f] = issue.State()
|
|
default:
|
|
sf := fieldByName(v, f)
|
|
data[f] = sf.Interface()
|
|
}
|
|
}
|
|
return data
|
|
}
|
|
|
|
func fieldByName(v reflect.Value, field string) reflect.Value {
|
|
return v.FieldByNameFunc(func(s string) bool {
|
|
return strings.EqualFold(field, s)
|
|
})
|
|
}
|