summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--brewget.go101
-rw-r--r--cache.go2
-rw-r--r--main.go103
3 files changed, 195 insertions, 11 deletions
diff --git a/brewget.go b/brewget.go
index 5bc38f2..b5c8c8a 100644
--- a/brewget.go
+++ b/brewget.go
@@ -2,9 +2,11 @@ package main
import (
"encoding/json"
- "github.com/fatih/color"
"os/exec"
"strings"
+ "time"
+
+ "github.com/fatih/color"
)
type FormulaInfo struct {
@@ -34,6 +36,9 @@ type CaskInfo struct {
Version string `json:"version"`
}
+const brewDepCacheTTL = 3*24*time.Hour
+const brewListCacheTTL = 24*time.Hour
+
// 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) {
@@ -76,3 +81,97 @@ func GetBrewUses(pkg string) []string {
return lines
}
+
+func GetBrewList() []string {
+ const cachepath = "brewlist.json"
+ var list []string
+
+ // Try reading from cache
+ list_raw, cache_hit := ReadCache(cachepath, brewListCacheTTL)
+ if cache_hit {
+ err := json.Unmarshal(list_raw, &list)
+ if err != nil {
+ color.Red("❌ Error unmarshaling cache of brew list")
+ return []string{}
+ }
+ } else {
+ out, err := exec.
+ Command("brew", "list", "--installed-on-request").
+ Output()
+ if err != nil {
+ color.Red("❌ Error fetching brew list.")
+ return []string{}
+ }
+
+ list = strings.Split(strings.TrimSpace(string(out)), "\n")
+ }
+
+ // Write cache
+ defer func() {
+ brewlist_json, err := json.Marshal(list)
+ if err != nil {
+ color.Red("❌ Error marshaling brew list data to write cache.")
+ }
+ WriteCache(cachepath, brewlist_json)
+ }()
+
+ return list
+}
+
+func GetBrewDeps() map[string][]string {
+ depmap := map[string][]string{}
+
+ // Try reading from cache
+ const cachepath = "depmap.json"
+ cache_data, cache_hit := ReadCache(cachepath, brewDepCacheTTL)
+
+ if cache_hit {
+ // if cache hit, decode
+ err := json.Unmarshal(cache_data, &depmap)
+ if err != nil {
+ color.Red("❌ Error unmarshaling cache of dependency map.")
+ }
+ } else {
+ // if not, try fetch
+ out, err := exec.
+ Command("brew", "deps", "--installed", "--direct").
+ Output()
+ if err != nil {
+ color.Red("❌ Error fetching brew dependencies.")
+ return map[string][]string{}
+ }
+
+ // parse raw data
+ // a list of items like "package: depA depB depC..."
+ dep_strs := strings.FieldsFunc(string(out), func(r rune) bool {
+ return r == '\n' || r == '\r'
+ })
+
+ for _, dep_str := range dep_strs {
+ parts := strings.Split(dep_str, ":")
+ pkg := parts[0]
+ pkg_deps := parts[1]
+
+ deps := strings.Split(pkg_deps, " ")
+ filtered := []string{}
+ // remove empty elements in slice
+ for _, dep := range deps {
+ if dep != "" {
+ filtered = append(filtered, dep)
+ }
+ }
+ depmap[pkg] = filtered
+ }
+ }
+
+ // Writing cache
+ defer func() {
+ depmap_json, err := json.Marshal(depmap)
+ if err != nil {
+ color.Red("Error marshaling JSON when writing cache:", err)
+ }
+ WriteCache("depmap.json", depmap_json)
+ }()
+
+ return depmap
+}
diff --git a/cache.go b/cache.go
index a81ea66..caab2e0 100644
--- a/cache.go
+++ b/cache.go
@@ -14,7 +14,7 @@ func CachePath(rel string) string {
return path
}
-func ReadCache(relPath string, ttl time.Duration) ([]byte, bool) {
+func ReadCache(relPath string, ttl time.Duration) (data []byte, cacheHit bool) {
path := CachePath(relPath)
info, err := os.Stat(path)
diff --git a/main.go b/main.go
index 1273c77..8796971 100644
--- a/main.go
+++ b/main.go
@@ -3,13 +3,18 @@ package main
import (
"context"
"fmt"
- "github.com/fatih/color"
- "github.com/urfave/cli/v3"
"log"
"os"
+ "slices"
+ "sort"
+
+ "github.com/fatih/color"
+ "github.com/urfave/cli/v3"
)
+// flags
var NoCache, DoClearCache bool
+var ListAscend, ListDescend, ListNoDep bool
func main() {
EnsureBrewAvailable()
@@ -26,13 +31,6 @@ func main() {
Value: false,
Destination: &NoCache,
},
- &cli.BoolFlag{
- Name: "clear-cache",
- Aliases: []string{"cc"},
- Usage: "Clear cache",
- Value: false,
- Destination: &DoClearCache,
- },
},
Action: func(ctx context.Context, cmd *cli.Command) error {
if DoClearCache {
@@ -60,6 +58,93 @@ func main() {
return nil
},
+ Commands: []*cli.Command{
+ {
+ Name: "list",
+ Aliases: []string{"l", "ls"},
+ Usage: "Better brew list.",
+ Flags: []cli.Flag{
+ &cli.BoolFlag{
+ Name: "ascending",
+ Aliases: []string{"a"},
+ Usage: "Sort list ascendingly, based on dependency count.",
+ Value: false,
+ Destination: &ListAscend,
+ },
+ &cli.BoolFlag{
+ Name: "descending",
+ Aliases: []string{"d"},
+ Usage: "Sort list descendingly, based on dependency count.",
+ Value: false,
+ Destination: &ListDescend,
+ },
+ &cli.BoolFlag{
+ Name: "no-dependency",
+ Aliases: []string{"n", "nd"},
+ Usage: "Only show packages installed on request, with no dependency list.",
+ Value: false,
+ Destination: &ListNoDep,
+ },
+ },
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ depmap := GetBrewDeps()
+ list := GetBrewList()
+
+ // filter and get essential items
+ var keys = []string{}
+ for k := range depmap {
+ if !slices.Contains(list, k) {
+ delete(depmap, k)
+ } else {
+ keys = append(keys, k)
+ }
+ }
+
+ // sort based on dependency count
+ if ListAscend {
+ sort.Slice(keys, func(i, j int) bool {
+ return len(depmap[keys[i]]) > len(depmap[keys[j]])
+ })
+ } else if ListDescend {
+ sort.Slice(keys, func(i, j int) bool {
+ return len(depmap[keys[i]]) < len(depmap[keys[j]])
+ })
+ } else {
+ sort.Strings(keys)
+ }
+
+ //
+ for _, pkg := range keys {
+ deps := depmap[pkg]
+ count := len(deps)
+
+ color.New(color.FgBlue).Printf("%s", pkg)
+ if count > 1 {
+ fmt.Printf(" (%d dependencies)\n", count)
+ } else {
+ fmt.Printf(" (%d dependency)\n", count)
+ }
+
+ if ListNoDep { continue }
+ for _, dep := range deps {
+ fmt.Printf(" ► %s\n", dep)
+ }
+ }
+ return nil
+ },
+ },
+ {
+ Name: "clean",
+ Aliases: []string{"c", "cache"},
+ Usage: "Delete all cache files.",
+ Action: func(ctx context.Context, cmd *cli.Command) error {
+ // TODO error handling here
+ ClearCache()
+ fmt.Println("Cache cleared.")
+ return nil
+ },
+ },
+ },
}
// run command and handle error