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 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 { for i, k := range ks { before, after, found := strings.Cut(k, ":") if !found { ks[i] = quote(k) } else { ks[i] = fmt.Sprintf("%s:%s", before, quote(after)) } } return ks } // 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 }