123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- package query
- import (
- "context"
- "fmt"
- "io"
- "sort"
- "strconv"
- "strings"
- "github.com/Jguer/aur"
- "github.com/Jguer/go-alpm/v2"
- "github.com/adrg/strutil"
- "github.com/adrg/strutil/metrics"
- "github.com/leonelquinteros/gotext"
- "github.com/Jguer/yay/v11/pkg/db"
- "github.com/Jguer/yay/v11/pkg/intrange"
- "github.com/Jguer/yay/v11/pkg/settings/parser"
- "github.com/Jguer/yay/v11/pkg/stringset"
- "github.com/Jguer/yay/v11/pkg/text"
- )
- const sourceAUR = "aur"
- type Builder interface {
- Len() int
- Execute(ctx context.Context, dbExecutor db.Executor, aurClient *aur.Client, pkgS []string)
- Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error
- GetTargets(include, exclude intrange.IntRanges, otherExclude stringset.StringSet) ([]string, error)
- }
- type MixedSourceQueryBuilder struct {
- results []abstractResult
- sortBy string
- searchBy string
- targetMode parser.TargetMode
- queryMap map[string]map[string]interface{}
- bottomUp bool
- singleLineResults bool
- }
- func NewMixedSourceQueryBuilder(
- sortBy string,
- targetMode parser.TargetMode,
- searchBy string,
- bottomUp,
- singleLineResults bool,
- ) *MixedSourceQueryBuilder {
- return &MixedSourceQueryBuilder{
- bottomUp: bottomUp,
- sortBy: sortBy,
- targetMode: targetMode,
- searchBy: searchBy,
- singleLineResults: singleLineResults,
- queryMap: map[string]map[string]interface{}{},
- results: make([]abstractResult, 0, 100),
- }
- }
- type abstractResult struct {
- source string
- name string
- description string
- votes int
- provides []string
- }
- type abstractResults struct {
- results []abstractResult
- search string
- distanceCache map[string]float64
- bottomUp bool
- metric strutil.StringMetric
- }
- 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
- }
- 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 = float64(pkg.votes) / float64(pkg.votes+60)
- }
- sim = sim*0.6 + simDesc*0.2 + popularity*0.2
- 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)
- if a.bottomUp {
- return simA < simB
- }
- return simA > simB
- }
- func (s *MixedSourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, aurClient *aur.Client, pkgS []string) {
- var aurErr error
- pkgS = RemoveInvalidTargets(pkgS, s.targetMode)
- metric := &metrics.JaroWinkler{
- CaseSensitive: false,
- }
- sortableResults := &abstractResults{
- results: []abstractResult{},
- search: strings.Join(pkgS, ""),
- distanceCache: map[string]float64{},
- bottomUp: s.bottomUp,
- metric: metric,
- }
- if s.targetMode.AtLeastAUR() {
- var aurResults aurQuery
- aurResults, aurErr = queryAUR(ctx, aurClient, pkgS, s.searchBy)
- dbName := sourceAUR
- for i := range aurResults {
- if s.queryMap[dbName] == nil {
- s.queryMap[dbName] = map[string]interface{}{}
- }
- s.queryMap[dbName][aurResults[i].Name] = aurResults[i]
- sortableResults.results = append(sortableResults.results, abstractResult{
- source: dbName,
- name: aurResults[i].Name,
- description: aurResults[i].Description,
- provides: aurResults[i].Provides,
- votes: aurResults[i].NumVotes,
- })
- }
- }
- var repoResults []alpm.IPackage
- if s.targetMode.AtLeastRepo() {
- repoResults = dbExecutor.SyncPackages(pkgS...)
- for i := range repoResults {
- dbName := repoResults[i].DB().Name()
- if s.queryMap[dbName] == nil {
- s.queryMap[dbName] = map[string]interface{}{}
- }
- s.queryMap[dbName][repoResults[i].Name()] = repoResults[i]
- rawProvides := repoResults[i].Provides().Slice()
- provides := make([]string, len(rawProvides))
- for j := range rawProvides {
- provides[j] = rawProvides[j].Name
- }
- sortableResults.results = append(sortableResults.results, abstractResult{
- source: repoResults[i].DB().Name(),
- name: repoResults[i].Name(),
- description: repoResults[i].Description(),
- provides: provides,
- votes: -1,
- })
- }
- }
- sort.Sort(sortableResults)
- s.results = sortableResults.results
- if aurErr != nil {
- text.Errorln(ErrAURSearch{inner: aurErr})
- if len(repoResults) != 0 {
- text.Warnln(gotext.Get("Showing repo packages only"))
- }
- }
- }
- func (s *MixedSourceQueryBuilder) Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
- for i := range s.results {
- if verboseSearch == Minimal {
- _, _ = fmt.Fprintln(w, s.results[i].name)
- continue
- }
- var toPrint string
- if verboseSearch == NumberMenu {
- if s.bottomUp {
- toPrint += text.Magenta(strconv.Itoa(len(s.results)-i)) + " "
- } else {
- toPrint += text.Magenta(strconv.Itoa(i+1)) + " "
- }
- }
- pkg := s.queryMap[s.results[i].source][s.results[i].name]
- if s.results[i].source == sourceAUR {
- aurPkg := pkg.(aur.Pkg)
- toPrint += aurPkgSearchString(&aurPkg, dbExecutor, s.singleLineResults)
- } else {
- syncPkg := pkg.(alpm.IPackage)
- toPrint += syncPkgSearchString(syncPkg, dbExecutor, s.singleLineResults)
- }
- fmt.Fprintln(w, toPrint)
- }
- return nil
- }
- func (s *MixedSourceQueryBuilder) Len() int {
- return len(s.results)
- }
- func (s *MixedSourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
- otherExclude stringset.StringSet,
- ) ([]string, error) {
- var (
- isInclude = len(exclude) == 0 && len(otherExclude) == 0
- targets []string
- lenRes = len(s.results)
- )
- for i := 0; i <= s.Len(); i++ {
- target := i - 1
- if s.bottomUp {
- target = lenRes - i
- }
- if (isInclude && include.Get(i)) || (!isInclude && !exclude.Get(i)) {
- targets = append(targets, s.results[target].source+"/"+s.results[target].name)
- }
- }
- return targets, nil
- }
|