diff options
| author | Eltrac <hi@guhub.cn> | 2025-11-10 12:37:16 +0800 |
|---|---|---|
| committer | Eltrac <hi@guhub.cn> | 2025-11-10 12:37:16 +0800 |
| commit | 72acc4fad20ccce95ef775f862c13f29719f12ca (patch) | |
| tree | 4565d8b559a65f0a94ed2a22ebab007520f0c8ad | |
initial commit
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | go.mod | 10 | ||||
| -rw-r--r-- | go.sum | 11 | ||||
| -rw-r--r-- | info.go | 79 | ||||
| -rw-r--r-- | main.go | 50 | ||||
| -rw-r--r-- | print.go | 109 |
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 @@ -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 +) @@ -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= @@ -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 +} @@ -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) + } +} |
