diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 35f5675..d563323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ distribution artwork should be: - line drawings, not filled - close to 8 lines long and square-ish -they are plain text files stored under `artwork/`. the file name should be the `OS.Vendor` value in the struct returned by [zcalusic/sysinfo](https://github.com/zcalusic/sysinfo). +they are plain text files stored under `artwork/`. the file name should be the `OS.Vendor` value in the struct returned by [modules/os](modules/os). ### platform compliance i am just one person: a linux user without much access to commercial/esoteric OSes or a variety of hardware; so i would like help with testing and tailoring to work on as many computers as possible. of course it's not going to work on *literally every machine*, but reasonably common ones are my general target. diff --git a/artwork.go b/artwork.go index 97b10d3..d74dcc1 100644 --- a/artwork.go +++ b/artwork.go @@ -6,9 +6,9 @@ import ( "regexp" "strings" + "git.cyberia.club/reese/mewfetch/modules" "git.cyberia.club/reese/slog" "github.com/mattn/go-runewidth" - "github.com/zcalusic/sysinfo" ) //go:embed artwork @@ -50,10 +50,7 @@ func draw() { artName = "custom" artwork = conf.Custom } else if conf.OS == "auto" { - var si sysinfo.SysInfo - si.GetSysInfo() - artName = si.OS.Vendor - artwork = getArt(artName) + artwork = getArt(modules.GetOSVendor()) } else { artName = conf.OS artwork = getArt(artName) diff --git a/artwork/apple b/artwork/apple new file mode 100644 index 0000000..9033afb --- /dev/null +++ b/artwork/apple @@ -0,0 +1,7 @@ + .:' + __ :'__ + .`` `-' ``. +: .` +: `._ + : ; + `.__.-.__.` diff --git a/go.mod b/go.mod index dad2b76..9908137 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ toolchain go1.22.8 require ( github.com/fatih/color v1.18.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/kr/pretty v0.1.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -23,6 +22,5 @@ require ( github.com/mackerelio/go-osstat v0.2.5 github.com/mattn/go-runewidth v0.0.16 github.com/spf13/pflag v1.0.5 - github.com/zcalusic/sysinfo v1.1.3 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 15348ef..074fc8a 100644 --- a/go.sum +++ b/go.sum @@ -7,8 +7,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -33,8 +31,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0= -github.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4= 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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= diff --git a/modules/cpu.go b/modules/cpu.go index 2507ae2..e085d15 100644 --- a/modules/cpu.go +++ b/modules/cpu.go @@ -1,23 +1,14 @@ package modules import ( - "os" - "regexp" - "runtime" - "git.cyberia.club/reese/slog" ) func CPU(opts map[string]string) (rune, string) { - var model_name string - switch runtime.GOOS { - case "linux": - meminfo, err := os.ReadFile("/proc/cpuinfo") - slog.Warn(err, "module: memory: unable to read /proc/meminfo") - - model_name = regexp.MustCompile(`model name\s*:\s*(\w.*)`).FindStringSubmatch(string(meminfo))[1] + model_name, err := getCPUModelName() + if err != nil { + slog.Warn(err, "could not get CPU model name") + return '', "Unknown" } - // TODO: more OS cases - return '', model_name } diff --git a/modules/cpu_darwin.go b/modules/cpu_darwin.go new file mode 100644 index 0000000..797bf04 --- /dev/null +++ b/modules/cpu_darwin.go @@ -0,0 +1,12 @@ +//go:build darwin +// +build darwin + +package modules + +import ( + "golang.org/x/sys/unix" +) + +func getCPUModelName() (string, error) { + return unix.Sysctl("machdep.cpu.brand_string") +} diff --git a/modules/cpu_linux.go b/modules/cpu_linux.go new file mode 100644 index 0000000..a94ce5e --- /dev/null +++ b/modules/cpu_linux.go @@ -0,0 +1,21 @@ +//go:build linux +// +build linux + +package modules + +import ( + "os" + "regexp" +) + +func getCPUModelName() (string, error) { + cpuinfo, err := os.ReadFile("/proc/cpuinfo") + if err != nil { + return "", err + } + matches := regexp.MustCompile(`model name\s*:\s*(\w.*)`).FindStringSubmatch(string(cpuinfo)) + if len(matches) < 2 { + return "", nil + } + return matches[1], nil +} diff --git a/modules/memory.go b/modules/memory.go index 1639a8c..6f90529 100644 --- a/modules/memory.go +++ b/modules/memory.go @@ -2,25 +2,15 @@ package modules import ( "fmt" - "os" - "regexp" - "runtime" - "strconv" "git.cyberia.club/reese/slog" ) func Memory(opts map[string]string) (rune, string) { - var memory int - switch runtime.GOOS { - case "linux": - meminfo, err := os.ReadFile("/proc/meminfo") - slog.Warn(err, "module: memory: unable to read /proc/meminfo") - - memory, err = strconv.Atoi(regexp.MustCompile(`MemTotal:\s+(\d+)`).FindStringSubmatch(string(meminfo))[1]) - memory *= 1000 + memory, err := getMemory() + if err != nil { + slog.Warn(err, "module: memory: unable to get memory") + return '󰘚', "Unknown" } - // TODO: more OS cases - return '󰘚', fmt.Sprintf("%s", byteFormat(memory)) } diff --git a/modules/memory_darwin.go b/modules/memory_darwin.go new file mode 100644 index 0000000..838ddee --- /dev/null +++ b/modules/memory_darwin.go @@ -0,0 +1,19 @@ +//go:build darwin +// +build darwin + +package modules + +import ( + "encoding/binary" + + "golang.org/x/sys/unix" +) + +func getMemory() (int, error) { + totalb, err := unix.Sysctl("hw.memsize") + if err != nil { + return 0, err + } + mem := int(binary.LittleEndian.Uint64([]byte(totalb + "\x00"))) + return mem, nil +} diff --git a/modules/memory_linux.go b/modules/memory_linux.go new file mode 100644 index 0000000..4985dc1 --- /dev/null +++ b/modules/memory_linux.go @@ -0,0 +1,29 @@ +//go:build linux +// +build linux + +package modules + +import ( + "errors" + "os" + "regexp" + "strconv" +) + +func getMemory() (int, error) { + meminfo, err := os.ReadFile("/proc/meminfo") + if err != nil { + return 0, err + } + + matches := regexp.MustCompile(`MemTotal:\s+(\d+)`).FindStringSubmatch(string(meminfo)) + if len(matches) < 2 { + return 0, errors.New("could not find MemTotal in /proc/meminfo") + } + + val, err := strconv.Atoi(matches[1]) + if err != nil { + return 0, err + } + return val * 1000, nil +} diff --git a/modules/os.go b/modules/os.go index 20a40ff..53fdc33 100644 --- a/modules/os.go +++ b/modules/os.go @@ -3,20 +3,44 @@ package modules import ( "runtime" - "github.com/zcalusic/sysinfo" + "git.cyberia.club/reese/slog" ) +type OSInfo struct { + Name string `json:"name,omitempty"` + Vendor string `json:"vendor,omitempty"` + Version string `json:"version,omitempty"` + Release string `json:"release,omitempty"` + Architecture string `json:"architecture,omitempty"` +} + func OS(opts map[string]string) (rune, string) { - var si sysinfo.SysInfo - si.GetSysInfo() + osInfo, err := getOSInfo() + if err != nil { + slog.Warn(err, "module: os: unable to get OS info") + return '', "Unknown" + } icon := map[string]rune{ "linux": '', - "macos": '', + "darwin": '', "windows": '', "freebsd": '', "openbsd": '', }[runtime.GOOS] - return icon, si.OS.Name + if icon == 0 { + icon = '' // default to Linux icon if unknown + } + + return icon, osInfo.Name +} + +func GetOSVendor() string { + os, err := getOSInfo() + if err != nil { + slog.Warn(err, "module: os: unable to get OS info") + return "Unknown" + } + return os.Vendor } diff --git a/modules/os_darwin.go b/modules/os_darwin.go new file mode 100644 index 0000000..71bfe67 --- /dev/null +++ b/modules/os_darwin.go @@ -0,0 +1,25 @@ +//go:build darwin +// +build darwin + +package modules + +import ( + "os/exec" + "strings" +) + +func getOSInfo() (*OSInfo, error) { + OS := &OSInfo{} + OS.Name = "macOS" + OS.Vendor = "apple" + + // Get architecture using `uname -m` + cmd := exec.Command("uname", "-m") + stdout, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + OS.Architecture = strings.TrimSpace(string(stdout)) + + return OS, nil +} diff --git a/modules/os_linux.go b/modules/os_linux.go new file mode 100644 index 0000000..ced87ba --- /dev/null +++ b/modules/os_linux.go @@ -0,0 +1,99 @@ +//go:build linux +// +build linux + +package modules + +import ( + "bufio" + "os" + "os/exec" + "regexp" + "strings" +) + +var ( + rePrettyName = regexp.MustCompile(`^PRETTY_NAME=(.*)$`) + reID = regexp.MustCompile(`^ID=(.*)$`) + reVersionID = regexp.MustCompile(`^VERSION_ID=(.*)$`) + reUbuntu = regexp.MustCompile(`[\( ]([\d\.]+)`) + reAlma = regexp.MustCompile(`^AlmaLinux release ([\d\.]+)`) + reCentOS = regexp.MustCompile(`^CentOS( Linux)? release ([\d\.]+)`) + reRocky = regexp.MustCompile(`^Rocky Linux release ([\d\.]+)`) + reRedHat = regexp.MustCompile(`[\( ]([\d\.]+)`) +) + +func getOSInfo() (*OSInfo, error) { + OS := &OSInfo{} + + // Get architecture using `uname -m` + cmd := exec.Command("uname", "-m") + stdout, err := cmd.CombinedOutput() + if err != nil { + return OS, err + } + OS.Architecture = strings.TrimSpace(string(stdout)) + + f, err := os.Open("/etc/os-release") + if err != nil { + return OS, err + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + line := s.Text() + if m := rePrettyName.FindStringSubmatch(line); m != nil { + OS.Name = strings.Trim(m[1], `"`) + } else if m := reID.FindStringSubmatch(line); m != nil { + OS.Vendor = strings.Trim(m[1], `"`) + } else if m := reVersionID.FindStringSubmatch(line); m != nil { + OS.Version = strings.Trim(m[1], `"`) + } + } + + switch OS.Vendor { + case "debian": + OS.Release = slurpFile("/etc/debian_version") + case "ubuntu": + if m := reUbuntu.FindStringSubmatch(OS.Name); m != nil { + OS.Release = m[1] + } + case "almalinux": + if release := slurpFile("/etc/almalinux-release"); release != "" { + if m := reAlma.FindStringSubmatch(release); m != nil { + OS.Release = m[1] + } + } + if OS.Release != "" { + OS.Version = strings.Split(OS.Release, ".")[0] + } + case "centos": + if release := slurpFile("/etc/centos-release"); release != "" { + if m := reCentOS.FindStringSubmatch(release); m != nil { + OS.Release = m[2] + } + } + case "rocky": + if release := slurpFile("/etc/rocky-release"); release != "" { + if m := reRocky.FindStringSubmatch(release); m != nil { + OS.Release = m[1] + } + } + if OS.Release != "" { + OS.Version = strings.Split(OS.Release, ".")[0] + } + case "rhel": + if release := slurpFile("/etc/redhat-release"); release != "" { + if m := reRedHat.FindStringSubmatch(release); m != nil { + OS.Release = m[1] + } + } + if OS.Release == "" { + if m := reRedHat.FindStringSubmatch(OS.Name); m != nil { + OS.Release = m[1] + } + } + } + + return OS, nil +} diff --git a/modules/util.go b/modules/util.go index 0f2a8e7..6829c95 100644 --- a/modules/util.go +++ b/modules/util.go @@ -2,6 +2,7 @@ package modules import ( "fmt" + "os" "os/exec" "strings" "time" @@ -64,3 +65,12 @@ func durationStr(d time.Duration) string { } return out } + +// slurpFile reads a file and trims whitespace. +func slurpFile(path string) string { + data, err := os.ReadFile(path) + if err != nil { + return "" + } + return strings.Trim(string(data), " \r\n\t\u0000\uffff") +}