cli/pkg/search/query.go

189 lines
4.2 KiB
Go

package search
import (
"fmt"
"reflect"
"sort"
"strings"
"unicode"
)
const (
KindRepositories = "repositories"
KindCode = "code"
KindIssues = "issues"
KindCommits = "commits"
)
type Query struct {
Keywords []string
Kind string
Limit int
Order string
Page int
Qualifiers Qualifiers
Sort string
}
type Qualifiers struct {
Archived *bool
Assignee string
Author string
AuthorDate string
AuthorEmail string
AuthorName string
Base string
Closed string
Commenter string
Comments string
Committer string
CommitterDate string
CommitterEmail string
CommitterName string
Created string
Draft *bool
Extension string
Filename string
Followers string
Fork string
Forks string
GoodFirstIssues string
Hash string
Head string
HelpWantedIssues string
In []string
Interactions string
Involves string
Is []string
Label []string
Language string
License []string
Mentions string
Merge *bool
Merged string
Milestone string
No []string
Parent string
Path string
Project string
Pushed string
Reactions string
Repo []string
Review string
ReviewRequested string
ReviewedBy string
Size string
Stars string
State string
Status string
Team string
TeamReviewRequested string
Topic []string
Topics string
Tree string
Type string
Updated string
User []string
}
func (q Query) String() string {
qualifiers := formatQualifiers(q.Qualifiers)
keywords := formatKeywords(q.Keywords)
all := append(keywords, qualifiers...)
return strings.TrimSpace(strings.Join(all, " "))
}
func (q Qualifiers) Map() map[string][]string {
m := map[string][]string{}
v := reflect.ValueOf(q)
t := reflect.TypeOf(q)
for i := 0; i < v.NumField(); i++ {
fieldName := t.Field(i).Name
key := camelToKebab(fieldName)
typ := v.FieldByName(fieldName).Kind()
value := v.FieldByName(fieldName)
switch typ {
case reflect.Ptr:
if value.IsNil() {
continue
}
v := reflect.Indirect(value)
m[key] = []string{fmt.Sprintf("%v", v)}
case reflect.Slice:
if value.IsNil() {
continue
}
s := []string{}
for i := 0; i < value.Len(); i++ {
if value.Index(i).IsZero() {
continue
}
s = append(s, fmt.Sprintf("%v", value.Index(i)))
}
m[key] = s
default:
if value.IsZero() {
continue
}
m[key] = []string{fmt.Sprintf("%v", value)}
}
}
return m
}
func quote(s string) string {
if strings.ContainsAny(s, " \"\t\r\n") {
return fmt.Sprintf("%q", s)
}
return s
}
func formatQualifiers(qs Qualifiers) []string {
var all []string
for k, vs := range qs.Map() {
for _, v := range vs {
all = append(all, fmt.Sprintf("%s:%s", k, quote(v)))
}
}
sort.Strings(all)
return all
}
func formatKeywords(ks []string) []string {
result := make([]string, len(ks))
for i, k := range ks {
before, after, found := strings.Cut(k, ":")
if !found {
result[i] = quote(k)
} else {
result[i] = fmt.Sprintf("%s:%s", before, quote(after))
}
}
return result
}
// CamelToKebab returns a copy of the string s that is converted from camel case form to '-' separated form.
func camelToKebab(s string) string {
var output []rune
var segment []rune
for _, r := range s {
if !unicode.IsLower(r) && string(r) != "-" && !unicode.IsNumber(r) {
output = addSegment(output, segment)
segment = nil
}
segment = append(segment, unicode.ToLower(r))
}
output = addSegment(output, segment)
return string(output)
}
func addSegment(inrune, segment []rune) []rune {
if len(segment) == 0 {
return inrune
}
if len(inrune) != 0 {
inrune = append(inrune, '-')
}
inrune = append(inrune, segment...)
return inrune
}