xplshn
·
2025-08-13
info.go
Go
1package main
2
3import (
4 "context"
5 "fmt"
6 "path/filepath"
7 "strings"
8
9 "github.com/fxamacker/cbor/v2"
10 "github.com/goccy/go-json"
11 "github.com/goccy/go-yaml"
12 "github.com/urfave/cli/v3"
13 "github.com/zeebo/errs"
14)
15
16var (
17 errBinaryInfoNotFound = errs.Class("binary info not found")
18)
19
20func infoCommand() *cli.Command {
21 return &cli.Command{
22 Name: "info",
23 Usage: "Show information about a specific binary OR display installed binaries",
24 Flags: []cli.Flag{
25 &cli.BoolFlag{
26 Name: "json",
27 Usage: "Print output as JSON",
28 },
29 &cli.BoolFlag{
30 Name: "cbor",
31 Usage: "Print output as CBOR",
32 },
33 &cli.BoolFlag{
34 Name: "yaml",
35 Usage: "Print output as YAML",
36 },
37 },
38 Action: func(_ context.Context, c *cli.Command) error {
39 config, err := loadConfig()
40 if err != nil {
41 return err
42 }
43 var bEntry binaryEntry
44 if c.Args().First() != "" {
45 uRepoIndex, err := fetchRepoIndex(config)
46 if err != nil {
47 return err
48 }
49 bEntry = stringToBinaryEntry(c.Args().First())
50 binaryInfo, err := getBinaryInfo(config, bEntry, uRepoIndex)
51 if err != nil {
52 return errBinaryInfoNotFound.Wrap(err)
53 }
54
55 if c.Bool("json") {
56 jsonData, err := json.MarshalIndent(binaryInfo, "", " ")
57 if err != nil {
58 return err
59 }
60 fmt.Println(string(jsonData))
61 return nil
62 }
63
64 if c.Bool("cbor") {
65 cborData, err := cbor.Marshal(binaryInfo)
66 if err != nil {
67 return err
68 }
69 fmt.Println(string(cborData))
70 return nil
71 }
72
73 if c.Bool("yaml") {
74 yamlData, err := yaml.Marshal(binaryInfo)
75 if err != nil {
76 return err
77 }
78 fmt.Println(string(yamlData))
79 return nil
80 }
81
82 printBEntry(binaryInfo)
83
84 } else {
85 binaryEntries, err := validateProgramsFrom(config, nil, nil)
86 if err != nil {
87 return err
88 }
89 for _, program := range binaryEntries {
90 fmt.Println(parseBinaryEntry(program, true))
91 }
92 }
93 return nil
94 },
95 }
96}
97
98func findBinaryInfo(bEntry binaryEntry, uRepoIndex []binaryEntry) (binaryEntry, bool) {
99 matchingBins := findMatchingBins(bEntry, uRepoIndex)
100
101 if len(matchingBins) == 0 {
102 return binaryEntry{}, false
103 }
104
105 return matchingBins[0], true
106}
107
108func getBinaryInfo(config *config, bEntry binaryEntry, uRepoIndex []binaryEntry) (*binaryEntry, error) {
109 if instBEntry := bEntryOfinstalledBinary(filepath.Join(config.InstallDir, bEntry.Name)); bEntry.PkgID == "" && instBEntry.PkgID != "" {
110 bEntry = instBEntry
111 }
112
113 binInfo, found := findBinaryInfo(bEntry, uRepoIndex)
114 if found {
115 return &binInfo, nil
116 }
117
118 return nil, errBinaryInfoNotFound.New("info for the requested binary ('%s') not found in any of the repository index files", parseBinaryEntry(bEntry, false))
119}
120
121func printBEntry(bEntry *binaryEntry) {
122 fields := []struct {
123 label string
124 value any
125 }{
126 // Most important to the user
127 {"Name", bEntry.Name + "#" + bEntry.PkgID},
128 {"Pkg ID", bEntry.PkgID},
129
130 {"Pretty Name", bEntry.PrettyName},
131 {"Description", bEntry.Description},
132
133 {"Version", bEntry.Version},
134 {"Size", bEntry.Size},
135
136 {"Categories", bEntry.Categories},
137
138 {"Download URL", bEntry.DownloadURL},
139 {"WebURLs", bEntry.WebURLs},
140 {"SrcURLs", bEntry.SrcURLs},
141
142 {"B3SUM", bEntry.Bsum},
143 {"SHA256", bEntry.Shasum},
144 {"Build Date", bEntry.BuildDate},
145 {"Build Script", bEntry.BuildScript},
146 {"Build Log", bEntry.BuildLog},
147
148 // ------------------------------------
149
150 // Clutter:
151 // Useless in the context of `dbin`
152 // These are shown only in the complete
153 // repository index (non-lite version)
154 {"Screenshots", bEntry.Screenshots},
155 {"Icon URL", bEntry.Icon},
156 {"Web Manifest", bEntry.WebManifest},
157 {"Extra Bins", bEntry.ExtraBins},
158
159 // Clutter, but useful:
160 {"Snapshots", bEntry.Snapshots},
161
162 // SBUILD meta
163 {"Maintainers", bEntry.Maintainers},
164 {"Notes", bEntry.Notes},
165 {"License", bEntry.License},
166
167 {"Rank", bEntry.Rank},
168 }
169 for _, field := range fields {
170 switch v := field.value.(type) {
171 case []string:
172 for n, str := range v {
173 prefixLength := len(field.label)
174 prefix := blueBgWhiteFg + field.label + resetColor
175 if n > 0 {
176 prefix = strings.Repeat(" ", prefixLength)
177 }
178 fmt.Printf("%s: %s\n", prefix, str)
179 }
180 case []snapshot:
181 for n, snap := range v {
182 prefix := blueBgWhiteFg + field.label + resetColor
183 if n > 0 {
184 prefix = " "
185 }
186 if snap.Commit != "" {
187 fmt.Printf("%s: %s %s\n", prefix, snap.Commit, ternary(snap.Version != "", "["+cyanColor+snap.Version+resetColor+"]", ""))
188 } else {
189 fmt.Printf("%s: %s\n", prefix, "["+cyanColor+snap.Version+resetColor+"]")
190 }
191 }
192 case uint16:
193 if v != 0 {
194 switch v {
195 case 1:
196 fmt.Printf("%s\x1b[0m: 🥇(%v)\n", blueBgWhiteFg+field.label+resetColor, v)
197 case 2:
198 fmt.Printf("%s\x1b[0m: 🥈(%v)\n", blueBgWhiteFg+field.label+resetColor, v)
199 case 3:
200 fmt.Printf("%s\x1b[0m: 🥉(%v)\n", blueBgWhiteFg+field.label+resetColor, v)
201 default:
202 fmt.Printf("%s\x1b[0m: %v\n", blueBgWhiteFg+field.label+resetColor, v)
203 }
204 }
205 default:
206 if v != "" && v != 0 {
207 fmt.Printf("%s\x1b[0m: %v\n", blueBgWhiteFg+field.label+resetColor, v)
208 }
209 }
210 }
211}