feat(search): improve exact match for separate source (#2044)

* unify query builder

* remove uneeded code

* reorganize code
This commit is contained in:
Jo 2023-04-02 01:23:02 +01:00 committed by GitHub
parent 6390d1c2b0
commit d13bdb0ce1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 163 additions and 630 deletions

2
cmd.go
View File

@ -395,7 +395,7 @@ func displayNumberMenu(ctx context.Context, cfg *settings.Configuration, pkgS []
) error {
queryBuilder.Execute(ctx, dbExecutor, pkgS)
if err := queryBuilder.Results(os.Stdout, dbExecutor, query.NumberMenu); err != nil {
if err := queryBuilder.Results(dbExecutor, query.NumberMenu); err != nil {
return err
}

18
main.go
View File

@ -115,19 +115,11 @@ func main() {
cfg.Runtime = runtime
if cfg.SeparateSources {
cfg.Runtime.QueryBuilder = query.NewSourceQueryBuilder(
cfg.Runtime.AURCache,
cfg.Runtime.Logger.Child("querybuilder"), cfg.SortBy,
cfg.Mode, cfg.SearchBy, cfg.BottomUp,
cfg.SingleLineResults)
} else {
cfg.Runtime.QueryBuilder = query.NewMixedSourceQueryBuilder(
cfg.Runtime.AURCache,
cfg.Runtime.Logger.Child("mixed.querybuilder"), cfg.SortBy,
cfg.Mode, cfg.SearchBy,
cfg.BottomUp, cfg.SingleLineResults)
}
cfg.Runtime.QueryBuilder = query.NewSourceQueryBuilder(
cfg.Runtime.AURCache,
cfg.Runtime.Logger.Child("mixed.querybuilder"), cfg.SortBy,
cfg.Mode, cfg.SearchBy,
cfg.BottomUp, cfg.SingleLineResults, cfg.SeparateSources)
var useColor bool

89
pkg/query/metric.go Normal file
View File

@ -0,0 +1,89 @@
package query
import (
"hash/fnv"
"strings"
"github.com/adrg/strutil"
)
const minVotes = 30
// TODO: Add support for Popularity and LastModified
func (a *abstractResults) aurSortByMetric(pkg *abstractResult) float64 {
return 1 - (minVotes / (minVotes + float64(pkg.votes)))
}
func (a *abstractResults) GetMetric(pkg *abstractResult) float64 {
if v, ok := a.distanceCache[pkg.name]; ok {
return v
}
if strings.EqualFold(pkg.name, a.search) {
return 1.0
}
sim := strutil.Similarity(pkg.name, a.search, a.metric)
for _, prov := range pkg.provides {
// If the package provides search, it's a perfect match
// AUR packages don't populate provides
candidate := strutil.Similarity(prov, a.search, a.metric) * 0.80
if candidate > sim {
sim = candidate
}
}
simDesc := strutil.Similarity(pkg.description, a.search, a.metric)
// slightly overweight sync sources by always giving them max popularity
popularity := 1.0
if pkg.source == sourceAUR {
popularity = a.aurSortByMetric(pkg)
}
sim = sim*0.5 + simDesc*0.2 + popularity*0.3
a.distanceCache[pkg.name] = sim
return sim
}
func (a *abstractResults) separateSourceScore(source string, score float64) float64 {
if !a.separateSources {
return 0
}
if score == 1.0 {
return 50
}
switch source {
case sourceAUR:
return 0
case "core":
return 40
case "extra":
return 30
case "community":
return 20
case "multilib":
return 10
}
if v, ok := a.separateSourceCache[source]; ok {
return v
}
h := fnv.New32a()
h.Write([]byte(source))
sourceScore := float64(int(h.Sum32())%9 + 2)
a.separateSourceCache[source] = sourceScore
return sourceScore
}
func (a *abstractResults) calculateMetric(pkg *abstractResult) float64 {
score := a.GetMetric(pkg)
return a.separateSourceScore(pkg.source, score) + score
}

View File

@ -2,11 +2,10 @@ package query
import (
"context"
"fmt"
"io"
"sort"
"strconv"
"strings"
"unicode"
"github.com/Jguer/aur"
"github.com/Jguer/go-alpm/v2"
@ -26,11 +25,11 @@ const sourceAUR = "aur"
type Builder interface {
Len() int
Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string)
Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error
Results(dbExecutor db.Executor, verboseSearch SearchVerbosity) error
GetTargets(include, exclude intrange.IntRanges, otherExclude stringset.StringSet) ([]string, error)
}
type MixedSourceQueryBuilder struct {
type SourceQueryBuilder struct {
results []abstractResult
sortBy string
searchBy string
@ -38,12 +37,13 @@ type MixedSourceQueryBuilder struct {
queryMap map[string]map[string]interface{}
bottomUp bool
singleLineResults bool
separateSources bool
aurClient aur.QueryClient
logger *text.Logger
}
func NewMixedSourceQueryBuilder(
func NewSourceQueryBuilder(
aurClient aur.QueryClient,
logger *text.Logger,
sortBy string,
@ -51,8 +51,9 @@ func NewMixedSourceQueryBuilder(
searchBy string,
bottomUp,
singleLineResults bool,
) *MixedSourceQueryBuilder {
return &MixedSourceQueryBuilder{
separateSources bool,
) *SourceQueryBuilder {
return &SourceQueryBuilder{
aurClient: aurClient,
logger: logger,
bottomUp: bottomUp,
@ -60,6 +61,7 @@ func NewMixedSourceQueryBuilder(
targetMode: targetMode,
searchBy: searchBy,
singleLineResults: singleLineResults,
separateSources: separateSources,
queryMap: map[string]map[string]interface{}{},
results: make([]abstractResult, 0, 100),
}
@ -74,57 +76,26 @@ type abstractResult struct {
}
type abstractResults struct {
results []abstractResult
search string
distanceCache map[string]float64
bottomUp bool
metric strutil.StringMetric
results []abstractResult
search string
bottomUp bool
metric strutil.StringMetric
separateSources bool
sortBy string
distanceCache map[string]float64
separateSourceCache map[string]float64
}
func (a *abstractResults) Len() int { return len(a.results) }
func (a *abstractResults) Swap(i, j int) { a.results[i], a.results[j] = a.results[j], a.results[i] }
func (a *abstractResults) GetMetric(pkg *abstractResult) float64 {
if v, ok := a.distanceCache[pkg.name]; ok {
return v
}
if strings.EqualFold(pkg.name, a.search) {
return 1.0
}
sim := strutil.Similarity(pkg.name, a.search, a.metric)
for _, prov := range pkg.provides {
// If the package provides search, it's a perfect match
// AUR packages don't populate provides
candidate := strutil.Similarity(prov, a.search, a.metric)
if candidate > sim {
sim = candidate
}
}
simDesc := strutil.Similarity(pkg.description, a.search, a.metric)
// slightly overweight sync sources by always giving them max popularity
popularity := 1.0
if pkg.source == sourceAUR {
popularity = 1 - (30 / (30 + float64(pkg.votes)))
}
sim = sim*0.5 + simDesc*0.2 + popularity*0.3
a.distanceCache[pkg.name] = sim
return sim
}
func (a *abstractResults) Less(i, j int) bool {
pkgA := a.results[i]
pkgB := a.results[j]
simA := a.GetMetric(&pkgA)
simB := a.GetMetric(&pkgB)
simA := a.calculateMetric(&pkgA)
simB := a.calculateMetric(&pkgB)
if a.bottomUp {
return simA < simB
@ -133,7 +104,7 @@ func (a *abstractResults) Less(i, j int) bool {
return simA > simB
}
func (s *MixedSourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) {
func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) {
var aurErr error
pkgS = RemoveInvalidTargets(pkgS, s.targetMode)
@ -143,15 +114,18 @@ func (s *MixedSourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Exe
}
sortableResults := &abstractResults{
results: []abstractResult{},
search: strings.Join(pkgS, ""),
distanceCache: map[string]float64{},
bottomUp: s.bottomUp,
metric: metric,
results: []abstractResult{},
search: strings.Join(pkgS, ""),
bottomUp: s.bottomUp,
metric: metric,
separateSources: s.separateSources,
sortBy: s.sortBy,
distanceCache: map[string]float64{},
separateSourceCache: map[string]float64{},
}
if s.targetMode.AtLeastAUR() {
var aurResults aurQuery
var aurResults []aur.Pkg
aurResults, aurErr = queryAUR(ctx, s.aurClient, pkgS, s.searchBy)
dbName := sourceAUR
@ -160,6 +134,10 @@ func (s *MixedSourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Exe
s.queryMap[dbName] = map[string]interface{}{}
}
if !matchesSearch(&aurResults[i], pkgS) {
continue
}
s.queryMap[dbName][aurResults[i].Name] = aurResults[i]
sortableResults.results = append(sortableResults.results, abstractResult{
@ -213,10 +191,10 @@ func (s *MixedSourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Exe
}
}
func (s *MixedSourceQueryBuilder) Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
func (s *SourceQueryBuilder) Results(dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
for i := range s.results {
if verboseSearch == Minimal {
_, _ = fmt.Fprintln(w, s.results[i].name)
s.logger.Println(s.results[i].name)
continue
}
@ -239,17 +217,17 @@ func (s *MixedSourceQueryBuilder) Results(w io.Writer, dbExecutor db.Executor, v
toPrint += syncPkgSearchString(syncPkg, dbExecutor, s.singleLineResults)
}
fmt.Fprintln(w, toPrint)
s.logger.Println(toPrint)
}
return nil
}
func (s *MixedSourceQueryBuilder) Len() int {
func (s *SourceQueryBuilder) Len() int {
return len(s.results)
}
func (s *MixedSourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
func (s *SourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
otherExclude stringset.StringSet,
) ([]string, error) {
var (
@ -259,7 +237,6 @@ func (s *MixedSourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges
)
for i := 0; i <= s.Len(); i++ {
// FIXME: this is probably broken
target := i - 1
if s.bottomUp {
target = lenRes - i
@ -272,3 +249,20 @@ func (s *MixedSourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges
return targets, nil
}
func matchesSearch(pkg *aur.Pkg, terms []string) bool {
for _, pkgN := range terms {
if strings.IndexFunc(pkgN, unicode.IsSymbol) != -1 {
return true
}
name := strings.ToLower(pkg.Name)
desc := strings.ToLower(pkg.Description)
targ := strings.ToLower(pkgN)
if !(strings.Contains(name, targ) || strings.Contains(desc, targ)) {
return false
}
}
return true
}

View File

@ -38,9 +38,9 @@ func TestMixedSourceQueryBuilder(t *testing.T) {
client, err := rpc.NewClient(rpc.WithHTTPClient(&mockDoer{}))
w := &strings.Builder{}
queryBuilder := NewMixedSourceQueryBuilder(client,
queryBuilder := NewSourceQueryBuilder(client,
text.NewLogger(w, strings.NewReader(""), false, "test"),
"votes", parser.ModeAny, "", tc.bottomUp, false)
"votes", parser.ModeAny, "", tc.bottomUp, false, false)
search := []string{"linux"}
mockStore := &mockDB{}
@ -59,7 +59,7 @@ func TestMixedSourceQueryBuilder(t *testing.T) {
assert.Equal(t, "linux", queryBuilder.results[0].name)
}
queryBuilder.Results(w, mockStore, Detailed)
queryBuilder.Results(mockStore, Detailed)
wString := w.String()
require.GreaterOrEqual(t, len(wString), 1, wString)

View File

@ -2,21 +2,9 @@ package query
import (
"context"
"io"
"sort"
"strings"
"unicode"
"github.com/Jguer/aur"
"github.com/Jguer/go-alpm/v2"
"github.com/hashicorp/go-multierror"
"github.com/leonelquinteros/gotext"
"github.com/Jguer/yay/v12/pkg/db"
"github.com/Jguer/yay/v12/pkg/intrange"
"github.com/Jguer/yay/v12/pkg/settings/parser"
"github.com/Jguer/yay/v12/pkg/stringset"
"github.com/Jguer/yay/v12/pkg/text"
)
type SearchVerbosity int
@ -28,168 +16,6 @@ const (
Minimal
)
type SourceQueryBuilder struct {
repoQuery
aurQuery
sortBy string
searchBy string
targetMode parser.TargetMode
bottomUp bool
singleLineResults bool
aurCache aur.QueryClient
logger *text.Logger
}
func NewSourceQueryBuilder(
aurCache aur.QueryClient,
logger *text.Logger,
sortBy string,
targetMode parser.TargetMode,
searchBy string,
bottomUp,
singleLineResults bool,
) *SourceQueryBuilder {
return &SourceQueryBuilder{
aurCache: aurCache,
logger: logger,
repoQuery: []alpm.IPackage{},
aurQuery: []aur.Pkg{},
bottomUp: bottomUp,
sortBy: sortBy,
targetMode: targetMode,
searchBy: searchBy,
singleLineResults: singleLineResults,
}
}
func (s *SourceQueryBuilder) Execute(ctx context.Context,
dbExecutor db.Executor,
pkgS []string,
) {
var aurErr error
pkgS = RemoveInvalidTargets(pkgS, s.targetMode)
if s.targetMode.AtLeastAUR() {
s.aurQuery, aurErr = queryAUR(ctx, s.aurCache, pkgS, s.searchBy)
s.aurQuery = filterAURResults(pkgS, s.aurQuery)
sort.Sort(aurSortable{aurQuery: s.aurQuery, sortBy: s.sortBy, bottomUp: s.bottomUp})
}
if s.targetMode.AtLeastRepo() {
s.repoQuery = repoQuery(dbExecutor.SyncPackages(pkgS...))
if s.bottomUp {
s.Reverse()
}
}
if aurErr != nil && len(s.repoQuery) != 0 {
s.logger.Errorln(ErrAURSearch{inner: aurErr})
s.logger.Warnln(gotext.Get("Showing repo packages only"))
}
}
func (s *SourceQueryBuilder) Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
if s.aurQuery == nil || s.repoQuery == nil {
return ErrNoQuery{}
}
if s.bottomUp {
if s.targetMode.AtLeastAUR() {
s.aurQuery.printSearch(w, len(s.repoQuery)+1, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
}
if s.targetMode.AtLeastRepo() {
s.repoQuery.printSearch(w, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
}
} else {
if s.targetMode.AtLeastRepo() {
s.repoQuery.printSearch(w, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
}
if s.targetMode.AtLeastAUR() {
s.aurQuery.printSearch(w, len(s.repoQuery)+1, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
}
}
return nil
}
func (s *SourceQueryBuilder) Len() int {
return len(s.repoQuery) + len(s.aurQuery)
}
func (s *SourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
otherExclude stringset.StringSet,
) ([]string, error) {
isInclude := len(exclude) == 0 && len(otherExclude) == 0
var targets []string
for i, pkg := range s.repoQuery {
var target int
if s.bottomUp {
target = len(s.repoQuery) - i
} else {
target = i + 1
}
if (isInclude && include.Get(target)) || (!isInclude && !exclude.Get(target)) {
targets = append(targets, pkg.DB().Name()+"/"+pkg.Name())
}
}
for i := range s.aurQuery {
var target int
if s.bottomUp {
target = len(s.aurQuery) - i + len(s.repoQuery)
} else {
target = i + 1 + len(s.repoQuery)
}
if (isInclude && include.Get(target)) || (!isInclude && !exclude.Get(target)) {
targets = append(targets, "aur/"+s.aurQuery[i].Name)
}
}
return targets, nil
}
// filter AUR results to remove strings that don't contain all of the search terms.
func filterAURResults(pkgS []string, results []aur.Pkg) []aur.Pkg {
aurPkgs := make([]aur.Pkg, 0, len(results))
matchesSearchTerms := func(pkg *aur.Pkg, terms []string) bool {
for _, pkgN := range terms {
if strings.IndexFunc(pkgN, unicode.IsSymbol) != -1 {
return true
}
name := strings.ToLower(pkg.Name)
desc := strings.ToLower(pkg.Description)
targ := strings.ToLower(pkgN)
if !(strings.Contains(name, targ) || strings.Contains(desc, targ)) {
return false
}
}
return true
}
for i := range results {
if matchesSearchTerms(&results[i], pkgS) {
aurPkgs = append(aurPkgs, results[i])
}
}
return aurPkgs
}
// queryAUR searches AUR and narrows based on subarguments.
func queryAUR(ctx context.Context,
aurClient aur.QueryClient,

View File

@ -5,7 +5,6 @@ import (
"context"
"io"
"net/http"
"strings"
"testing"
"github.com/Jguer/aur/rpc"
@ -112,30 +111,24 @@ func TestSourceQueryBuilder(t *testing.T) {
queryBuilder := NewSourceQueryBuilder(client,
text.NewLogger(io.Discard, bytes.NewBufferString(""), false, "test"),
"votes", parser.ModeAny, "", tc.bottomUp, false)
"votes", parser.ModeAny, "", tc.bottomUp, false, true)
search := []string{"linux"}
mockStore := &mockDB{}
queryBuilder.Execute(context.Background(), mockStore, search)
assert.Len(t, queryBuilder.aurQuery, 1)
assert.Len(t, queryBuilder.repoQuery, 2)
assert.Equal(t, 3, queryBuilder.Len())
assert.Equal(t, "linux-ck", queryBuilder.aurQuery[0].Name)
if tc.bottomUp {
assert.Equal(t, "linux-zen", queryBuilder.repoQuery[0].Name())
assert.Equal(t, "linux", queryBuilder.repoQuery[1].Name())
assert.Equal(t, "linux-ck", queryBuilder.results[0].name)
assert.Equal(t, "linux-zen", queryBuilder.results[1].name)
assert.Equal(t, "linux", queryBuilder.results[2].name)
} else {
assert.Equal(t, "linux-zen", queryBuilder.repoQuery[1].Name())
assert.Equal(t, "linux", queryBuilder.repoQuery[0].Name())
assert.Equal(t, "linux-ck", queryBuilder.results[2].name)
assert.Equal(t, "linux-zen", queryBuilder.results[1].name)
assert.Equal(t, "linux", queryBuilder.results[0].name)
}
w := &strings.Builder{}
queryBuilder.Results(w, mockStore, Detailed)
wString := w.String()
require.GreaterOrEqual(t, len(wString), 1)
assert.Equal(t, tc.want, wString)
queryBuilder.Results(mockStore, Detailed)
})
}
}

View File

@ -2,7 +2,6 @@ package query
import (
"fmt"
"io"
"strconv"
"github.com/Jguer/aur"
@ -13,64 +12,6 @@ import (
"github.com/Jguer/yay/v12/pkg/text"
)
type (
aurQuery []aur.Pkg // Query is a collection of Results.
repoQuery []alpm.IPackage // Query holds the results of a repository search.
)
type aurSortable struct {
aurQuery
sortBy string
bottomUp bool
}
func (r repoQuery) Reverse() {
for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
}
func (r repoQuery) Less(i, j int) bool {
return text.LessRunes([]rune(r[i].Name()), []rune(r[j].Name()))
}
func (q aurSortable) Len() int {
return len(q.aurQuery)
}
func (q aurSortable) Less(i, j int) bool {
var result bool
switch q.sortBy {
case "votes":
result = q.aurQuery[i].NumVotes > q.aurQuery[j].NumVotes
case "popularity":
result = q.aurQuery[i].Popularity > q.aurQuery[j].Popularity
case "name":
result = text.LessRunes([]rune(q.aurQuery[i].Name), []rune(q.aurQuery[j].Name))
case "base":
result = text.LessRunes([]rune(q.aurQuery[i].PackageBase), []rune(q.aurQuery[j].PackageBase))
case "submitted":
result = q.aurQuery[i].FirstSubmitted < q.aurQuery[j].FirstSubmitted
case "modified":
result = q.aurQuery[i].LastModified < q.aurQuery[j].LastModified
case "id":
result = q.aurQuery[i].ID < q.aurQuery[j].ID
case "baseid":
result = q.aurQuery[i].PackageBaseID < q.aurQuery[j].PackageBaseID
}
if q.bottomUp {
return !result
}
return result
}
func (q aurSortable) Swap(i, j int) {
q.aurQuery[i], q.aurQuery[j] = q.aurQuery[j], q.aurQuery[i]
}
func getSearchBy(value string) aur.By {
switch value {
case "name":
@ -104,36 +45,6 @@ func getSearchBy(value string) aur.By {
}
}
// PrintSearch handles printing search results in a given format.
func (q aurQuery) printSearch(
w io.Writer,
start int,
dbExecutor db.Executor,
searchMode SearchVerbosity,
bottomUp,
singleLineResults bool,
) {
for i := range q {
if searchMode == Minimal {
_, _ = fmt.Fprintln(w, q[i].Name)
continue
}
var toprint string
if searchMode == NumberMenu {
if bottomUp {
toprint += text.Magenta(strconv.Itoa(len(q)+start-i-1) + " ")
} else {
toprint += text.Magenta(strconv.Itoa(start+i) + " ")
}
}
toprint += aurPkgSearchString(&q[i], dbExecutor, singleLineResults)
_, _ = fmt.Fprintln(w, toprint)
}
}
func aurPkgSearchString(
pkg *aur.Pkg,
dbExecutor db.Executor,
@ -171,29 +82,6 @@ func aurPkgSearchString(
return toPrint
}
// PrintSearch receives a RepoSearch type and outputs pretty text.
func (r repoQuery) printSearch(w io.Writer, dbExecutor db.Executor, searchMode SearchVerbosity, bottomUp, singleLineResults bool) {
for i, res := range r {
if searchMode == Minimal {
_, _ = fmt.Fprintln(w, res.Name())
continue
}
var toprint string
if searchMode == NumberMenu {
if bottomUp {
toprint += text.Magenta(strconv.Itoa(len(r)-i) + " ")
} else {
toprint += text.Magenta(strconv.Itoa(i+1) + " ")
}
}
toprint += syncPkgSearchString(res, dbExecutor, singleLineResults)
_, _ = fmt.Fprintln(w, toprint)
}
}
// PrintSearch receives a RepoSearch type and outputs pretty text.
func syncPkgSearchString(pkg alpm.IPackage, dbExecutor db.Executor, singleLineResults bool) string {
toPrint := text.Bold(text.ColorHash(pkg.DB().Name())) + "/" + text.Bold(pkg.Name()) +

View File

@ -1,249 +0,0 @@
package query
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/Jguer/yay/v12/pkg/db/mock"
"github.com/Jguer/yay/v12/pkg/text"
"github.com/Jguer/aur"
)
var (
pkgA = aur.Pkg{
Name: "package-a",
Version: "1.0.0",
Description: "Package A description",
Maintainer: "Package A Maintainer",
}
pkgARepo = &mock.Package{
PName: pkgA.Name,
PVersion: pkgA.Version,
PDescription: pkgA.Description,
PSize: 1,
PISize: 1,
PDB: mock.NewDB("dba"),
}
pkgB = aur.Pkg{
Name: "package-b",
Version: "1.0.0",
Description: "Package B description",
Maintainer: "Package B Maintainer",
}
pkgBRepo = &mock.Package{
PName: pkgB.Name,
PVersion: pkgB.Version,
PDescription: pkgB.Description,
PSize: 1,
PISize: 1,
PDB: mock.NewDB("dbb"),
}
)
func Test_aurQuery_printSearch(t *testing.T) {
type args struct {
searchMode SearchVerbosity
singleLineResults bool
}
tests := []struct {
name string
q aurQuery
args args
useColor bool
want string
}{
{
name: "AUR,Minimal,NoColor",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: Minimal,
},
want: "package-a\npackage-b\n",
},
{
name: "AUR,DoubleLine,NumberMenu,NoColor",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: NumberMenu,
singleLineResults: false,
},
want: "1 aur/package-a 1.0.0 (+0 0.00) \n Package A description\n2 aur/package-b 1.0.0 (+0 0.00) \n Package B description\n",
},
{
name: "AUR,SingleLine,NumberMenu,NoColor",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: NumberMenu,
singleLineResults: true,
},
want: "1 aur/package-a 1.0.0 (+0 0.00) \tPackage A description\n2 aur/package-b 1.0.0 (+0 0.00) \tPackage B description\n",
},
{
name: "AUR,DoubleLine,Detailed,NoColor",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: Detailed,
singleLineResults: false,
},
want: "aur/package-a 1.0.0 (+0 0.00) \n Package A description\naur/package-b 1.0.0 (+0 0.00) \n Package B description\n",
},
{
name: "AUR,SingleLine,Detailed,NoColor",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: Detailed,
singleLineResults: true,
},
want: "aur/package-a 1.0.0 (+0 0.00) \tPackage A description\naur/package-b 1.0.0 (+0 0.00) \tPackage B description\n",
},
{
name: "AUR,DoubleLine,Detailed,Color",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: Detailed,
singleLineResults: false,
},
useColor: true,
want: "\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mpackage-a\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (+0\x1b[0m \x1b[1m0.00) \x1b[0m\n Package A description\n\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mpackage-b\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (+0\x1b[0m \x1b[1m0.00) \x1b[0m\n Package B description\n",
},
{
name: "AUR,SingleLine,Detailed,Color",
q: aurQuery{pkgA, pkgB},
args: args{
searchMode: Detailed,
singleLineResults: true,
},
useColor: true,
want: "\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mpackage-a\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (+0\x1b[0m \x1b[1m0.00) \x1b[0m\tPackage A description\n\x1b[1m\x1b[34maur\x1b[0m\x1b[0m/\x1b[1mpackage-b\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (+0\x1b[0m \x1b[1m0.00) \x1b[0m\tPackage B description\n",
},
{
name: "AUR,NoPackages",
q: aurQuery{},
args: args{
searchMode: Detailed,
singleLineResults: true,
},
useColor: true,
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &strings.Builder{}
executor := &mock.DBExecutor{LocalPackageFn: func(string) mock.IPackage { return nil }}
text.UseColor = tt.useColor
// Fire
tt.q.printSearch(w, 1, executor, tt.args.searchMode, false, tt.args.singleLineResults)
got := w.String()
assert.Equal(t, tt.want, got)
})
}
}
func Test_repoQuery_printSearch(t *testing.T) {
type args struct {
searchMode SearchVerbosity
singleLineResults bool
}
tests := []struct {
name string
q repoQuery
args args
useColor bool
want string
}{
{
name: "REPO,Minimal,NoColor",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: Minimal,
},
want: "package-a\npackage-b\n",
},
{
name: "REPO,DoubleLine,NumberMenu,NoColor",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: NumberMenu,
singleLineResults: false,
},
want: "1 dba/package-a 1.0.0 (1.0 B 1.0 B) \n Package A description\n2 dbb/package-b 1.0.0 (1.0 B 1.0 B) \n Package B description\n",
},
{
name: "REPO,SingleLine,NumberMenu,NoColor",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: NumberMenu,
singleLineResults: true,
},
want: "1 dba/package-a 1.0.0 (1.0 B 1.0 B) \tPackage A description\n2 dbb/package-b 1.0.0 (1.0 B 1.0 B) \tPackage B description\n",
},
{
name: "REPO,DoubleLine,Detailed,NoColor",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: Detailed,
singleLineResults: false,
},
want: "dba/package-a 1.0.0 (1.0 B 1.0 B) \n Package A description\ndbb/package-b 1.0.0 (1.0 B 1.0 B) \n Package B description\n",
},
{
name: "REPO,SingleLine,Detailed,NoColor",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: Detailed,
singleLineResults: true,
},
want: "dba/package-a 1.0.0 (1.0 B 1.0 B) \tPackage A description\ndbb/package-b 1.0.0 (1.0 B 1.0 B) \tPackage B description\n",
},
{
name: "AUR,DoubleLine,Detailed,Color",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: Detailed,
singleLineResults: false,
},
useColor: true,
want: "\x1b[1m\x1b[35mdba\x1b[0m\x1b[0m/\x1b[1mpackage-a\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n Package A description\n\x1b[1m\x1b[36mdbb\x1b[0m\x1b[0m/\x1b[1mpackage-b\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\n Package B description\n",
},
{
name: "REPO,SingleLine,Detailed,Color",
q: repoQuery{pkgARepo, pkgBRepo},
args: args{
searchMode: Detailed,
singleLineResults: true,
},
useColor: true,
want: "\x1b[1m\x1b[35mdba\x1b[0m\x1b[0m/\x1b[1mpackage-a\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\tPackage A description\n\x1b[1m\x1b[36mdbb\x1b[0m\x1b[0m/\x1b[1mpackage-b\x1b[0m \x1b[36m1.0.0\x1b[0m\x1b[1m (1.0 B 1.0 B) \x1b[0m\tPackage B description\n",
},
{
name: "REPO,NoPackages",
q: repoQuery{},
args: args{
searchMode: Detailed,
singleLineResults: true,
},
useColor: true,
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &strings.Builder{}
executor := &mock.DBExecutor{LocalPackageFn: func(string) mock.IPackage { return nil }}
text.UseColor = tt.useColor
// Fire
tt.q.printSearch(w, executor, tt.args.searchMode, false, tt.args.singleLineResults)
got := w.String()
assert.Equal(t, tt.want, got)
})
}
}

View File

@ -29,7 +29,7 @@ func syncSearch(ctx context.Context, pkgS []string,
searchMode = query.Detailed
}
return queryBuilder.Results(os.Stdout, dbExecutor, searchMode)
return queryBuilder.Results(dbExecutor, searchMode)
}
// SyncInfo serves as a pacman -Si for repo packages and AUR packages.