summaryrefslogblamecommitdiff
path: root/config.go
blob: beebf7a2c40c5f75361446b5e8baf28c747449a6 (plain) (tree)
1
2
3
4
5
6
7
8
9


              
                
             
                 
            
                 
              
                 
                 
 

                                     
                                     
 
                                     

 
                                                 





                                                           
                                                  








                                    
                                       






                                                                                                                            
                                     



                  
                          

                          
                             

 
                    










                         

                                 
                             




                             
                            

                           
 
                  

 









                                                               










                                       
                                        




                                       
                                    
                                     

                                                   
                                                   






                             
                                                              





                                       


                               











                                                          
                                                                        







                                                                     





                                                                                               
                 
         


              














































                                                                                            



                                                                         








                                                                                        








                                                                                                      












                                                                                                        

                                                                                               

                                                          

                                                                                  

                                                          
                                                          
                                                                          


                                                                                    


                                                                           
                                               

                                                                                              

                                                          

                                                                                

                                                          
                                                         
                                                                            


                                                                                     


                                                                            








                                                                                                   
































                                                                                             




                                                                                    












                                                                                                           



                                                                                   

                                                   


                                                                 















                                                                                             



              
package senpai

import (
	"errors"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"

	"git.sr.ht/~taiite/senpai/ui"

	"github.com/gdamore/tcell/v2"

	"git.sr.ht/~emersion/go-scfg"
)

func parseColor(s string, c *tcell.Color) error {
	if strings.HasPrefix(s, "#") {
		hex, err := strconv.ParseInt(s[1:], 16, 32)
		if err != nil {
			return err
		}

		*c = tcell.NewHexColor(int32(hex))
		return nil
	}

	code, err := strconv.Atoi(s)
	if err != nil {
		return err
	}

	if code == -1 {
		*c = tcell.ColorDefault
		return nil
	}

	if code < 0 || code > 255 {
		return fmt.Errorf("color code must be between 0-255. If you meant to use true colors, use #aabbcc notation")
	}

	*c = tcell.PaletteColor(code)

	return nil
}

type ConfigColors struct {
	Prompt tcell.Color
	Unread tcell.Color
	Nicks  ui.ColorScheme
}

type Config struct {
	Addr     string
	Nick     string
	Real     string
	User     string
	Password *string
	TLS      bool
	Channels []string

	Typings bool
	Mouse   bool

	Highlights       []string
	OnHighlightPath  string
	OnHighlightBeep  bool
	NickColWidth     int
	ChanColWidth     int
	ChanColEnabled   bool
	MemberColWidth   int
	MemberColEnabled bool
	TextMaxWidth     int

	Colors ConfigColors

	Debug bool
}

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:  "",
		OnHighlightBeep:  false,
		NickColWidth:     14,
		ChanColWidth:     16,
		ChanColEnabled:   true,
		MemberColWidth:   16,
		MemberColEnabled: true,
		TextMaxWidth:     0,
		Colors: ConfigColors{
			Prompt: tcell.ColorDefault,
			Unread: tcell.ColorDefault,
			Nicks:  ui.ColorSchemeBase,
		},
		Debug: false,
	}

	return
}

func LoadConfigFile(filename string) (cfg Config, err error) {
	cfg, err = Defaults()
	if err != nil {
		return
	}

	err = unmarshal(filename, &cfg)
	if err != nil {
		return cfg, err
	}
	if cfg.Addr == "" {
		return cfg, errors.New("addr is required")
	}
	if cfg.Nick == "" {
		return cfg, errors.New("nick is required")
	}
	if cfg.User == "" {
		cfg.User = cfg.Nick
	}
	if cfg.Real == "" {
		cfg.Real = cfg.Nick
	}
	if u, err := url.Parse(cfg.Addr); err == nil && u.Scheme != "" {
		switch u.Scheme {
		case "ircs":
			cfg.TLS = true
		case "irc+insecure":
			cfg.TLS = false
		case "irc":
			// Could be TLS or plaintext, keep TLS as is.
		default:
			if u.Host != "" {
				return cfg, fmt.Errorf("invalid IRC addr scheme: %v", cfg.Addr)
			}
		}
		if u.Host != "" {
			cfg.Addr = u.Host
		}
	}
	return
}

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)
			}

			passCmdOut := strings.Split(string(stdout), "\n")
			if len(passCmdOut) >= 1 {
				cfg.Password = &passCmdOut[0]
			}
		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 "on-highlight-beep":
			var onHighlightBeep string
			if err := d.ParseParams(&onHighlightBeep); err != nil {
				return err
			}

			if cfg.OnHighlightBeep, err = strconv.ParseBool(onHighlightBeep); 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 channelsStr string
					if err := child.ParseParams(&channelsStr); err != nil {
						return err
					}
					channels, err := strconv.Atoi(channelsStr)
					if err != nil {
						return err
					}
					if channels <= 0 {
						cfg.ChanColEnabled = false
						if channels < 0 {
							cfg.ChanColWidth = -channels
						}
					} else {
						cfg.ChanColWidth = channels
					}
				case "members":
					var membersStr string
					if err := child.ParseParams(&membersStr); err != nil {
						return err
					}
					members, err := strconv.Atoi(membersStr)
					if err != nil {
						return err
					}
					if members <= 0 {
						cfg.MemberColEnabled = false
						if members < 0 {
							cfg.MemberColWidth = -members
						}
					} else {
						cfg.MemberColWidth = members
					}
				case "text":
					var text string
					if err := child.ParseParams(&text); err != nil {
						return err
					}

					if cfg.TextMaxWidth, err = strconv.Atoi(text); 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 {
				var colorStr string
				if err := child.ParseParams(&colorStr); err != nil {
					return err
				}

				switch child.Name {
				case "nicks":
					switch colorStr {
					case "base":
						cfg.Colors.Nicks = ui.ColorSchemeBase
					case "extended":
						cfg.Colors.Nicks = ui.ColorSchemeExtended
					default:
						return fmt.Errorf("unknown nick color scheme %q", colorStr)
					}
					continue
				}

				var color tcell.Color
				if err = parseColor(colorStr, &color); err != nil {
					return err
				}
				switch child.Name {
				case "prompt":
					cfg.Colors.Prompt = color
				case "unread":
					cfg.Colors.Unread = color
				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
}