summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEltrac <hi@guhub.cn>2025-11-10 12:37:16 +0800
committerEltrac <hi@guhub.cn>2025-11-10 12:37:16 +0800
commit72acc4fad20ccce95ef775f862c13f29719f12ca (patch)
tree4565d8b559a65f0a94ed2a22ebab007520f0c8ad
initial commit
-rw-r--r--.gitignore2
-rw-r--r--go.mod10
-rw-r--r--go.sum11
-rw-r--r--info.go79
-rw-r--r--main.go50
-rw-r--r--print.go109
6 files changed, 261 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0aaa0b1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+DS_Store
+wtfis
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..3256eb0
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,10 @@
+module github.com/BigCoke233/wtfis
+
+go 1.25.4
+
+require (
+ github.com/fatih/color v1.18.0 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ golang.org/x/sys v0.25.0 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..33148a4
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,11 @@
+github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/info.go b/info.go
new file mode 100644
index 0000000..17b6b13
--- /dev/null
+++ b/info.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+ "encoding/json"
+ "os/exec"
+ "strings"
+ "bytes"
+)
+
+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, error) {
+ out, err := exec.Command("brew", "info", "--json=v2", pkg).Output()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ var data struct {
+ Formulae []FormulaInfo `json:"formulae"`
+ Casks []CaskInfo `json:"casks"`
+ }
+
+ if err := json.Unmarshal(out, &data); err != nil {
+ return nil, nil, err
+ }
+
+ if len(data.Formulae) > 0 {
+ return &data.Formulae[0], nil, nil
+ }
+ if len(data.Casks) > 0 {
+ return nil, &data.Casks[0], nil
+ }
+
+ return nil, 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
new file mode 100644
index 0000000..9fb16e5
--- /dev/null
+++ b/main.go
@@ -0,0 +1,50 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "github.com/fatih/color"
+)
+
+func main() {
+ // Check if Homebrew is installed
+ _, brewInstallationErr := exec.LookPath("brew")
+ if brewInstallationErr != nil {
+ color.Red("🍺 Homebrew not installed.")
+ fmt.Println("This tool is for looking up formulaermation about packages installed with Homebrew. If you don't have Homebrew, it's no use.")
+ os.Exit(1)
+ }
+
+ args := os.Args
+ // handle arguments
+ var printUsage = func() {
+ color.New(color.FgBlue).Print("Usage: ")
+ fmt.Print("wtfis <package>\n")
+ }
+ if len(args) < 2 {
+ printUsage()
+ os.Exit(1)
+ }
+ if args[1] == "-h" || args[1] == "--help" {
+ printUsage()
+ os.Exit(0)
+ }
+ pkg := args[1]
+
+ // look up formulae
+ out, brewLookupError := exec.Command("brew", "formulae", "--json=v2", pkg).Output()
+ if brewLookupError != nil {
+ fmt.Println("🍺 Homebrew Error:", string(out))
+ os.Exit(1)
+ }
+
+ // parse formulae
+ var formulae, cask, getformulaeErr = GetInfo(pkg)
+ if getformulaeErr != nil {
+ fmt.Println("Error:", getformulaeErr)
+ os.Exit(1)
+ }
+
+ PrintInfo(formulae, cask, pkg)
+}
diff --git a/print.go b/print.go
new file mode 100644
index 0000000..b498894
--- /dev/null
+++ b/print.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+ "fmt"
+ "github.com/fatih/color"
+ "strings"
+)
+
+// PrintInfo prints info for either a formula or a cask.
+func PrintInfo(formula *FormulaInfo, cask *CaskInfo, pkgName string) {
+ if formula == nil && cask == nil {
+ fmt.Println("Package not found.")
+ return
+ }
+
+ printHeader(formula, cask)
+ printDescription(formula, cask)
+ printVersionInfo(formula, cask)
+ printMetadata(formula, cask)
+
+ fmt.Println()
+}
+
+// ------------------- Helper functions -------------------
+
+// printHeader prints the icon and package name
+func printHeader(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)"
+ }
+
+ fmt.Printf("%s ", icon)
+ color.New(color.FgYellow).Printf("%s ", typ)
+ color.New(color.Bold).Printf("%s", name)
+ fmt.Printf(" - %s\n\n", desc)
+}
+
+// printDescription prints installation description for formulae
+func printDescription(formula *FormulaInfo, cask *CaskInfo) {
+ if formula != nil {
+ if len(formula.Installed) == 0 {
+ color.Blue("This package is not installed.\n")
+ } else if formula.Installed[0].InstalledOnRequest {
+ color.Blue("You installed this package by running `brew install`.\n")
+ } else if formula.Installed[0].InstalledAsDependency {
+ 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, ", "))
+ }
+ }
+ } else if cask != nil {
+ if cask.Installed == "" {
+ color.Blue("This cask is not installed.\n")
+ } else {
+ color.Blue("This cask is installed.\n")
+ }
+ }
+
+ fmt.Println()
+}
+
+// printVersionInfo prints the latest version and status
+func printVersionInfo(formula *FormulaInfo, cask *CaskInfo) {
+ if formula != nil {
+ fmt.Printf("📦 Latest: %s", formula.Versions.Stable)
+ if len(formula.Installed) == 0 {
+ color.New(color.FgRed).Print(" [Not Installed]\n")
+ } else if formula.Outdated {
+ color.New(color.FgYellow).Print(" [Outdated]\n")
+ } else {
+ color.New(color.FgGreen).Print(" [Up to date]\n")
+ }
+ } else if cask != nil {
+ fmt.Printf("📦 Installed version: %s", cask.Installed)
+ if cask.Installed == "" {
+ color.New(color.FgRed).Print(" [Not Installed]\n")
+ } else if cask.Outdated {
+ color.New(color.FgYellow).Print(" [Outdated]\n")
+ } else {
+ color.New(color.FgGreen).Print(" [Up to date]\n")
+ }
+ }
+}
+
+// printMetadata prints homepage, license, and other fields
+func printMetadata(formula *FormulaInfo, cask *CaskInfo) {
+ if formula != nil {
+ fmt.Printf("🔗 Homepage: %s\n", formula.Homepage)
+ fmt.Printf("📜 License: %s\n", formula.License)
+ } else if cask != nil {
+ fmt.Printf("🔗 Homepage: %s\n", cask.Homepage)
+ }
+}