summaryrefslogtreecommitdiff
path: root/config.go
diff options
context:
space:
mode:
authorKalyan Sriram <kalyan@coderkalyan.com>2021-11-22 18:37:40 +0000
committerHubert Hirtz <hubert@hirtz.pm>2021-11-23 09:10:59 +0100
commitb46900566104c0143e06df452fb994e888b23548 (patch)
tree2d4f0e14c069fd8a4a7ff91ea9ee43d87ad66b58 /config.go
parentAdd support for CHATHISTORY TARGETS (diff)
config: replace YAML with scfg config format
This patch replaces the YAML configuration format with scfg (https://git.sr.ht/~emersion/scfg). Additionally, a few things about configuration are cleaned up: * abbreviated names are expanded (addr -> address, nick -> nickname) * negative bools switched to positive (no-tls -> tls) * independent column widths are grouped under the "pane-width" directive * implementation of default configuration values is improved * password-cmd is executed directly (with scfg field parsing) instead of with "sh -c". * on-highlight is now a file, $XDG_CONFIG_HOME/senpai/highlight by default, which can be changed with the on-highlight-path directive
Diffstat (limited to 'config.go')
-rw-r--r--config.go272
1 files changed, 215 insertions, 57 deletions
diff --git a/config.go b/config.go
index 8c8181d..5a3cc2b 100644
--- a/config.go
+++ b/config.go
@@ -3,21 +3,20 @@ package senpai
import (
"errors"
"fmt"
- "io/ioutil"
+ "os"
"os/exec"
+ "path"
"strconv"
"strings"
"github.com/gdamore/tcell/v2"
- "gopkg.in/yaml.v2"
+ "git.sr.ht/~emersion/go-scfg"
)
type Color tcell.Color
-func (c *Color) UnmarshalText(data []byte) error {
- s := string(data)
-
+func parseColor(s string, c *Color) error {
if strings.HasPrefix(s, "#") {
hex, err := strconv.ParseInt(s[1:], 16, 32)
if err != nil {
@@ -47,34 +46,73 @@ func (c *Color) UnmarshalText(data []byte) error {
return nil
}
+type ConfigColors struct {
+ Prompt Color
+}
+
type Config struct {
- Addr string
- Nick string
- Real string
- User string
- Password *string
- PasswordCmd string `yaml:"password-cmd"`
- NoTLS bool `yaml:"no-tls"`
- Channels []string
-
- NoTypings bool `yaml:"no-typings"`
- Mouse *bool
-
- Highlights []string
- OnHighlight string `yaml:"on-highlight"`
- NickColWidth int `yaml:"nick-column-width"`
- ChanColWidth int `yaml:"chan-column-width"`
- MemberColWidth int `yaml:"member-column-width"`
-
- Colors struct {
- Prompt Color
- }
+ Addr string
+ Nick string
+ Real string
+ User string
+ Password *string
+ TLS bool
+ Channels []string
+
+ Typings bool
+ Mouse bool
+
+ Highlights []string
+ OnHighlightPath string
+ NickColWidth int
+ ChanColWidth int
+ MemberColWidth int
+
+ Colors ConfigColors
Debug bool
}
-func ParseConfig(buf []byte) (cfg Config, err error) {
- err = yaml.Unmarshal(buf, &cfg)
+func DefaultHighlightPath() (string, error) {
+ configDir, err := os.UserConfigDir()
+ if err != nil {
+ return "", err
+ }
+ return path.Join(configDir, "senpai", "highlight"), nil
+}
+
+func Defaults() (cfg Config, err error) {
+ cfg = Config{
+ Addr: "",
+ Nick: "",
+ Real: "",
+ User: "",
+ Password: nil,
+ TLS: true,
+ Channels: nil,
+ Typings: true,
+ Mouse: true,
+ Highlights: nil,
+ OnHighlightPath: "",
+ NickColWidth: 16,
+ ChanColWidth: 0,
+ MemberColWidth: 0,
+ Colors: ConfigColors{
+ Prompt: Color(tcell.ColorDefault),
+ },
+ Debug: false,
+ }
+
+ return
+}
+
+func ParseConfig(filename string) (cfg Config, err error) {
+ cfg, err = Defaults()
+ if err != nil {
+ return
+ }
+
+ err = unmarshal(filename, &cfg)
if err != nil {
return cfg, err
}
@@ -90,45 +128,165 @@ func ParseConfig(buf []byte) (cfg Config, err error) {
if cfg.Real == "" {
cfg.Real = cfg.Nick
}
- if cfg.PasswordCmd != "" {
- password, err := runPasswordCmd(cfg.PasswordCmd)
- if err != nil {
- return cfg, err
- }
- cfg.Password = &password
- }
- if cfg.NickColWidth <= 0 {
- cfg.NickColWidth = 16
- }
- if cfg.ChanColWidth < 0 {
- cfg.ChanColWidth = 0
- }
- if cfg.MemberColWidth < 0 {
- cfg.MemberColWidth = 0
- }
return
}
func LoadConfigFile(filename string) (cfg Config, err error) {
- var buf []byte
-
- buf, err = ioutil.ReadFile(filename)
- if err != nil {
- return cfg, fmt.Errorf("failed to read the file: %s", err)
- }
-
- cfg, err = ParseConfig(buf)
+ cfg, err = ParseConfig(filename)
if err != nil {
return cfg, fmt.Errorf("invalid content found in the file: %s", err)
}
return
}
-func runPasswordCmd(command string) (password string, err error) {
- cmd := exec.Command("sh", "-c", command)
- stdout, err := cmd.Output()
- if err == nil {
- password = strings.TrimSuffix(string(stdout), "\n")
+func unmarshal(filename string, cfg *Config) (err error) {
+ directives, err := scfg.Load(filename)
+ if err != nil {
+ return fmt.Errorf("error parsing scfg: %s", err)
+ }
+
+ for _, d := range directives {
+ switch d.Name {
+ case "address":
+ if err := d.ParseParams(&cfg.Addr); err != nil {
+ return err
+ }
+ case "nickname":
+ if err := d.ParseParams(&cfg.Nick); err != nil {
+ return err
+ }
+ case "username":
+ if err := d.ParseParams(&cfg.User); err != nil {
+ return err
+ }
+ case "realname":
+ if err := d.ParseParams(&cfg.Real); err != nil {
+ return err
+ }
+ case "password":
+ // if a password-cmd is provided, don't use this value
+ if directives.Get("password-cmd") != nil {
+ continue
+ }
+
+ var password string
+ if err := d.ParseParams(&password); err != nil {
+ return err
+ }
+ cfg.Password = &password
+ case "password-cmd":
+ var cmdName string
+ if err := d.ParseParams(&cmdName); err != nil {
+ return err
+ }
+
+ cmd := exec.Command(cmdName, d.Params[1:]...)
+ var stdout []byte
+ if stdout, err = cmd.Output(); err != nil {
+ return fmt.Errorf("error running password command: %s", err)
+ }
+
+ password := strings.TrimSuffix(string(stdout), "\n")
+ cfg.Password = &password
+ case "channel":
+ // TODO: does this work with soju.im/bouncer-networks extension?
+ cfg.Channels = append(cfg.Channels, d.Params...)
+ case "highlight":
+ cfg.Highlights = append(cfg.Highlights, d.Params...)
+ case "on-highlight-path":
+ if err := d.ParseParams(&cfg.OnHighlightPath); err != nil {
+ return err
+ }
+ case "pane-widths":
+ for _, child := range d.Children {
+ switch child.Name {
+ case "nicknames":
+ var nicknames string
+ if err := child.ParseParams(&nicknames); err != nil {
+ return err
+ }
+
+ if cfg.NickColWidth, err = strconv.Atoi(nicknames); err != nil {
+ return err
+ }
+ case "channels":
+ var channels string
+ if err := child.ParseParams(&channels); err != nil {
+ return err
+ }
+
+ if cfg.ChanColWidth, err = strconv.Atoi(channels); err != nil {
+ return err
+ }
+ case "members":
+ var members string
+ if err := child.ParseParams(&members); err != nil {
+ return err
+ }
+
+ if cfg.MemberColWidth, err = strconv.Atoi(members); err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("unknown directive %q", child.Name)
+ }
+ }
+ case "tls":
+ var tls string
+ if err := d.ParseParams(&tls); err != nil {
+ return err
+ }
+
+ if cfg.TLS, err = strconv.ParseBool(tls); err != nil {
+ return err
+ }
+ case "typings":
+ var typings string
+ if err := d.ParseParams(&typings); err != nil {
+ return err
+ }
+
+ if cfg.Typings, err = strconv.ParseBool(typings); err != nil {
+ return err
+ }
+ case "mouse":
+ var mouse string
+ if err := d.ParseParams(&mouse); err != nil {
+ return err
+ }
+
+ if cfg.Mouse, err = strconv.ParseBool(mouse); err != nil {
+ return err
+ }
+ case "colors":
+ for _, child := range d.Children {
+ switch child.Name {
+ case "prompt":
+ var prompt string
+ if err := child.ParseParams(&prompt); err != nil {
+ return err
+ }
+
+ fmt.Println(prompt)
+ if err = parseColor(prompt, &cfg.Colors.Prompt); err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("unknown directive %q", child.Name)
+ }
+ }
+ case "debug":
+ var debug string
+ if err := d.ParseParams(&debug); err != nil {
+ return err
+ }
+
+ if cfg.Debug, err = strconv.ParseBool(debug); err != nil {
+ return err
+ }
+ default:
+ return fmt.Errorf("unknown directive %q", d.Name)
+ }
}
return