Add ability to look in system-wide directories for configuration files
This commit is contained in:
parent
9188b14094
commit
9ebcff9b78
14
CHANGELOG.md
14
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
|
||||
|
|
|
@ -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, ", "))
|
||||
}
|
||||
return fin
|
||||
lo, err := folder.ReadFile(conf.Layout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
27
config.go
27
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 {
|
||||
|
|
1
go.mod
1
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
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user