mirror of
https://github.com/Jguer/yay.git
synced 2025-10-04 00:03:11 -04:00
feat(search): improve exact match for separate source (#2044)
* unify query builder * remove uneeded code * reorganize code
This commit is contained in:
parent
6390d1c2b0
commit
d13bdb0ce1
2
cmd.go
2
cmd.go
@ -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
18
main.go
@ -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
89
pkg/query/metric.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
@ -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,
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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()) +
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
2
query.go
2
query.go
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user