Skip to content

Commit

Permalink
Support search plugins by name and description
Browse files Browse the repository at this point in the history
  • Loading branch information
astraw99 committed Dec 17, 2022
1 parent f9772a9 commit eb497a5
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
*.so
*.dylib

# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA
.idea/

# Test binary, build with `go test -c`
*.test

Expand Down
68 changes: 57 additions & 11 deletions cmd/krew/cmd/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,29 @@ import (
"sigs.k8s.io/krew/internal/installation"
)

type searchItem struct {
name string
description string
}

type searchCorpus []searchItem

func (s searchCorpus) descriptions() []string {
var res []string
for _, corpus := range s {
res = append(res, corpus.description)
}
return res
}

func (s searchCorpus) names() []string {
var res []string
for _, corpus := range s {
res = append(res, corpus.name)
}
return res
}

// searchCmd represents the search command
var searchCmd = &cobra.Command{
Use: "search",
Expand Down Expand Up @@ -62,11 +85,14 @@ Examples:
}
}

pluginCanonicalNames := make([]string, len(plugins))
searchTarget := make([]searchItem, len(plugins))
pluginCanonicalNameMap := make(map[string]pluginEntry, len(plugins))
for i, p := range plugins {
cn := canonicalName(p.p, p.indexName)
pluginCanonicalNames[i] = cn
searchTarget[i] = searchItem{
name: cn,
description: p.p.Spec.ShortDescription,
}
pluginCanonicalNameMap[cn] = p
}

Expand All @@ -80,15 +106,8 @@ Examples:
installed[cn] = true
}

var searchResults []string
if len(args) > 0 {
matches := fuzzy.Find(strings.Join(args, ""), pluginCanonicalNames)
for _, m := range matches {
searchResults = append(searchResults, m.Str)
}
} else {
searchResults = pluginCanonicalNames
}
keyword := strings.Join(args, "")
searchResults := searchByNameAndDesc(keyword, searchTarget)

// No plugins found
if len(searchResults) == 0 {
Expand Down Expand Up @@ -118,6 +137,33 @@ Examples:
PreRunE: checkIndex,
}

func searchByNameAndDesc(keyword string, targets searchCorpus) []string {
if keyword == "" {
return targets.names()
}

var searchResults = make([]string, 0, len(targets))

// find by names
matches := fuzzy.Find(keyword, targets.names())
searchResultsMap := make(map[int]bool)
for _, m := range matches {
searchResults = append(searchResults, m.Str)
searchResultsMap[m.Index] = true
}

// find by short descriptions
matches = fuzzy.Find(keyword, targets.descriptions())
for _, m := range matches {
// use map to deduplicate and score > 0 to match more accurately
if _, exist := searchResultsMap[m.Index]; !exist && m.Score > 0 {
searchResults = append(searchResults, targets.names()[m.Index])
}
}

return searchResults
}

func limitString(s string, length int) string {
if len(s) > length && length > 3 {
s = s[:length-3] + "..."
Expand Down
79 changes: 79 additions & 0 deletions cmd/krew/cmd/search_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2019 The Kubernetes Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"testing"

"github.com/google/go-cmp/cmp"
)

// Test_searchByNameAndDesc tests fuzzy search
// name matches are shown first, then the description matches
func Test_searchByNameAndDesc(t *testing.T) {
testPlugins := []struct {
keyword string
names []string
descs []string
expected []string
}{
{
keyword: "foo",
names: []string{"foo", "bar", "foobar"}, // names match first
descs: []string{
"This is the description for the first plugin, not contain keyword",
"This is the description to the second plugin, not contain keyword",
"This is the description for the third plugin, not contain keyword",
},
expected: []string{"foo", "foobar"},
},
{
keyword: "bar",
names: []string{"baz", "qux", "fred"}, // names not match
descs: []string{
"This is the description for the first plugin, contain keyword bar", // description match, but score < 0
"This is the description for the second plugin, not contain keyword",
"This is the description for the third plugin, contain ba fuzzy keyword", // fuzzy match, but score < 0
},
expected: []string{},
},
{
keyword: "baz",
names: []string{"baz", "foo", "bar"}, // both name and description match
descs: []string{
"This is the description for the first plugin, contain keyword baz", // both name and description match
"This is the description for the second plugin, not contain keyword",
"This is the description for the third plugin, contain bar keyword",
},
expected: []string{"baz"},
},
}

for _, tp := range testPlugins {
t.Run(tp.keyword, func(t *testing.T) {
searchTarget := make([]searchItem, len(tp.names))
for i, name := range tp.names {
searchTarget[i] = searchItem{
name: name,
description: tp.descs[i],
}
}
result := searchByNameAndDesc(tp.keyword, searchTarget)
if diff := cmp.Diff(tp.expected, result); diff != "" {
t.Fatalf("expected %v does not match got %v", tp.expected, result)
}
})
}
}

0 comments on commit eb497a5

Please sign in to comment.