source.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package query
  2. import (
  3. "context"
  4. "io"
  5. "sort"
  6. "strings"
  7. "unicode"
  8. "github.com/Jguer/aur"
  9. "github.com/Jguer/go-alpm/v2"
  10. "github.com/hashicorp/go-multierror"
  11. "github.com/leonelquinteros/gotext"
  12. "github.com/Jguer/yay/v12/pkg/db"
  13. "github.com/Jguer/yay/v12/pkg/intrange"
  14. "github.com/Jguer/yay/v12/pkg/settings/parser"
  15. "github.com/Jguer/yay/v12/pkg/stringset"
  16. "github.com/Jguer/yay/v12/pkg/text"
  17. "github.com/Jguer/aur/rpc"
  18. )
  19. type SearchVerbosity int
  20. // Verbosity settings for search.
  21. const (
  22. NumberMenu SearchVerbosity = iota
  23. Detailed
  24. Minimal
  25. )
  26. type SourceQueryBuilder struct {
  27. repoQuery
  28. aurQuery
  29. sortBy string
  30. searchBy string
  31. targetMode parser.TargetMode
  32. useAURCache bool
  33. bottomUp bool
  34. singleLineResults bool
  35. aurClient rpc.ClientInterface
  36. aurCache aur.QueryClient
  37. logger *text.Logger
  38. }
  39. func NewSourceQueryBuilder(
  40. aurClient rpc.ClientInterface,
  41. aurCache aur.QueryClient,
  42. logger *text.Logger,
  43. sortBy string,
  44. targetMode parser.TargetMode,
  45. searchBy string,
  46. bottomUp,
  47. singleLineResults bool,
  48. useAURCache bool,
  49. ) *SourceQueryBuilder {
  50. return &SourceQueryBuilder{
  51. aurClient: aurClient,
  52. aurCache: aurCache,
  53. logger: logger,
  54. repoQuery: []alpm.IPackage{},
  55. aurQuery: []aur.Pkg{},
  56. bottomUp: bottomUp,
  57. sortBy: sortBy,
  58. targetMode: targetMode,
  59. searchBy: searchBy,
  60. singleLineResults: singleLineResults,
  61. useAURCache: useAURCache,
  62. }
  63. }
  64. func (s *SourceQueryBuilder) Execute(ctx context.Context,
  65. dbExecutor db.Executor,
  66. pkgS []string,
  67. ) {
  68. var aurErr error
  69. pkgS = RemoveInvalidTargets(pkgS, s.targetMode)
  70. if s.targetMode.AtLeastAUR() {
  71. s.aurQuery, aurErr = queryAUR(ctx, s.aurClient, s.aurCache, pkgS, s.searchBy, s.useAURCache)
  72. s.aurQuery = filterAURResults(pkgS, s.aurQuery)
  73. sort.Sort(aurSortable{aurQuery: s.aurQuery, sortBy: s.sortBy, bottomUp: s.bottomUp})
  74. }
  75. if s.targetMode.AtLeastRepo() {
  76. s.repoQuery = repoQuery(dbExecutor.SyncPackages(pkgS...))
  77. if s.bottomUp {
  78. s.Reverse()
  79. }
  80. }
  81. if aurErr != nil && len(s.repoQuery) != 0 {
  82. s.logger.Errorln(ErrAURSearch{inner: aurErr})
  83. s.logger.Warnln(gotext.Get("Showing repo packages only"))
  84. }
  85. }
  86. func (s *SourceQueryBuilder) Results(w io.Writer, dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
  87. if s.aurQuery == nil || s.repoQuery == nil {
  88. return ErrNoQuery{}
  89. }
  90. if s.bottomUp {
  91. if s.targetMode.AtLeastAUR() {
  92. s.aurQuery.printSearch(w, len(s.repoQuery)+1, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
  93. }
  94. if s.targetMode.AtLeastRepo() {
  95. s.repoQuery.printSearch(w, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
  96. }
  97. } else {
  98. if s.targetMode.AtLeastRepo() {
  99. s.repoQuery.printSearch(w, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
  100. }
  101. if s.targetMode.AtLeastAUR() {
  102. s.aurQuery.printSearch(w, len(s.repoQuery)+1, dbExecutor, verboseSearch, s.bottomUp, s.singleLineResults)
  103. }
  104. }
  105. return nil
  106. }
  107. func (s *SourceQueryBuilder) Len() int {
  108. return len(s.repoQuery) + len(s.aurQuery)
  109. }
  110. func (s *SourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
  111. otherExclude stringset.StringSet,
  112. ) ([]string, error) {
  113. isInclude := len(exclude) == 0 && len(otherExclude) == 0
  114. var targets []string
  115. for i, pkg := range s.repoQuery {
  116. var target int
  117. if s.bottomUp {
  118. target = len(s.repoQuery) - i
  119. } else {
  120. target = i + 1
  121. }
  122. if (isInclude && include.Get(target)) || (!isInclude && !exclude.Get(target)) {
  123. targets = append(targets, pkg.DB().Name()+"/"+pkg.Name())
  124. }
  125. }
  126. for i := range s.aurQuery {
  127. var target int
  128. if s.bottomUp {
  129. target = len(s.aurQuery) - i + len(s.repoQuery)
  130. } else {
  131. target = i + 1 + len(s.repoQuery)
  132. }
  133. if (isInclude && include.Get(target)) || (!isInclude && !exclude.Get(target)) {
  134. targets = append(targets, "aur/"+s.aurQuery[i].Name)
  135. }
  136. }
  137. return targets, nil
  138. }
  139. // filter AUR results to remove strings that don't contain all of the search terms.
  140. func filterAURResults(pkgS []string, results []aur.Pkg) []aur.Pkg {
  141. aurPkgs := make([]aur.Pkg, 0, len(results))
  142. matchesSearchTerms := func(pkg *aur.Pkg, terms []string) bool {
  143. for _, pkgN := range terms {
  144. if strings.IndexFunc(pkgN, unicode.IsSymbol) != -1 {
  145. return true
  146. }
  147. name := strings.ToLower(pkg.Name)
  148. desc := strings.ToLower(pkg.Description)
  149. targ := strings.ToLower(pkgN)
  150. if !(strings.Contains(name, targ) || strings.Contains(desc, targ)) {
  151. return false
  152. }
  153. }
  154. return true
  155. }
  156. for i := range results {
  157. if matchesSearchTerms(&results[i], pkgS) {
  158. aurPkgs = append(aurPkgs, results[i])
  159. }
  160. }
  161. return aurPkgs
  162. }
  163. // queryAUR searches AUR and narrows based on subarguments.
  164. func queryAUR(ctx context.Context,
  165. rpcClient rpc.ClientInterface, aurClient aur.QueryClient,
  166. pkgS []string, searchBy string, newEngine bool,
  167. ) ([]aur.Pkg, error) {
  168. var (
  169. err error
  170. by = getSearchBy(searchBy)
  171. )
  172. queryClient := aurClient
  173. if !newEngine {
  174. queryClient = rpcClient
  175. }
  176. for _, word := range pkgS {
  177. r, errM := queryClient.Get(ctx, &aur.Query{
  178. Needles: []string{word},
  179. By: by,
  180. Contains: true,
  181. })
  182. if errM == nil {
  183. return r, nil
  184. }
  185. err = multierror.Append(err, errM)
  186. }
  187. return nil, err
  188. }