aerc/config/config.go

289 lines
7 KiB
Go
Raw Normal View History

2018-01-10 00:18:19 +00:00
package config
import (
2019-03-21 21:36:42 +00:00
"errors"
2018-01-10 02:24:50 +00:00
"fmt"
2018-01-10 00:18:19 +00:00
"path"
2019-03-31 18:42:18 +00:00
"regexp"
2018-01-10 02:24:50 +00:00
"strings"
2018-01-10 00:18:19 +00:00
"unicode"
2018-01-10 16:19:45 +00:00
"github.com/go-ini/ini"
"github.com/kyoh86/xdg"
2018-01-10 00:18:19 +00:00
)
type UIConfig struct {
2019-03-16 00:40:28 +00:00
IndexFormat string `ini:"index-format"`
TimestampFormat string `ini:"timestamp-format"`
2018-01-10 00:18:19 +00:00
ShowHeaders []string `delim:","`
LoadingFrames []string `delim:","`
2019-03-16 00:40:28 +00:00
RenderAccountTabs string `ini:"render-account-tabs"`
SidebarWidth int `ini:"sidebar-width"`
PreviewHeight int `ini:"preview-height"`
EmptyMessage string `ini:"empty-message"`
2018-01-10 00:18:19 +00:00
}
2019-03-31 18:24:53 +00:00
const (
FILTER_MIMETYPE = iota
FILTER_HEADER
)
2018-01-10 00:18:19 +00:00
type AccountConfig struct {
2019-05-13 03:35:36 +00:00
Default string
From string
2019-05-13 03:35:36 +00:00
Name string
Source string
Folders []string
Params map[string]string
Outgoing string
2018-01-10 00:18:19 +00:00
}
2019-03-21 21:36:42 +00:00
type BindingConfig struct {
Global *KeyBindings
Compose *KeyBindings
ComposeEditor *KeyBindings
ComposeReview *KeyBindings
MessageList *KeyBindings
MessageView *KeyBindings
Terminal *KeyBindings
2019-03-21 21:36:42 +00:00
}
type ComposeConfig struct {
Editor string `ini:"editor"`
}
2019-03-31 18:24:53 +00:00
type FilterConfig struct {
FilterType int
Filter string
Command string
2019-03-31 18:42:18 +00:00
Header string
Regex *regexp.Regexp
2019-03-31 18:24:53 +00:00
}
type ViewerConfig struct {
Pager string
Alternatives []string
}
2018-01-10 00:18:19 +00:00
type AercConfig struct {
2019-03-21 21:36:42 +00:00
Bindings BindingConfig
Compose ComposeConfig
2018-01-10 00:18:19 +00:00
Ini *ini.File `ini:"-"`
Accounts []AccountConfig `ini:"-"`
2019-03-31 18:24:53 +00:00
Filters []FilterConfig `ini:"-"`
Viewer ViewerConfig `ini:"-"`
2018-01-10 00:18:19 +00:00
Ui UIConfig
}
// Input: TimestampFormat
// Output: timestamp-format
func mapName(raw string) string {
newstr := make([]rune, 0, len(raw))
for i, chr := range raw {
if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
if i > 0 {
newstr = append(newstr, '-')
}
}
newstr = append(newstr, unicode.ToLower(chr))
}
return string(newstr)
}
2018-01-10 02:24:50 +00:00
func loadAccountConfig(path string) ([]AccountConfig, error) {
2018-01-10 16:19:45 +00:00
file, err := ini.Load(path)
if err != nil {
2018-01-10 02:24:50 +00:00
return nil, err
}
file.NameMapper = mapName
2018-01-10 16:19:45 +00:00
var accounts []AccountConfig
2018-01-10 02:24:50 +00:00
for _, _sec := range file.SectionStrings() {
if _sec == "DEFAULT" {
continue
}
sec := file.Section(_sec)
2019-03-16 01:33:08 +00:00
account := AccountConfig{
Default: "INBOX",
Name: _sec,
Params: make(map[string]string),
}
2018-01-10 02:24:50 +00:00
if err = sec.MapTo(&account); err != nil {
return nil, err
}
for key, val := range sec.KeysHash() {
2019-03-16 01:33:08 +00:00
if key == "folders" {
2018-01-10 02:24:50 +00:00
account.Folders = strings.Split(val, ",")
2019-05-13 03:35:36 +00:00
} else if key == "outgoing" {
account.Outgoing = val
} else if key == "from" {
account.From = val
2018-01-10 02:24:50 +00:00
} else if key != "name" {
account.Params[key] = val
}
}
if account.Source == "" {
return nil, fmt.Errorf("Expected source for account %s", _sec)
}
accounts = append(accounts, account)
}
if len(accounts) == 0 {
err = errors.New("No accounts configured in accounts.conf")
return nil, err
}
2018-01-10 02:24:50 +00:00
return accounts, nil
}
2018-01-10 00:18:19 +00:00
func LoadConfig(root *string) (*AercConfig, error) {
if root == nil {
_root := path.Join(xdg.ConfigHome(), "aerc")
root = &_root
}
2018-01-10 16:19:45 +00:00
file, err := ini.Load(path.Join(*root, "aerc.conf"))
if err != nil {
2018-01-10 00:18:19 +00:00
return nil, err
}
file.NameMapper = mapName
config := &AercConfig{
2019-03-21 21:36:42 +00:00
Bindings: BindingConfig{
Global: NewKeyBindings(),
Compose: NewKeyBindings(),
ComposeEditor: NewKeyBindings(),
ComposeReview: NewKeyBindings(),
MessageList: NewKeyBindings(),
MessageView: NewKeyBindings(),
Terminal: NewKeyBindings(),
2019-03-21 21:36:42 +00:00
},
Ini: file,
2018-01-10 00:18:19 +00:00
Ui: UIConfig{
IndexFormat: "%4C %Z %D %-17.17n %s",
TimestampFormat: "%F %l:%M %p",
ShowHeaders: []string{
"From", "To", "Cc", "Bcc", "Subject", "Date",
},
LoadingFrames: []string{
"[..] ", " [..] ", " [..]", " [..] ",
},
RenderAccountTabs: "auto",
SidebarWidth: 20,
PreviewHeight: 12,
EmptyMessage: "(no messages)",
},
}
2019-03-31 18:24:53 +00:00
if filters, err := file.GetSection("filters"); err == nil {
// TODO: Parse the filter more finely, e.g. parse the regex
for _, match := range filters.KeyStrings() {
cmd := filters.KeysHash()[match]
2019-03-31 18:24:53 +00:00
filter := FilterConfig{
Command: cmd,
Filter: match,
}
2019-03-31 18:42:18 +00:00
if strings.Contains(match, ",~") {
2019-03-31 18:24:53 +00:00
filter.FilterType = FILTER_HEADER
2019-03-31 18:42:18 +00:00
header := filter.Filter[:strings.Index(filter.Filter, ",")]
regex := filter.Filter[strings.Index(filter.Filter, "~")+1:]
filter.Header = strings.ToLower(header)
filter.Regex, err = regexp.Compile(regex)
if err != nil {
panic(err)
}
} else if strings.ContainsRune(match, ',') {
filter.FilterType = FILTER_HEADER
header := filter.Filter[:strings.Index(filter.Filter, ",")]
value := filter.Filter[strings.Index(filter.Filter, ",")+1:]
filter.Header = strings.ToLower(header)
filter.Regex, err = regexp.Compile(regexp.QuoteMeta(value))
2019-03-31 18:24:53 +00:00
} else {
filter.FilterType = FILTER_MIMETYPE
}
config.Filters = append(config.Filters, filter)
}
}
if viewer, err := file.GetSection("viewer"); err == nil {
if err := viewer.MapTo(&config.Viewer); err != nil {
return nil, err
}
for key, val := range viewer.KeysHash() {
switch key {
case "alternatives":
config.Viewer.Alternatives = strings.Split(val, ",")
}
}
}
if compose, err := file.GetSection("compose"); err == nil {
if err := compose.MapTo(&config.Compose); err != nil {
return nil, err
}
}
if ui, err := file.GetSection("ui"); err == nil {
2019-03-16 00:40:28 +00:00
if err := ui.MapTo(&config.Ui); err != nil {
return nil, err
}
2018-01-10 00:18:19 +00:00
}
2018-01-10 02:24:50 +00:00
accountsPath := path.Join(*root, "accounts.conf")
if accounts, err := loadAccountConfig(accountsPath); err != nil {
return nil, err
} else {
config.Accounts = accounts
}
2019-03-21 21:36:42 +00:00
binds, err := ini.Load(path.Join(*root, "binds.conf"))
if err != nil {
return nil, err
}
groups := map[string]**KeyBindings{
"default": &config.Bindings.Global,
"compose": &config.Bindings.Compose,
"messages": &config.Bindings.MessageList,
"terminal": &config.Bindings.Terminal,
"view": &config.Bindings.MessageView,
"compose::editor": &config.Bindings.ComposeEditor,
"compose::review": &config.Bindings.ComposeReview,
2019-03-21 21:36:42 +00:00
}
for _, name := range binds.SectionStrings() {
sec, err := binds.GetSection(name)
if err != nil {
return nil, err
}
group, ok := groups[strings.ToLower(name)]
if !ok {
return nil, errors.New("Unknown keybinding group " + name)
}
bindings := NewKeyBindings()
for key, value := range sec.KeysHash() {
if key == "$ex" {
strokes, err := ParseKeyStrokes(value)
if err != nil {
return nil, err
}
if len(strokes) != 1 {
return nil, errors.New(
"Error: only one keystroke supported for $ex")
}
bindings.ExKey = strokes[0]
continue
}
if key == "$noinherit" {
if value == "false" {
continue
}
if value != "true" {
return nil, errors.New(
"Error: expected 'true' or 'false' for $noinherit")
}
bindings.Globals = false
}
binding, err := ParseBinding(key, value)
if err != nil {
return nil, err
}
bindings.Add(binding)
}
*group = MergeBindings(bindings, *group)
}
// Globals can't inherit from themselves
config.Bindings.Global.Globals = false
2018-01-10 00:18:19 +00:00
return config, nil
}