Add MacOS support, replace sysinfo #1
15 changed files with 262 additions and 44 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
7
artwork/apple
Normal file
7
artwork/apple
Normal file
|
@ -0,0 +1,7 @@
|
|||
.:'
|
||||
__ :'__
|
||||
.`` `-' ``.
|
||||
: .`
|
||||
: `._
|
||||
: ;
|
||||
`.__.-.__.`
|
2
go.mod
2
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
|
||||
)
|
||||
|
|
4
go.sum
4
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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
12
modules/cpu_darwin.go
Normal file
12
modules/cpu_darwin.go
Normal 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
21
modules/cpu_linux.go
Normal 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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
19
modules/memory_darwin.go
Normal file
19
modules/memory_darwin.go
Normal 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
29
modules/memory_linux.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
25
modules/os_darwin.go
Normal file
25
modules/os_darwin.go
Normal 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
99
modules/os_linux.go
Normal 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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue