query_builder.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. package query
  2. import (
  3. "context"
  4. "sort"
  5. "strconv"
  6. "strings"
  7. "unicode"
  8. "github.com/Jguer/aur"
  9. "github.com/Jguer/go-alpm/v2"
  10. "github.com/adrg/strutil"
  11. "github.com/adrg/strutil/metrics"
  12. mapset "github.com/deckarep/golang-set/v2"
  13. "github.com/leonelquinteros/gotext"
  14. "github.com/Jguer/yay/v12/pkg/db"
  15. "github.com/Jguer/yay/v12/pkg/intrange"
  16. "github.com/Jguer/yay/v12/pkg/settings/parser"
  17. "github.com/Jguer/yay/v12/pkg/text"
  18. )
  19. const sourceAUR = "aur"
  20. type SearchVerbosity int
  21. // Verbosity settings for search.
  22. const (
  23. NumberMenu SearchVerbosity = iota
  24. Detailed
  25. Minimal
  26. )
  27. type Builder interface {
  28. Len() int
  29. Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string)
  30. Results(dbExecutor db.Executor, verboseSearch SearchVerbosity) error
  31. GetTargets(include, exclude intrange.IntRanges, otherExclude mapset.Set[string]) ([]string, error)
  32. }
  33. type SourceQueryBuilder struct {
  34. results []abstractResult
  35. sortBy string
  36. searchBy string
  37. targetMode parser.TargetMode
  38. queryMap map[string]map[string]interface{}
  39. bottomUp bool
  40. singleLineResults bool
  41. separateSources bool
  42. aurClient aur.QueryClient
  43. logger *text.Logger
  44. }
  45. func NewSourceQueryBuilder(
  46. aurClient aur.QueryClient,
  47. logger *text.Logger,
  48. sortBy string,
  49. targetMode parser.TargetMode,
  50. searchBy string,
  51. bottomUp,
  52. singleLineResults bool,
  53. separateSources bool,
  54. ) *SourceQueryBuilder {
  55. return &SourceQueryBuilder{
  56. aurClient: aurClient,
  57. logger: logger,
  58. bottomUp: bottomUp,
  59. sortBy: sortBy,
  60. targetMode: targetMode,
  61. searchBy: searchBy,
  62. singleLineResults: singleLineResults,
  63. separateSources: separateSources,
  64. queryMap: map[string]map[string]interface{}{},
  65. results: make([]abstractResult, 0, 100),
  66. }
  67. }
  68. type abstractResult struct {
  69. source string
  70. name string
  71. description string
  72. votes int
  73. provides []string
  74. }
  75. type abstractResults struct {
  76. results []abstractResult
  77. search string
  78. bottomUp bool
  79. metric strutil.StringMetric
  80. separateSources bool
  81. sortBy string
  82. distanceCache map[string]float64
  83. separateSourceCache map[string]float64
  84. }
  85. func (a *abstractResults) Len() int { return len(a.results) }
  86. func (a *abstractResults) Swap(i, j int) { a.results[i], a.results[j] = a.results[j], a.results[i] }
  87. func (a *abstractResults) Less(i, j int) bool {
  88. pkgA := a.results[i]
  89. pkgB := a.results[j]
  90. var cmpResult bool
  91. switch a.sortBy {
  92. case "name":
  93. cmpResult = !text.LessRunes([]rune(pkgA.name), []rune(pkgB.name))
  94. if a.separateSources {
  95. cmpSources := strings.Compare(pkgA.source, pkgB.source)
  96. if cmpSources != 0 {
  97. cmpResult = cmpSources > 0
  98. }
  99. }
  100. default:
  101. simA := a.calculateMetric(&pkgA)
  102. simB := a.calculateMetric(&pkgB)
  103. cmpResult = simA > simB
  104. }
  105. if a.bottomUp {
  106. cmpResult = !cmpResult
  107. }
  108. return cmpResult
  109. }
  110. func (s *SourceQueryBuilder) Execute(ctx context.Context, dbExecutor db.Executor, pkgS []string) {
  111. var aurErr error
  112. pkgS = RemoveInvalidTargets(s.logger, pkgS, s.targetMode)
  113. metric := &metrics.Hamming{
  114. CaseSensitive: false,
  115. }
  116. sortableResults := &abstractResults{
  117. results: []abstractResult{},
  118. search: strings.Join(pkgS, ""),
  119. bottomUp: s.bottomUp,
  120. metric: metric,
  121. separateSources: s.separateSources,
  122. sortBy: s.sortBy,
  123. distanceCache: map[string]float64{},
  124. separateSourceCache: map[string]float64{},
  125. }
  126. if s.targetMode.AtLeastAUR() {
  127. var aurResults []aur.Pkg
  128. aurResults, aurErr = queryAUR(ctx, s.aurClient, pkgS, s.searchBy)
  129. dbName := sourceAUR
  130. for i := range aurResults {
  131. if s.queryMap[dbName] == nil {
  132. s.queryMap[dbName] = map[string]interface{}{}
  133. }
  134. by := getSearchBy(s.searchBy)
  135. if (by == aur.NameDesc || by == aur.None || by == aur.Name) &&
  136. !matchesSearch(&aurResults[i], pkgS) {
  137. continue
  138. }
  139. s.queryMap[dbName][aurResults[i].Name] = aurResults[i]
  140. sortableResults.results = append(sortableResults.results, abstractResult{
  141. source: dbName,
  142. name: aurResults[i].Name,
  143. description: aurResults[i].Description,
  144. provides: aurResults[i].Provides,
  145. votes: aurResults[i].NumVotes,
  146. })
  147. }
  148. }
  149. var repoResults []alpm.IPackage
  150. if s.targetMode.AtLeastRepo() {
  151. repoResults = dbExecutor.SyncPackages(pkgS...)
  152. for i := range repoResults {
  153. dbName := repoResults[i].DB().Name()
  154. if s.queryMap[dbName] == nil {
  155. s.queryMap[dbName] = map[string]interface{}{}
  156. }
  157. s.queryMap[dbName][repoResults[i].Name()] = repoResults[i]
  158. rawProvides := repoResults[i].Provides().Slice()
  159. provides := make([]string, len(rawProvides))
  160. for j := range rawProvides {
  161. provides[j] = rawProvides[j].Name
  162. }
  163. sortableResults.results = append(sortableResults.results, abstractResult{
  164. source: repoResults[i].DB().Name(),
  165. name: repoResults[i].Name(),
  166. description: repoResults[i].Description(),
  167. provides: provides,
  168. votes: -1,
  169. })
  170. }
  171. }
  172. sort.Sort(sortableResults)
  173. s.results = sortableResults.results
  174. if aurErr != nil {
  175. s.logger.Errorln(ErrAURSearch{inner: aurErr})
  176. if len(repoResults) != 0 {
  177. s.logger.Warnln(gotext.Get("Showing repo packages only"))
  178. }
  179. }
  180. }
  181. func (s *SourceQueryBuilder) Results(dbExecutor db.Executor, verboseSearch SearchVerbosity) error {
  182. for i := range s.results {
  183. if verboseSearch == Minimal {
  184. s.logger.Println(s.results[i].name)
  185. continue
  186. }
  187. var toPrint string
  188. if verboseSearch == NumberMenu {
  189. if s.bottomUp {
  190. toPrint += text.Magenta(strconv.Itoa(len(s.results)-i)) + " "
  191. } else {
  192. toPrint += text.Magenta(strconv.Itoa(i+1)) + " "
  193. }
  194. }
  195. pkg := s.queryMap[s.results[i].source][s.results[i].name]
  196. switch pPkg := pkg.(type) {
  197. case aur.Pkg:
  198. toPrint += aurPkgSearchString(&pPkg, dbExecutor, s.singleLineResults)
  199. case alpm.IPackage:
  200. toPrint += syncPkgSearchString(pPkg, dbExecutor, s.singleLineResults)
  201. }
  202. s.logger.Println(toPrint)
  203. }
  204. return nil
  205. }
  206. func (s *SourceQueryBuilder) Len() int {
  207. return len(s.results)
  208. }
  209. func (s *SourceQueryBuilder) GetTargets(include, exclude intrange.IntRanges,
  210. otherExclude mapset.Set[string],
  211. ) ([]string, error) {
  212. var (
  213. isInclude = len(exclude) == 0 && otherExclude.Cardinality() == 0
  214. targets []string
  215. lenRes = len(s.results)
  216. )
  217. for i := 1; i <= s.Len(); i++ {
  218. target := i - 1
  219. if s.bottomUp {
  220. target = lenRes - i
  221. }
  222. if (isInclude && include.Get(i)) || (!isInclude && !exclude.Get(i)) {
  223. targets = append(targets, s.results[target].source+"/"+s.results[target].name)
  224. }
  225. }
  226. return targets, nil
  227. }
  228. func matchesSearch(pkg *aur.Pkg, terms []string) bool {
  229. if len(terms) <= 1 {
  230. return true
  231. }
  232. for _, pkgN := range terms {
  233. if strings.IndexFunc(pkgN, unicode.IsSymbol) != -1 {
  234. return true
  235. }
  236. name := strings.ToLower(pkg.Name)
  237. desc := strings.ToLower(pkg.Description)
  238. targ := strings.ToLower(pkgN)
  239. if !(strings.Contains(name, targ) || strings.Contains(desc, targ)) {
  240. return false
  241. }
  242. }
  243. return true
  244. }