From 363bc4114ccf8e59e7ddbe32361fd8d6eeca93ca Mon Sep 17 00:00:00 2001 From: Eltrac Date: Tue, 11 Nov 2025 17:46:37 +0800 Subject: refactor: separate data processing and printing - `print.go` no longer handles retrieving atime and other data operations. It just receives info and prints. - New `statistics.go` gather formulae and cask struct and spits out a unified data structure for `print.go` to use. Getting atime and reverse dependencies are moved here as well. - `info.go` is now `brewget.go`, since what it does is basically running `brew info` and `brew uses` commands to gather raw data. --- brewget.go | 83 ++++++++++++++++++++++++++++++ info.go | 82 ------------------------------ main.go | 5 +- print.go | 160 ++++++++++++++++++++++++---------------------------------- statistics.go | 70 +++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 178 deletions(-) create mode 100644 brewget.go delete mode 100644 info.go create mode 100644 statistics.go diff --git a/brewget.go b/brewget.go new file mode 100644 index 0000000..202e985 --- /dev/null +++ b/brewget.go @@ -0,0 +1,83 @@ +package main + +import ( + "encoding/json" + "os/exec" + "strings" + "bytes" + "github.com/fatih/color" +) + +type FormulaInfo struct { + Name string `json:"name"` + Desc string `json:"desc"` + Homepage string `json:"homepage"` + License string `json:"license"` + Versions struct { + Stable string `json:"stable"` + } `json:"versions"` + Installed []struct { + Version string `json:"version"` + InstalledAsDependency bool `json:"installed_as_dependency"` + InstalledOnRequest bool `json:"installed_on_request"` + } `json:"installed"` + Outdated bool `json:"outdated"` +} + +type CaskInfo struct { + Token string `json:"token"` + Name []string `json:"name"` + Desc string `json:"desc"` + Homepage string `json:"homepage"` + Installed string `json:"installed"` + AutoUpdates bool `json:"auto_updates"` + Outdated bool `json:"outdated"` + Version string `json:"version"` +} + +// GetInfo fetches package info for both formulae and casks. +// It returns one of the two structs depending on the package type. +func GetBrewInfo(pkg string) (*FormulaInfo, *CaskInfo) { + out, err := exec.Command("brew", "info", "--json=v2", pkg).Output() + if err != nil { + color.Red("❌ Error fetching info. Please check for typo.") + return nil, nil + } + + var data struct { + Formulae []FormulaInfo `json:"formulae"` + Casks []CaskInfo `json:"casks"` + } + + if err := json.Unmarshal(out, &data); err != nil { + color.Red("❌ Error parsing data.") + return nil, nil + } + + if len(data.Formulae) > 0 { + return &data.Formulae[0], nil + } + if len(data.Casks) > 0 { + return nil, &data.Casks[0] + } + + return nil, nil // not found +} + +func GetBrewUses(pkg string) ([]string) { + cmd := exec.Command("brew", "uses", "--installed", pkg) + var out bytes.Buffer + cmd.Stdout = &out + if err := cmd.Run(); err != nil { + color.Red("❌ Error fetching reverse dependencies.") + return []string{} + } + + // Each line is a formula name + lines := strings.Split(strings.TrimSpace(out.String()), "\n") + if len(lines) == 1 && lines[0] == "" { + return []string{} + } + + return lines +} diff --git a/info.go b/info.go deleted file mode 100644 index cfc80f0..0000000 --- a/info.go +++ /dev/null @@ -1,82 +0,0 @@ -package main - -import ( - "encoding/json" - "os/exec" - "strings" - "bytes" - "github.com/fatih/color" -) - -type FormulaInfo struct { - Name string `json:"name"` - Desc string `json:"desc"` - Homepage string `json:"homepage"` - License string `json:"license"` - Versions struct { - Stable string `json:"stable"` - } `json:"versions"` - Installed []struct { - Version string `json:"version"` - InstalledAsDependency bool `json:"installed_as_dependency"` - InstalledOnRequest bool `json:"installed_on_request"` - } `json:"installed"` - Outdated bool `json:"outdated"` -} - -type CaskInfo struct { - Token string `json:"token"` - Name []string `json:"name"` - Desc string `json:"desc"` - Homepage string `json:"homepage"` - Installed string `json:"installed"` - AutoUpdates bool `json:"auto_updates"` - Outdated bool `json:"outdated"` - Version string `json:"version"` -} - -// GetInfo fetches package info for both formulae and casks. -// It returns one of the two structs depending on the package type. -func GetInfo(pkg string) (*FormulaInfo, *CaskInfo) { - out, err := exec.Command("brew", "info", "--json=v2", pkg).Output() - if err != nil { - color.Red("❌ Error fetching info. Please check for typo.") - return nil, nil - } - - var data struct { - Formulae []FormulaInfo `json:"formulae"` - Casks []CaskInfo `json:"casks"` - } - - if err := json.Unmarshal(out, &data); err != nil { - color.Red("❌ Error parsing data.") - return nil, nil - } - - if len(data.Formulae) > 0 { - return &data.Formulae[0], nil - } - if len(data.Casks) > 0 { - return nil, &data.Casks[0] - } - - return nil, nil // not found -} - -func GetReverseDependencies(pkg string) ([]string, error) { - cmd := exec.Command("brew", "uses", "--installed", pkg) - var out bytes.Buffer - cmd.Stdout = &out - if err := cmd.Run(); err != nil { - return nil, err - } - - // Each line is a formula name - lines := strings.Split(strings.TrimSpace(out.String()), "\n") - if len(lines) == 1 && lines[0] == "" { - return []string{}, nil - } - - return lines, nil -} diff --git a/main.go b/main.go index ad8deb7..6ea7ed7 100644 --- a/main.go +++ b/main.go @@ -27,8 +27,9 @@ func main() { // prompt color.White("What the heck is \"%s\" ...?", pkgName) // search and print - formulae, cask := GetInfo(pkgName) - PrintInfo(formulae, cask, pkgName) + formulae, cask := GetBrewInfo(pkgName) + data := UnifyInfo(formulae, cask, pkgName) + PrintInfo(data) return nil }, } diff --git a/print.go b/print.go index e463fe6..45601f4 100644 --- a/print.go +++ b/print.go @@ -4,64 +4,62 @@ import ( "fmt" "github.com/fatih/color" "strings" - "os" - "path/filepath" - "syscall" "time" "github.com/hako/durafmt" ) // === Print Entry Point === // -func PrintInfo(formula *FormulaInfo, cask *CaskInfo, pkgName string) { - if formula == nil && cask == nil { +func PrintInfo(data *statistics) { + if data == nil { fmt.Println("Package not found.") return } - printHeader(formula, cask) - printUserInteractionSummary(formula, cask, pkgName) - printMetadata(formula, cask) + printHeader(data) + printUserInteractionSummary(data) + printMetadata(data) fmt.Println() } // === Main Sections === // -func printHeader(formula *FormulaInfo, cask *CaskInfo) { - printTypeNameAndDesc(formula, cask) +func printHeader(data *statistics) { + printTypeNameAndDesc(data.Type, data.Name, data.Desc) fmt.Println() } -func printUserInteractionSummary( - formula *FormulaInfo, cask *CaskInfo, pkgName string) { - printInstallDesc(formula, cask) - printRecentActivity(formula, cask, pkgName) +func printUserInteractionSummary(data *statistics) { + printInstallDesc( + data.Type == "formula", + data.InstalledAsDependency, + data.InstalledOnRequest, + data.InstalledVersion, + data.ReverseDependencies) + printRecentActivity( + data.InstalledVersion, + data.LastAccessTime) fmt.Println() } -func printMetadata(formula *FormulaInfo, cask *CaskInfo) { - printVersionInfo(formula, cask) - printHomeAndLicense(formula, cask) +func printMetadata(data *statistics) { + printVersionInfo( + data.Type, + data.InstalledVersion, + data.Outdated) + printHomeAndLicense(data.Homepage, data.License) } // === #Section: Header# === // -func printTypeNameAndDesc(formula *FormulaInfo, cask *CaskInfo) { - var icon, typ, name, desc string - - if formula != nil { - icon = "🍺" - typ = "(formula)" - name = formula.Name - desc = formula.Desc - } else if cask != nil { - icon = "☕️" - typ = "(cask)" - name = cask.Token - desc = cask.Desc - } else { - name = "(unknown)" +func printTypeNameAndDesc(typ string, name string, desc string) { + var icon string + switch typ { + case "formula": + icon = "🍺" + case "cask": + icon = "☕️" } fmt.Printf("%s ", icon) @@ -73,53 +71,45 @@ func printTypeNameAndDesc(formula *FormulaInfo, cask *CaskInfo) { // === #Section: User Interaction Summary# === // // answers questions like "how it got here?" -func printInstallDesc(formula *FormulaInfo, cask *CaskInfo) { - if cask != nil || formula == nil { - return // formulae only +func printInstallDesc( + isFormulae bool, + asDependency bool, + onRequest bool, + version string, + reverseDependencies []string, +) { + // formulae only + if !isFormulae { + return } - if len(formula.Installed) == 0 { + if version == "" { return - } else if formula.Installed[0].InstalledOnRequest { + } else if onRequest { color.Blue("► You installed this package by running `brew install`.\n") - } else if formula.Installed[0].InstalledAsDependency { + } else if asDependency { color.Blue("► This package was installed automatically as a dependency.\n") - // list reverse dependencies - dependencies, err := GetReverseDependencies(formula.Name) - if err != nil { - fmt.Printf("Error getting reverse dependencies: %v\n", err) - } else { - fmt.Printf("Used by: %s\n", strings.Join(dependencies, ", ")) - } + fmt.Printf("Used by: %s\n", + strings.Join(reverseDependencies, ", ")) } } // answers questions like "have I used this recently?" -func printRecentActivity(formula *FormulaInfo, cask *CaskInfo, pkgName string) { +func printRecentActivity(installedVersion string, atime time.Time) { // skip if not installed - if (formula != nil && len(formula.Installed) == 0) || - (cask != nil && cask.Installed == "") { + if installedVersion == "" { return } - // get last access time (atime) - binPath := filepath.Join("/opt/homebrew/bin", pkgName) - info, err := os.Stat(binPath) - if err != nil { - return // skip if no binary is found - } - atime := info.Sys().(*syscall.Stat_t).Atimespec - // print usage info var format string var humanReadableDuration string - timestamp := time.Unix(atime.Unix()) - humanReadableDuration = durafmt.ParseShort(time.Since(timestamp)).String() + humanReadableDuration = durafmt.ParseShort(time.Since(atime)).String() format = func() string { - if atime.Nano() == 0 { + if atime.Nanosecond() == 0 { return "► You never used this." - } else if time.Since(timestamp) < 24*7*time.Hour { + } else if time.Since(atime) < 24*7*time.Hour { return "► You used this in %s." } else { return "► You haven't used this for %s." @@ -133,50 +123,32 @@ func printRecentActivity(formula *FormulaInfo, cask *CaskInfo, pkgName string) { // prints badges like [Outdated] [Up to date] ... // used by printMetadata -func printVersionInfo(formula *FormulaInfo, cask *CaskInfo) { - var installedVersion string +func printVersionInfo(typ string, installedVersion string, outdated bool) { var installBadge string var installBadgeColor color.Attribute - if formula != nil { - if len(formula.Installed) != 0 { - installedVersion = formula.Installed[0].Version - if formula.Outdated { - installBadge = "[Outdated]" - installBadgeColor = color.FgYellow - } else { - installBadge = "[Up to date]" - installBadgeColor = color.FgGreen - } - } else { - installBadge = "[Not installed]" - installBadgeColor = color.FgRed - } - } else if cask != nil { - installedVersion = cask.Version - if cask.Installed != "" { - if cask.Outdated { - installBadge = "[Outdated]" - installBadgeColor = color.FgYellow - } else { - installBadge = "[Up to date]" - installBadgeColor = color.FgGreen - } + if installedVersion != "" { + if outdated { + installBadge = "[Outdated]" + installBadgeColor = color.FgYellow } else { - installBadge = "[Not installed]" - installBadgeColor = color.FgRed + installBadge = "[Up to date]" + installBadgeColor = color.FgGreen } + } else { + installBadge = "[Not installed]" + installBadgeColor = color.FgRed } fmt.Printf("📦 %s", installedVersion) color.New(installBadgeColor).Printf(" %s\n", installBadge) } -func printHomeAndLicense(formula *FormulaInfo, cask *CaskInfo) { - if formula != nil { - fmt.Printf("🔗 %s\n", formula.Homepage) - fmt.Printf("📜 %s\n", formula.License) - } else if cask != nil { - fmt.Printf("🔗 %s\n", cask.Homepage) +func printHomeAndLicense(homepage string, license string) { + if homepage != "" { + fmt.Printf("🔗 %s\n", homepage) + } + if license != "" { + fmt.Printf("📜 %s\n", license) } } diff --git a/statistics.go b/statistics.go new file mode 100644 index 0000000..428d19c --- /dev/null +++ b/statistics.go @@ -0,0 +1,70 @@ +package main + +import ( + "os" + "path/filepath" + "syscall" + "time" +) + +type statistics struct { + Name string; + Type string; //formula or cask + Desc string; + Outdated bool; + + InstalledAsDependency bool; + InstalledOnRequest bool; + LastAccessTime time.Time; + + InstalledVersion string; + Homepage string; + License string; + + ReverseDependencies []string; +} + +func UnifyInfo(formula *FormulaInfo, cask *CaskInfo, pkgName string) (*statistics) { + var statObject statistics + + // === formula/cask specific === // + + if formula != nil { + statObject.Name = formula.Name + statObject.Type = "formula" + statObject.Desc = formula.Desc + statObject.Homepage = formula.Homepage + statObject.License = formula.License + statObject.Outdated = formula.Outdated + + // if installed + if len(formula.Installed) != 0 { + statObject.InstalledVersion = formula.Installed[0].Version + statObject.InstalledAsDependency = formula.Installed[0].InstalledAsDependency + statObject.InstalledOnRequest = formula.Installed[0].InstalledOnRequest + } + + // get reverse dependencies + statObject.ReverseDependencies = GetBrewUses(pkgName) + } else if cask != nil { + statObject.Name = cask.Token + statObject.Type = "cask" + statObject.Desc = cask.Desc + statObject.Homepage = cask.Homepage + statObject.InstalledVersion = cask.Installed + statObject.Outdated = cask.Outdated + } + + // === general === // + + // get system atime + binPath := filepath.Join("/opt/homebrew/bin", pkgName) + info, err := os.Stat(binPath) + if err != nil { + return nil // skip if no binary is found + } + atime := info.Sys().(*syscall.Stat_t).Atimespec + statObject.LastAccessTime = time.Unix(atime.Unix()) + + return &statObject +} -- cgit v1.2.3