repos / dbin

📦 Poor man's package manager.
git clone https://github.com/xplshn/dbin.git

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}