Add MacOS support, replace sysinfo #1

Merged
reese merged 6 commits from liljes/mewfetch:main into main 2024-12-20 00:29:51 +00:00
15 changed files with 262 additions and 44 deletions

View file

@ -7,7 +7,7 @@ distribution artwork should be:
- line drawings, not filled - line drawings, not filled
- close to 8 lines long and square-ish - 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 ### 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. 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.

View file

@ -6,9 +6,9 @@ import (
"regexp" "regexp"
"strings" "strings"
"git.cyberia.club/reese/mewfetch/modules"
"git.cyberia.club/reese/slog" "git.cyberia.club/reese/slog"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
"github.com/zcalusic/sysinfo"
) )
//go:embed artwork //go:embed artwork
@ -50,10 +50,7 @@ func draw() {
artName = "custom" artName = "custom"
artwork = conf.Custom artwork = conf.Custom
} else if conf.OS == "auto" { } else if conf.OS == "auto" {
var si sysinfo.SysInfo artwork = getArt(modules.GetOSVendor())
si.GetSysInfo()
artName = si.OS.Vendor
artwork = getArt(artName)
} else { } else {
artName = conf.OS artName = conf.OS
artwork = getArt(artName) artwork = getArt(artName)

7
artwork/apple Normal file
View file

@ -0,0 +1,7 @@
.:'
__ :'__
.`` `-' ``.
: .`
: `._
: ;
`.__.-.__.`

2
go.mod
View file

@ -6,7 +6,6 @@ toolchain go1.22.8
require ( require (
github.com/fatih/color v1.18.0 // indirect 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/pretty v0.1.0 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // 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/mackerelio/go-osstat v0.2.5
github.com/mattn/go-runewidth v0.0.16 github.com/mattn/go-runewidth v0.0.16
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/zcalusic/sysinfo v1.1.3
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

4
go.sum
View file

@ -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/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 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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/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 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=

View file

@ -1,23 +1,14 @@
package modules package modules
import ( import (
"os"
"regexp"
"runtime"
"git.cyberia.club/reese/slog" "git.cyberia.club/reese/slog"
) )
func CPU(opts map[string]string) (rune, string) { func CPU(opts map[string]string) (rune, string) {
var model_name string model_name, err := getCPUModelName()
switch runtime.GOOS { if err != nil {
case "linux": slog.Warn(err, "could not get CPU model name")
meminfo, err := os.ReadFile("/proc/cpuinfo") return '', "Unknown"
slog.Warn(err, "module: memory: unable to read /proc/meminfo")
model_name = regexp.MustCompile(`model name\s*:\s*(\w.*)`).FindStringSubmatch(string(meminfo))[1]
} }
// TODO: more OS cases
return '', model_name return '', model_name
} }

12
modules/cpu_darwin.go Normal file
View file

@ -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")
}

21
modules/cpu_linux.go Normal file
View file

@ -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
}

View file

@ -2,25 +2,15 @@ package modules
import ( import (
"fmt" "fmt"
"os"
"regexp"
"runtime"
"strconv"
"git.cyberia.club/reese/slog" "git.cyberia.club/reese/slog"
) )
func Memory(opts map[string]string) (rune, string) { func Memory(opts map[string]string) (rune, string) {
var memory int memory, err := getMemory()
switch runtime.GOOS { if err != nil {
case "linux": slog.Warn(err, "module: memory: unable to get memory")
meminfo, err := os.ReadFile("/proc/meminfo") return '󰘚', "Unknown"
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
} }
// TODO: more OS cases
return '󰘚', fmt.Sprintf("%s", byteFormat(memory)) return '󰘚', fmt.Sprintf("%s", byteFormat(memory))
} }

19
modules/memory_darwin.go Normal file
View file

@ -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
}

29
modules/memory_linux.go Normal file
View file

@ -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
}

View file

@ -3,20 +3,44 @@ package modules
import ( import (
"runtime" "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) { func OS(opts map[string]string) (rune, string) {
var si sysinfo.SysInfo osInfo, err := getOSInfo()
si.GetSysInfo() if err != nil {
slog.Warn(err, "module: os: unable to get OS info")
return '', "Unknown"
}
icon := map[string]rune{ icon := map[string]rune{
"linux": '', "linux": '',
"macos": '', "darwin": '',
"windows": '', "windows": '',
"freebsd": '', "freebsd": '',
"openbsd": '', "openbsd": '',
}[runtime.GOOS] }[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
} }

25
modules/os_darwin.go Normal file
View file

@ -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
}

99
modules/os_linux.go Normal file
View file

@ -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
}

View file

@ -2,6 +2,7 @@ package modules
import ( import (
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
@ -64,3 +65,12 @@ func durationStr(d time.Duration) string {
} }
return out 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")
}