@@ -38,24 +38,141 @@ func LibrarySearch(ctx context.Context, req *rpc.LibrarySearchRequest) (*rpc.Lib
38
38
return searchLibrary (req , lm ), nil
39
39
}
40
40
41
+ // MatcherTokensFromQueryString parses the query string into tokens of interest
42
+ // for the qualifier-value pattern matching.
43
+ func MatcherTokensFromQueryString (query string ) []string {
44
+ escaped := false
45
+ quoted := false
46
+ tokens := []string {}
47
+ sb := & strings.Builder {}
48
+
49
+ for _ , r := range query {
50
+ // Short circuit the loop on backslash so that all other paths can clear
51
+ // the escaped flag.
52
+ if ! escaped && r == '\\' {
53
+ escaped = true
54
+ continue
55
+ }
56
+
57
+ if r == '"' {
58
+ if ! escaped {
59
+ quoted = ! quoted
60
+ } else {
61
+ sb .WriteRune (r )
62
+ }
63
+ } else if ! quoted && r == ' ' {
64
+ tokens = append (tokens , strings .ToLower (sb .String ()))
65
+ sb .Reset ()
66
+ } else {
67
+ sb .WriteRune (r )
68
+ }
69
+ escaped = false
70
+ }
71
+ if sb .Len () > 0 {
72
+ tokens = append (tokens , strings .ToLower (sb .String ()))
73
+ }
74
+
75
+ return tokens
76
+ }
77
+
78
+ // DefaulLibraryMatchExtractor returns a string describing the library that
79
+ // is used for the simple search.
80
+ func DefaultLibraryMatchExtractor (lib * librariesindex.Library ) string {
81
+ res := lib .Name + " " +
82
+ lib .Latest .Paragraph + " " +
83
+ lib .Latest .Sentence + " " +
84
+ lib .Latest .Author + " "
85
+ for _ , include := range lib .Latest .ProvidesIncludes {
86
+ res += include + " "
87
+ }
88
+ return res
89
+ }
90
+
91
+ // MatcherFromQueryString returns a closure that takes a library as a
92
+ // parameter and returns true if the library matches the query.
93
+ func MatcherFromQueryString (query string ) func (* librariesindex.Library ) bool {
94
+ // A qv-query is one using <qualifier>[:=]<value> syntax.
95
+ qvQuery := strings .Contains (query , ":" ) || strings .Contains (query , "=" )
96
+
97
+ if ! qvQuery {
98
+ queryTerms := utils .SearchTermsFromQueryString (query )
99
+ return func (lib * librariesindex.Library ) bool {
100
+ return utils .Match (DefaultLibraryMatchExtractor (lib ), queryTerms )
101
+ }
102
+ }
103
+
104
+ joinedStrings := func (strs []string ) string {
105
+ return strings .Join (strs , " " )
106
+ }
107
+
108
+ qualifiers := []struct {
109
+ key string
110
+ extractor func (* librariesindex.Library ) string
111
+ }{
112
+ // The library name comes from the Library object.
113
+ {"name" , func (lib * librariesindex.Library ) string { return lib .Name }},
114
+
115
+ // All other values come from the latest Release.
116
+ {"architectures" , func (lib * librariesindex.Library ) string { return joinedStrings (lib .Latest .Architectures ) }},
117
+ {"author" , func (lib * librariesindex.Library ) string { return lib .Latest .Author }},
118
+ {"category" , func (lib * librariesindex.Library ) string { return lib .Latest .Category }},
119
+ {"dependencies" , func (lib * librariesindex.Library ) string {
120
+ names := []string {}
121
+ for _ , dep := range lib .Latest .Dependencies {
122
+ names = append (names , dep .GetName ())
123
+ }
124
+ return joinedStrings (names )
125
+ }},
126
+ {"maintainer" , func (lib * librariesindex.Library ) string { return lib .Latest .Maintainer }},
127
+ {"paragraph" , func (lib * librariesindex.Library ) string { return lib .Latest .Paragraph }},
128
+ {"sentence" , func (lib * librariesindex.Library ) string { return lib .Latest .Sentence }},
129
+ {"types" , func (lib * librariesindex.Library ) string { return joinedStrings (lib .Latest .Types ) }},
130
+ {"version" , func (lib * librariesindex.Library ) string { return lib .Latest .Version .String () }},
131
+ {"website" , func (lib * librariesindex.Library ) string { return lib .Latest .Website }},
132
+ }
133
+
134
+ queryTerms := MatcherTokensFromQueryString (query )
135
+
136
+ return func (lib * librariesindex.Library ) bool {
137
+ matched := true
138
+ for _ , term := range queryTerms {
139
+
140
+ // Flag indicating whether the search term matched a known qualifier
141
+ knownQualifier := false
142
+
143
+ for _ , q := range qualifiers {
144
+ if strings .HasPrefix (term , q .key + ":" ) {
145
+ target := strings .TrimPrefix (term , q .key + ":" )
146
+ matched = (matched && utils .Match (q .extractor (lib ), []string {target }))
147
+ knownQualifier = true
148
+ break
149
+ } else if strings .HasPrefix (term , q .key + "=" ) {
150
+ target := strings .TrimPrefix (term , q .key + "=" )
151
+ matched = (matched && strings .ToLower (q .extractor (lib )) == target )
152
+ knownQualifier = true
153
+ break
154
+ }
155
+ }
156
+
157
+ if ! knownQualifier {
158
+ matched = (matched && utils .Match (DefaultLibraryMatchExtractor (lib ), []string {term }))
159
+ }
160
+ }
161
+ return matched
162
+ }
163
+ }
164
+
41
165
func searchLibrary (req * rpc.LibrarySearchRequest , lm * librariesmanager.LibrariesManager ) * rpc.LibrarySearchResponse {
42
166
res := []* rpc.SearchedLibrary {}
43
167
query := req .GetSearchArgs ()
44
168
if query == "" {
45
169
query = req .GetQuery ()
46
170
}
47
- queryTerms := utils .SearchTermsFromQueryString (query )
48
171
49
- for _ , lib := range lm .Index .Libraries {
50
- toTest := lib .Name + " " +
51
- lib .Latest .Paragraph + " " +
52
- lib .Latest .Sentence + " " +
53
- lib .Latest .Author + " "
54
- for _ , include := range lib .Latest .ProvidesIncludes {
55
- toTest += include + " "
56
- }
172
+ matcher := MatcherFromQueryString (query )
57
173
58
- if utils .Match (toTest , queryTerms ) {
174
+ for _ , lib := range lm .Index .Libraries {
175
+ if matcher (lib ) {
59
176
res = append (res , indexLibraryToRPCSearchLibrary (lib , req .GetOmitReleasesDetails ()))
60
177
}
61
178
}
0 commit comments