xmtop/config.go
2021-07-20 09:31:42 -05:00

290 lines
8.7 KiB
Go

package gotop
import (
"bufio"
"bytes"
"embed"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/shibukawa/configdir"
"github.com/xxxserxxx/gotop/v4/colorschemes"
"github.com/xxxserxxx/gotop/v4/widgets"
"github.com/xxxserxxx/lingo/v2"
)
// FIXME github action uses old(er) Go version that doesn't have embed
//go:embed "dicts/*.toml"
var Dicts embed.FS
// CONFFILE is the name of the default config file
const CONFFILE = "gotop.conf"
type Config struct {
ConfigDir configdir.ConfigDir
GraphHorizontalScale int
HelpVisible bool
Colorscheme colorschemes.Colorscheme
UpdateInterval time.Duration
AverageLoad bool
PercpuLoad bool
Statusbar bool
TempScale widgets.TempScale
NetInterface string
Layout string
MaxLogSize int64
ExportPort string
Mbps bool
Temps []string
Test bool
ExtensionVars map[string]string
ConfigFile string
Tr lingo.Translations
Nvidia bool
NvidiaRefresh time.Duration
}
// FIXME parsing can't handle blank lines
func NewConfig() Config {
cd := configdir.New("", "gotop")
cd.LocalPath, _ = filepath.Abs(".")
conf := Config{
ConfigDir: cd,
GraphHorizontalScale: 7,
HelpVisible: false,
UpdateInterval: time.Second,
AverageLoad: false,
PercpuLoad: true,
TempScale: widgets.Celsius,
Statusbar: false,
NetInterface: widgets.NetInterfaceAll,
MaxLogSize: 5000000,
Layout: "default",
ExtensionVars: make(map[string]string),
}
conf.Colorscheme, _ = colorschemes.FromName(conf.ConfigDir, "default")
folder := conf.ConfigDir.QueryFolderContainsFile(CONFFILE)
if folder != nil {
conf.ConfigFile = filepath.Join(folder.Path, CONFFILE)
}
return conf
}
func (conf *Config) Load() error {
var in []byte
if conf.ConfigFile == "" {
return nil
}
var err error
if _, err = os.Stat(conf.ConfigFile); os.IsNotExist(err) {
// Check for the file in the usual suspects
folder := conf.ConfigDir.QueryFolderContainsFile(conf.ConfigFile)
if folder == nil {
return nil
}
conf.ConfigFile = filepath.Join(folder.Path, conf.ConfigFile)
}
if in, err = ioutil.ReadFile(conf.ConfigFile); err != nil {
return err
}
return load(bytes.NewReader(in), conf)
}
func load(in io.Reader, conf *Config) error {
r := bufio.NewScanner(in)
var lineNo int
for r.Scan() {
l := strings.TrimSpace(r.Text())
if l[0] == '#' {
continue
}
kv := strings.Split(l, "=")
if len(kv) != 2 {
return fmt.Errorf(conf.Tr.Value("config.err.configsyntax", l))
}
key := strings.ToLower(kv[0])
ln := strconv.Itoa(lineNo)
switch key {
default:
conf.ExtensionVars[key] = kv[1]
case "configdir", "logdir", "logfile":
log.Printf(conf.Tr.Value("config.err.deprecation", ln, key, kv[1]))
case graphhorizontalscale:
iv, err := strconv.Atoi(kv[1])
if err != nil {
return err
}
conf.GraphHorizontalScale = iv
case helpvisible:
bv, err := strconv.ParseBool(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.HelpVisible = bv
case colorscheme:
cs, err := colorschemes.FromName(conf.ConfigDir, kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.Colorscheme = cs
case updateinterval:
iv, err := strconv.Atoi(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.UpdateInterval = time.Duration(iv)
case averagecpu:
bv, err := strconv.ParseBool(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.AverageLoad = bv
case percpuload:
bv, err := strconv.ParseBool(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.PercpuLoad = bv
case tempscale:
switch kv[1] {
case "C":
conf.TempScale = 'C'
case "F":
conf.TempScale = 'F'
default:
conf.TempScale = 'C'
return fmt.Errorf(conf.Tr.Value("config.err.tempscale", kv[1]))
}
case statusbar:
bv, err := strconv.ParseBool(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.Statusbar = bv
case netinterface:
conf.NetInterface = kv[1]
case layout:
conf.Layout = kv[1]
case maxlogsize:
iv, err := strconv.Atoi(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.MaxLogSize = int64(iv)
case export:
conf.ExportPort = kv[1]
case mbps:
conf.Mbps = true
case temperatures:
conf.Temps = strings.Split(kv[1], ",")
case nvidia:
nv, err := strconv.ParseBool(kv[1])
if err != nil {
return fmt.Errorf(conf.Tr.Value("config.err.line", ln, err.Error()))
}
conf.Nvidia = nv
}
}
return nil
}
// Write serializes the configuration to a file.
// The configuration written is based on the loaded configuration, plus any
// command-line changes, so it can be used to update an existing configuration
// file. The file will be written to the specificed `--config` argument file,
// if one is set; otherwise, it'll create one in the user's config directory.
func (conf *Config) Write() (string, error) {
var dir *configdir.Config
var file string = CONFFILE
if conf.ConfigFile == "" {
ds := conf.ConfigDir.QueryFolders(configdir.Global)
if len(ds) == 0 {
ds = conf.ConfigDir.QueryFolders(configdir.Local)
if len(ds) == 0 {
return "", fmt.Errorf("error locating config folders")
}
}
ds[0].CreateParentDir(CONFFILE)
dir = ds[0]
} else {
dir = &configdir.Config{}
dir.Path = filepath.Dir(conf.ConfigFile)
file = filepath.Base(conf.ConfigFile)
}
marshalled := marshal(conf)
err := dir.WriteFile(file, marshalled)
if err != nil {
return "", err
}
return filepath.Join(dir.Path, file), nil
}
func marshal(c *Config) []byte {
buff := bytes.NewBuffer(nil)
fmt.Fprintln(buff, "# Scale graphs to this level; 7 is the default, 2 is zoomed out.")
fmt.Fprintf(buff, "%s=%d\n", graphhorizontalscale, c.GraphHorizontalScale)
fmt.Fprintln(buff, "# If true, start the UI with the help visible")
fmt.Fprintf(buff, "%s=%t\n", helpvisible, c.HelpVisible)
fmt.Fprintln(buff, "# The color scheme to use. See `--list colorschemes`")
fmt.Fprintf(buff, "%s=%s\n", colorscheme, c.Colorscheme.Name)
fmt.Fprintln(buff, "# How frequently to update the UI, in nanoseconds")
fmt.Fprintf(buff, "%s=%d\n", updateinterval, c.UpdateInterval)
fmt.Fprintln(buff, "# If true, show the average CPU load")
fmt.Fprintf(buff, "%s=%t\n", averagecpu, c.AverageLoad)
fmt.Fprintln(buff, "# If true, show load per CPU")
fmt.Fprintf(buff, "%s=%t\n", percpuload, c.PercpuLoad)
fmt.Fprintln(buff, "# Temperature units. C for Celcius, F for Fahrenheit")
fmt.Fprintf(buff, "%s=%c\n", tempscale, c.TempScale)
fmt.Fprintln(buff, "# If true, display a status bar")
fmt.Fprintf(buff, "%s=%t\n", statusbar, c.Statusbar)
fmt.Fprintln(buff, "# The network interface to monitor")
fmt.Fprintf(buff, "%s=%s\n", netinterface, c.NetInterface)
fmt.Fprintln(buff, "# A layout name. See `--list layouts`")
fmt.Fprintf(buff, "%s=%s\n", layout, c.Layout)
fmt.Fprintln(buff, "# The maximum log file size, in bytes")
fmt.Fprintf(buff, "%s=%d\n", maxlogsize, c.MaxLogSize)
fmt.Fprintln(buff, "# If set, export data as Promethius metrics on the interface:port.\n# E.g., `:8080` (colon is required, interface is not)")
if c.ExportPort == "" {
fmt.Fprint(buff, "#")
}
fmt.Fprintf(buff, "%s=%s\n", export, c.ExportPort)
fmt.Fprintln(buff, "# Display network IO in mpbs if true")
fmt.Fprintf(buff, "%s=%t\n", mbps, c.Mbps)
fmt.Fprintln(buff, "# A list of enabled temp sensors. See `--list devices`")
if len(c.Temps) == 0 {
fmt.Fprint(buff, "#")
}
fmt.Fprintf(buff, "%s=%s\n", temperatures, strings.Join(c.Temps, ","))
fmt.Fprintln(buff, "# Enable NVidia GPU metrics.")
fmt.Fprintf(buff, "%s=%t\n", nvidia, c.Nvidia)
fmt.Fprintln(buff, "# To configure the NVidia refresh rate, set a duration:")
fmt.Fprintln(buff, "#nvidiarefresh=30s")
return buff.Bytes()
}
const (
graphhorizontalscale = "graphhorizontalscale"
helpvisible = "helpvisible"
colorscheme = "colorscheme"
updateinterval = "updateinterval"
averagecpu = "averagecpu"
percpuload = "percpuload"
tempscale = "tempscale"
statusbar = "statusbar"
netinterface = "netinterface"
layout = "layout"
maxlogsize = "maxlogsize"
export = "metricsexportport"
mbps = "mbps"
temperatures = "temperatures"
nvidia = "nvidia"
)