xplshn
·
2025-08-13
search.go
Go
1package main
2
3import (
4 "context"
5 "path/filepath"
6 "strings"
7
8 "github.com/urfave/cli/v3"
9 "github.com/zeebo/errs"
10)
11
12var (
13 errSearchFailed = errs.Class("search failed")
14)
15
16func searchCommand() *cli.Command {
17 return &cli.Command{
18 Name: "search",
19 Usage: "Search for a binary by supplying one or more search terms",
20 Flags: []cli.Flag{
21 &cli.UintFlag{
22 Name: "limit",
23 Aliases: []string{"l"},
24 Usage: "Set the limit of entries to be shown at once on the screen",
25 },
26 &cli.StringFlag{
27 Name: "repo",
28 Aliases: []string{"repos", "r"},
29 Usage: "Filter binaries by repository name, comma-separated",
30 },
31 },
32 Action: func(_ context.Context, c *cli.Command) error {
33 config, err := loadConfig()
34 if err != nil {
35 return errSearchFailed.Wrap(err)
36 }
37
38 if uint(c.Uint("limit")) > 0 {
39 config.Limit = uint(c.Uint("limit"))
40 }
41
42 uRepoIndex, err := fetchRepoIndex(config)
43 if err != nil {
44 return errSearchFailed.Wrap(err)
45 }
46
47 // Apply repository filter if specified
48 if repoNames := c.String("repo"); repoNames != "" {
49 repoSet := make(map[string]struct{})
50 for _, repo := range strings.Split(repoNames, ",") {
51 repoSet[strings.TrimSpace(repo)] = struct{}{}
52 }
53 filterBEntries(&uRepoIndex, func(entry binaryEntry) bool {
54 _, ok := repoSet[entry.Repository.Name]
55 return ok
56 })
57 }
58
59 return fSearch(config, c.Args().Slice(), uRepoIndex)
60 },
61 }
62}
63
64func fSearch(config *config, searchTerms []string, uRepoIndex []binaryEntry) error {
65 var results []binaryEntry
66 for _, bin := range uRepoIndex {
67 name, pkgID, version, description, rank, repo := bin.Name, bin.PkgID, bin.Version, bin.Description, bin.Rank, bin.Repository
68 if name == "" || description == "" {
69 continue
70 }
71 match := true
72 for _, term := range searchTerms {
73 if !strings.Contains(strings.ToLower(name), strings.ToLower(term)) &&
74 !strings.Contains(strings.ToLower(description), strings.ToLower(term)) &&
75 !strings.Contains(strings.ToLower(pkgID), strings.ToLower(term)) {
76 match = false
77 break
78 }
79 }
80 if match {
81 results = append(results, binaryEntry{
82 Name: name,
83 PkgID: pkgID,
84 Version: version,
85 Description: description,
86 Rank: rank,
87 Repository: repo,
88 })
89 }
90 }
91 if len(results) == 0 {
92 return errSearchFailed.New("no matching binaries found for '%s'", strings.Join(searchTerms, " "))
93 } else if uint(len(results)) > config.Limit {
94 return errSearchFailed.New("too many matching binaries (+%d. [Use --limit or -l before your query]) found for '%s'", len(results), strings.Join(searchTerms, " "))
95 }
96 disableTruncation := config.DisableTruncation
97 for _, result := range results {
98 prefix := "[-]"
99 if bEntryOfinstalledBinary(filepath.Join(config.InstallDir, filepath.Base(result.Name))).PkgID == result.PkgID {
100 prefix = "[i]"
101 } else if _, err := isCached(config, result); err == nil {
102 prefix = "[c]"
103 }
104 truncatePrintf(disableTruncation, "%s %s - %s\n",
105 prefix, parseBinaryEntry(result, true), result.Description)
106 }
107 return nil
108}