Skip to content

Commit 0fc730c

Browse files
authored
Support search plugins by name and description (#799)
1 parent 4469bee commit 0fc730c

File tree

3 files changed

+139
-11
lines changed

3 files changed

+139
-11
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
*.so
66
*.dylib
77

8+
# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
9+
.idea/
10+
811
# Test binary, build with `go test -c`
912
*.test
1013

cmd/krew/cmd/search.go

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@ import (
3030
"sigs.k8s.io/krew/internal/installation"
3131
)
3232

33+
type searchItem struct {
34+
name string
35+
description string
36+
}
37+
38+
type searchCorpus []searchItem
39+
40+
func (s searchCorpus) descriptions() []string {
41+
var res = make([]string, len(s))
42+
for _, corpus := range s {
43+
res = append(res, corpus.description)
44+
}
45+
return res
46+
}
47+
48+
func (s searchCorpus) names() []string {
49+
var res = make([]string, len(s))
50+
for _, corpus := range s {
51+
res = append(res, corpus.name)
52+
}
53+
return res
54+
}
55+
3356
// searchCmd represents the search command
3457
var searchCmd = &cobra.Command{
3558
Use: "search",
@@ -62,11 +85,14 @@ Examples:
6285
}
6386
}
6487

65-
pluginCanonicalNames := make([]string, len(plugins))
88+
searchTarget := make([]searchItem, len(plugins))
6689
pluginCanonicalNameMap := make(map[string]pluginEntry, len(plugins))
6790
for i, p := range plugins {
6891
cn := canonicalName(p.p, p.indexName)
69-
pluginCanonicalNames[i] = cn
92+
searchTarget[i] = searchItem{
93+
name: cn,
94+
description: p.p.Spec.ShortDescription,
95+
}
7096
pluginCanonicalNameMap[cn] = p
7197
}
7298

@@ -80,15 +106,8 @@ Examples:
80106
installed[cn] = true
81107
}
82108

83-
var searchResults []string
84-
if len(args) > 0 {
85-
matches := fuzzy.Find(strings.Join(args, ""), pluginCanonicalNames)
86-
for _, m := range matches {
87-
searchResults = append(searchResults, m.Str)
88-
}
89-
} else {
90-
searchResults = pluginCanonicalNames
91-
}
109+
keyword := strings.Join(args, "")
110+
searchResults := searchByNameAndDesc(keyword, searchTarget)
92111

93112
// No plugins found
94113
if len(searchResults) == 0 {
@@ -118,6 +137,33 @@ Examples:
118137
PreRunE: checkIndex,
119138
}
120139

140+
func searchByNameAndDesc(keyword string, targets searchCorpus) []string {
141+
if keyword == "" {
142+
return targets.names()
143+
}
144+
145+
var searchResults = make([]string, 0, len(targets))
146+
147+
// find by names
148+
matches := fuzzy.Find(keyword, targets.names())
149+
searchResultsMap := make(map[int]bool)
150+
for _, m := range matches {
151+
searchResults = append(searchResults, m.Str)
152+
searchResultsMap[m.Index] = true
153+
}
154+
155+
// find by short descriptions
156+
matches = fuzzy.Find(keyword, targets.descriptions())
157+
for _, m := range matches {
158+
// use map to deduplicate and score > 0 to match more accurately
159+
if _, exist := searchResultsMap[m.Index]; !exist && m.Score > 0 {
160+
searchResults = append(searchResults, targets.names()[m.Index])
161+
}
162+
}
163+
164+
return searchResults
165+
}
166+
121167
func limitString(s string, length int) string {
122168
if len(s) > length && length > 3 {
123169
s = s[:length-3] + "..."

cmd/krew/cmd/search_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2019 The Kubernetes Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package cmd
16+
17+
import (
18+
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
21+
)
22+
23+
// Test_searchByNameAndDesc tests fuzzy search
24+
// name matches are shown first, then the description matches
25+
func Test_searchByNameAndDesc(t *testing.T) {
26+
testPlugins := []struct {
27+
keyword string
28+
names []string
29+
descs []string
30+
expected []string
31+
}{
32+
{
33+
keyword: "foo",
34+
names: []string{"foo", "bar", "foobar"}, // names match first
35+
descs: []string{
36+
"This is the description for the first plugin, not contain keyword",
37+
"This is the description to the second plugin, not contain keyword",
38+
"This is the description for the third plugin, not contain keyword",
39+
},
40+
expected: []string{"foo", "foobar"},
41+
},
42+
{
43+
keyword: "bar",
44+
names: []string{"baz", "qux", "fred"}, // names not match
45+
descs: []string{
46+
"This is the description for the first plugin, contain keyword bar", // description match, but score < 0
47+
"This is the description for the second plugin, not contain keyword",
48+
"This is the description for the third plugin, contain ba fuzzy keyword", // fuzzy match, but score < 0
49+
},
50+
expected: []string{},
51+
},
52+
{
53+
keyword: "baz",
54+
names: []string{"baz", "foo", "bar"}, // both name and description match
55+
descs: []string{
56+
"This is the description for the first plugin, contain keyword baz", // both name and description match
57+
"This is the description for the second plugin, not contain keyword",
58+
"This is the description for the third plugin, contain bar keyword",
59+
},
60+
expected: []string{"baz"},
61+
},
62+
}
63+
64+
for _, tp := range testPlugins {
65+
t.Run(tp.keyword, func(t *testing.T) {
66+
searchTarget := make([]searchItem, len(tp.names))
67+
for i, name := range tp.names {
68+
searchTarget[i] = searchItem{
69+
name: name,
70+
description: tp.descs[i],
71+
}
72+
}
73+
result := searchByNameAndDesc(tp.keyword, searchTarget)
74+
if diff := cmp.Diff(tp.expected, result); diff != "" {
75+
t.Fatalf("expected %v does not match got %v", tp.expected, result)
76+
}
77+
})
78+
}
79+
}

0 commit comments

Comments
 (0)