summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean E. Russell <ser@ser1.net>2020-03-07 09:03:25 -0600
committerSean E. Russell <ser@ser1.net>2020-03-07 09:03:25 -0600
commit9ebcff9b78ef6c529969d34ebac757af9a28ebb2 (patch)
tree1122fdf4c06e31312bb1e5d9ebef919101d038d3
parent9188b14094ecb36fbea9ecdfe6247342a962e7d2 (diff)
Add ability to look in system-wide directories for configuration files
-rw-r--r--CHANGELOG.md14
-rw-r--r--cmd/gotop/main.go98
-rw-r--r--colorschemes/registry.go22
-rw-r--r--config.go27
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--logging/logging.go14
7 files changed, 120 insertions, 58 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 72af996..4b1d134 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
> - **Fixed**: for any bug fixes.
> - **Security**: in case of vulnerabilities.
+## [3.6.0] - ???
+
+### Added
+
+- Adds support for system-wide configurations. This improves support for package maintainers.
+
+### Changed
+
+- Log files stored in \$XDG_CACHE_HOME; DATA, CONFIG, CACHE, and RUNTIME are the only directories specified by the FreeDesktop spec.
+
+### Removed
+
+- configdir, logdir, and logfile options in the config file are no longer used. gotop looks for a configuration file, layouts, and colorschemes in the following order: command-line; `pwd`; user-home, and finally a system-wide path. The paths depend on the OS and whether XDG is in use.
+
## [3.5.1] - ??
### Fixed
diff --git a/cmd/gotop/main.go b/cmd/gotop/main.go
index b1dcc4d..c408f18 100644
--- a/cmd/gotop/main.go
+++ b/cmd/gotop/main.go
@@ -17,12 +17,12 @@ import (
docopt "github.com/docopt/docopt.go"
ui "github.com/gizak/termui/v3"
"github.com/prometheus/client_golang/prometheus/promhttp"
+ "github.com/shibukawa/configdir"
"github.com/xxxserxxx/gotop/v3"
"github.com/xxxserxxx/gotop/v3/colorschemes"
"github.com/xxxserxxx/gotop/v3/layout"
"github.com/xxxserxxx/gotop/v3/logging"
- "github.com/xxxserxxx/gotop/v3/utils"
w "github.com/xxxserxxx/gotop/v3/widgets"
)
@@ -73,6 +73,7 @@ Options:
-x, --export=PORT Enable metrics for export on the specified port.
-X, --extensions=NAMES Enables the listed extensions. This is a comma-separated list without the .so suffix. The current and config directories will be searched.
--test Runs tests and exits with success/failure code
+ --print-paths List out the paths that gotop will look for gotop.conf, layouts, color schemes, and extensions
Built-in layouts:
@@ -158,6 +159,14 @@ Colorschemes:
if val, _ := args["--test"]; val != nil {
conf.Test = val.(bool)
}
+ if args["--print-paths"].(bool) {
+ paths := make([]string, 0)
+ for _, d := range conf.ConfigDir.QueryFolders(configdir.All) {
+ paths = append(paths, d.Path)
+ }
+ fmt.Println(strings.Join(paths, "\n"))
+ os.Exit(0)
+ }
return nil
}
@@ -347,12 +356,10 @@ func eventLoop(c gotop.Config, grid *layout.MyGrid) {
}
func makeConfig() gotop.Config {
- ld := utils.GetLogDir(appName)
- cd := utils.GetConfigDir(appName)
+ cd := configdir.New("", appName)
+ cd.LocalPath, _ = filepath.Abs(".")
conf = gotop.Config{
ConfigDir: cd,
- LogDir: ld,
- LogFile: "errors.log",
GraphHorizontalScale: 7,
HelpVisible: false,
UpdateInterval: time.Second,
@@ -371,27 +378,29 @@ func makeConfig() gotop.Config {
func main() {
// Set up default config
conf := makeConfig()
- // Parse the config file
- cfn := filepath.Join(conf.ConfigDir, "gotop.conf")
- if cf, err := os.Open(cfn); err == nil {
- err := gotop.Parse(cf, &conf)
- if err != nil {
- stderrLogger.Fatalf("error parsing config file %v", err)
- }
+ // Find the config file; look in (1) local, (2) user, (3) global
+ err := conf.Load()
+ if err != nil {
+ stderrLogger.Printf("failed to parse config file: %s", err)
}
// Override with command line arguments
- err := parseArgs(&conf)
+ err = parseArgs(&conf)
if err != nil {
stderrLogger.Fatalf("failed to parse cli args: %v", err)
}
logfile, err := logging.New(conf)
if err != nil {
- stderrLogger.Fatalf("failed to setup log file: %v", err)
+ fmt.Printf("failed to setup log file: %v\n", err)
+ os.Exit(1)
}
defer logfile.Close()
- lstream := getLayout(conf)
+ lstream, err := getLayout(conf)
+ if err != nil {
+ fmt.Printf("failed to find layou: %s\n", err)
+ os.Exit(1)
+ }
ly := layout.ParseLayout(lstream)
loadExtensions(conf)
@@ -439,30 +448,34 @@ func main() {
eventLoop(conf, grid)
}
-func getLayout(conf gotop.Config) io.Reader {
+func getLayout(conf gotop.Config) (io.Reader, error) {
switch conf.Layout {
case "-":
- return os.Stdin
+ return os.Stdin, nil
case "default":
- return strings.NewReader(defaultUI)
+ return strings.NewReader(defaultUI), nil
case "minimal":
- return strings.NewReader(minimalUI)
+ return strings.NewReader(minimalUI), nil
case "battery":
- return strings.NewReader(batteryUI)
+ return strings.NewReader(batteryUI), nil
case "procs":
- return strings.NewReader(procsUI)
+ return strings.NewReader(procsUI), nil
case "kitchensink":
- return strings.NewReader(kitchensink)
+ return strings.NewReader(kitchensink), nil
default:
- fp := filepath.Join(conf.ConfigDir, conf.Layout)
- fin, err := os.Open(fp)
- if err != nil {
- fin, err = os.Open(conf.Layout)
- if err != nil {
- log.Fatalf("Unable to open layout file %s or ./%s", fp, conf.Layout)
+ folder := conf.ConfigDir.QueryFolderContainsFile(conf.Layout)
+ if folder == nil {
+ paths := make([]string, 0)
+ for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) {
+ paths = append(paths, d.Path)
}
+ return nil, fmt.Errorf("unable find layout file %s in %s", conf.Layout, strings.Join(paths, ", "))
+ }
+ lo, err := folder.ReadFile(conf.Layout)
+ if err != nil {
+ return nil, err
}
- return fin
+ return strings.NewReader(string(lo)), nil
}
}
@@ -471,18 +484,17 @@ func loadExtensions(conf gotop.Config) {
for _, ex := range conf.Extensions {
exf := ex + ".so"
fn := exf
- _, err := os.Stat(fn)
- if err != nil && os.IsNotExist(err) {
- log.Printf("no plugin %s found in current directory", fn)
- fn = filepath.Join(conf.ConfigDir, exf)
- _, err = os.Stat(fn)
- if err != nil || os.IsNotExist(err) {
- hasError = true
- log.Printf("no plugin %s found in config directory", fn)
- continue
+ folder := conf.ConfigDir.QueryFolderContainsFile(fn)
+ if folder == nil {
+ paths := make([]string, 0)
+ for _, d := range conf.ConfigDir.QueryFolders(configdir.Existing) {
+ paths = append(paths, d.Path)
}
+ log.Printf("unable find extension %s in %s", fn, strings.Join(paths, ", "))
+ continue
}
- p, err := plugin.Open(fn)
+ fp := filepath.Join(folder.Path, fn)
+ p, err := plugin.Open(fp)
if err != nil {
hasError = true
log.Printf(err.Error())
@@ -494,7 +506,6 @@ func loadExtensions(conf gotop.Config) {
log.Printf(err.Error())
continue
}
-
initFunc, ok := init.(func())
if !ok {
hasError = true
@@ -505,7 +516,12 @@ func loadExtensions(conf gotop.Config) {
}
if hasError {
ui.Close()
- fmt.Printf("Error initializing requested plugins; check the log file %s\n", filepath.Join(conf.ConfigDir, conf.LogFile))
+ folder := conf.ConfigDir.QueryFolderContainsFile(logging.LOGFILE)
+ if folder == nil {
+ fmt.Printf("error initializing requested plugins\n")
+ } else {
+ fmt.Printf("error initializing requested plugins; check the log file %s\n", filepath.Join(folder.Path, logging.LOGFILE))
+ }
os.Exit(1)
}
}
diff --git a/colorschemes/registry.go b/colorschemes/registry.go
index d79271d..8f86491 100644
--- a/colorschemes/registry.go
+++ b/colorschemes/registry.go
@@ -3,8 +3,10 @@ package colorschemes
import (
"encoding/json"
"fmt"
- "io/ioutil"
"path/filepath"
+ "strings"
+
+ "github.com/shibukawa/configdir"
)
var registry map[string]Colorscheme
@@ -15,7 +17,7 @@ func init() {
}
}
-func FromName(confDir string, c string) (Colorscheme, error) {
+func FromName(confDir configdir.ConfigDir, c string) (Colorscheme, error) {
cs, ok := registry[c]
if !ok {
cs, err := getCustomColorscheme(confDir, c)
@@ -34,12 +36,20 @@ func register(name string, c Colorscheme) {
}
// getCustomColorscheme tries to read a custom json colorscheme from <configDir>/<name>.json
-func getCustomColorscheme(confDir string, name string) (Colorscheme, error) {
+func getCustomColorscheme(confDir configdir.ConfigDir, name string) (Colorscheme, error) {
var cs Colorscheme
- filePath := filepath.Join(confDir, name+".json")
- dat, err := ioutil.ReadFile(filePath)
+ fn := name + ".json"
+ folder := confDir.QueryFolderContainsFile(fn)
+ if folder == nil {
+ paths := make([]string, 0)
+ for _, d := range confDir.QueryFolders(configdir.Existing) {
+ paths = append(paths, d.Path)
+ }
+ return cs, fmt.Errorf("failed to find colorscheme file %s in %s", fn, strings.Join(paths, ", "))
+ }
+ dat, err := folder.ReadFile(fn)
if err != nil {
- return cs, fmt.Errorf("failed to read colorscheme file: %v", err)
+ return cs, fmt.Errorf("failed to read colorscheme file %s: %v", filepath.Join(folder.Path, fn), err)
}
err = json.Unmarshal(dat, &cs)
if err != nil {
diff --git a/config.go b/config.go
index d072dee..de543d3 100644
--- a/config.go
+++ b/config.go
@@ -4,19 +4,20 @@ import (
"bufio"
"fmt"
"io"
+ "log"
+ "os"
"strconv"
"strings"
"time"
+ "github.com/shibukawa/configdir"
"github.com/xxxserxxx/gotop/v3/colorschemes"
"github.com/xxxserxxx/gotop/v3/widgets"
)
// TODO: test, build, release [#119] [#120] [#121]
type Config struct {
- ConfigDir string
- LogDir string
- LogFile string
+ ConfigDir configdir.ConfigDir
GraphHorizontalScale int
HelpVisible bool
@@ -36,7 +37,19 @@ type Config struct {
Test bool
}
-func Parse(in io.Reader, conf *Config) error {
+func (conf *Config) Load() error {
+ var in io.Reader
+ cfn := "gotop.conf"
+ folder := conf.ConfigDir.QueryFolderContainsFile(cfn)
+ if folder != nil {
+ if cf, err := os.Open(cfn); err == nil {
+ defer cf.Close()
+ } else {
+ return err
+ }
+ } else {
+ return nil
+ }
r := bufio.NewScanner(in)
var lineNo int
for r.Scan() {
@@ -50,11 +63,11 @@ func Parse(in io.Reader, conf *Config) error {
default:
return fmt.Errorf("invalid config option %s", key)
case "configdir":
- conf.ConfigDir = kv[1]
+ log.Printf("configdir is deprecated. Ignored configdir=%s", kv[1])
case "logdir":
- conf.LogDir = kv[1]
+ log.Printf("logdir is deprecated. Ignored logdir=%s", kv[1])
case "logfile":
- conf.LogFile = kv[1]
+ log.Printf("logfile is deprecated. Ignored logfile=%s", kv[1])
case "graphhorizontalscale":
iv, err := strconv.Atoi(kv[1])
if err != nil {
diff --git a/go.mod b/go.mod
index 38e1380..2b53f3a 100644
--- a/go.mod
+++ b/go.mod
@@ -9,6 +9,7 @@ require (
github.com/mattn/go-runewidth v0.0.4
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/prometheus/client_golang v1.4.1
+ github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
github.com/shirou/gopsutil v2.18.11+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/stretchr/testify v1.4.0
diff --git a/go.sum b/go.sum
index 3cf7498..0a20cb4 100644
--- a/go.sum
+++ b/go.sum
@@ -83,6 +83,8 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
+github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
github.com/shirou/gopsutil v2.18.11+incompatible h1:PMFTKnFTr/YTRW5rbLK4vWALV3a+IGXse5nvhSjztmg=
github.com/shirou/gopsutil v2.18.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
diff --git a/logging/logging.go b/logging/logging.go
index 6b023d7..c79b5c8 100644
--- a/logging/logging.go
+++ b/logging/logging.go
@@ -11,16 +11,22 @@ import (
"github.com/xxxserxxx/gotop/v3"
)
+const (
+ LOGFILE = "errors.log"
+)
+
func New(c gotop.Config) (io.WriteCloser, error) {
// create the log directory
- if err := os.MkdirAll(c.LogDir, 0755); err != nil {
- return nil, fmt.Errorf("failed to make the log directory: %v", err)
+ cache := c.ConfigDir.QueryCacheFolder()
+ err := cache.MkdirAll()
+ if err != nil && !os.IsExist(err) {
+ return nil, err
}
w := &RotateWriter{
- filename: filepath.Join(c.LogDir, c.LogFile),
+ filename: filepath.Join(cache.Path, LOGFILE),
maxLogSize: c.MaxLogSize,
}
- err := w.rotate()
+ err = w.rotate()
if err != nil {
return nil, err
}