query_builder.go 6.5 KB

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