aboutsummaryrefslogtreecommitdiffstats
path: root/config/accounts.go
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2023-02-21 16:18:54 +0100
committerRobin Jarry <robin@jarry.cc>2023-03-02 23:56:13 +0100
commitb63c93563c622e70cda7006c1816dc6b59e75844 (patch)
tree821bd7636cf676640e7b391d8ce015bb33f2843e /config/accounts.go
parentd9a8edd8e9269aa1189d55c8d13caa05084435f5 (diff)
downloadaerc-b63c93563c622e70cda7006c1816dc6b59e75844.tar.gz
config: use reflection to map ini keys to struct fields
The default ini.Section.MapTo() function only handles basic types. Implement a more complete mapping solution that allows: * parsing templates, regexps, email addresses * defining a custom parsing method via the `parse:"MethodName"` tag * defining default values via the `default:"value"` tag * parsing rune values with the `type:"rune"` tag The field name must be specified in the `ini:"field-name"` tag as it was before. It is no longer optional. The `delim:"<separator>"` tag remains but can only be used to parse string arrays. It is now possible to override default values with "zero" values. For example: [ui] dirlist-delay = 0 Will override the default "200ms" value. Also: [statusline] status-columns = Will override the default "left<*,center>=,right>*" value. Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Tim Culverhouse <tim@timculverhouse.com>
Diffstat (limited to 'config/accounts.go')
-rw-r--r--config/accounts.go180
1 files changed, 78 insertions, 102 deletions
diff --git a/config/accounts.go b/config/accounts.go
index e9b7c633..8aadb329 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -10,7 +10,6 @@ import (
"reflect"
"regexp"
"sort"
- "strconv"
"strings"
"time"
@@ -70,30 +69,32 @@ func (c *RemoteConfig) ConnectionString() (string, error) {
}
type AccountConfig struct {
- Archive string `ini:"archive"`
- CopyTo string `ini:"copy-to"`
- Default string `ini:"default"`
- Postpone string `ini:"postpone"`
- From *mail.Address `ini:"-"`
- Aliases []*mail.Address `ini:"-"`
- Name string `ini:"-"`
- Source string `ini:"-"`
- Folders []string `ini:"folders" delim:","`
- FoldersExclude []string `ini:"folders-exclude" delim:","`
- Params map[string]string `ini:"-"`
- Outgoing RemoteConfig `ini:"-"`
- SignatureFile string `ini:"signature-file"`
- SignatureCmd string `ini:"signature-cmd"`
- EnableFoldersSort bool `ini:"enable-folders-sort"`
- FoldersSort []string `ini:"folders-sort" delim:","`
- AddressBookCmd string `ini:"address-book-cmd"`
- SendAsUTC bool `ini:"send-as-utc"`
- LocalizedRe *regexp.Regexp `ini:"-"`
+ Name string
+ // backend specific
+ Params map[string]string
+
+ Archive string `ini:"archive" default:"Archive"`
+ CopyTo string `ini:"copy-to"`
+ Default string `ini:"default" default:"INBOX"`
+ Postpone string `ini:"postpone" default:"Drafts"`
+ From *mail.Address `ini:"from"`
+ Aliases []*mail.Address `ini:"aliases"`
+ Source string `ini:"source" parse:"ParseSource"`
+ Folders []string `ini:"folders" delim:","`
+ FoldersExclude []string `ini:"folders-exclude" delim:","`
+ Outgoing RemoteConfig `ini:"outgoing" parse:"ParseOutgoing"`
+ SignatureFile string `ini:"signature-file"`
+ SignatureCmd string `ini:"signature-cmd"`
+ EnableFoldersSort bool `ini:"enable-folders-sort" default:"true"`
+ FoldersSort []string `ini:"folders-sort" delim:","`
+ AddressBookCmd string `ini:"address-book-cmd"`
+ SendAsUTC bool `ini:"send-as-utc" default:"false"`
+ LocalizedRe *regexp.Regexp `ini:"subject-re-pattern" default:"(?i)^((AW|RE|SV|VS|ODP|R): ?)+"`
// CheckMail
CheckMail time.Duration `ini:"check-mail"`
CheckMailCmd string `ini:"check-mail-cmd"`
- CheckMailTimeout time.Duration `ini:"check-mail-timeout"`
+ CheckMailTimeout time.Duration `ini:"check-mail-timeout" default:"10s"`
CheckMailInclude []string `ini:"check-mail-include"`
CheckMailExclude []string `ini:"check-mail-exclude"`
@@ -101,7 +102,7 @@ type AccountConfig struct {
PgpKeyId string `ini:"pgp-key-id"`
PgpAutoSign bool `ini:"pgp-auto-sign"`
PgpOpportunisticEncrypt bool `ini:"pgp-opportunistic-encrypt"`
- PgpErrorLevel int `ini:"-"`
+ PgpErrorLevel int `ini:"pgp-error-level" parse:"ParsePgpErrorLevel" default:"warn"`
// AuthRes
TrustedAuthRes []string `ini:"trusted-authres" delim:","`
@@ -130,7 +131,6 @@ func parseAccounts(root string, accts []string) error {
// No config triggers account configuration wizard
return nil
}
- file.NameMapper = mapName
for _, _sec := range file.SectionStrings() {
if _sec == "DEFAULT" {
@@ -140,88 +140,27 @@ func parseAccounts(root string, accts []string) error {
continue
}
sec := file.Section(_sec)
- sourceRemoteConfig := RemoteConfig{}
account := AccountConfig{
- Archive: "Archive",
- Default: "INBOX",
- Postpone: "Drafts",
- Name: _sec,
- Params: make(map[string]string),
- EnableFoldersSort: true,
- CheckMailTimeout: 10 * time.Second,
- PgpErrorLevel: PgpErrorLevelWarn,
- // localizedRe contains a list of known translations for the common Re:
- LocalizedRe: regexp.MustCompile(`(?i)^((AW|RE|SV|VS|ODP|R): ?)+`),
+ Name: _sec,
+ Params: make(map[string]string),
}
- if err = sec.MapTo(&account); err != nil {
+ if err = MapToStruct(sec, &account, true); err != nil {
return err
}
for key, val := range sec.KeysHash() {
- switch key {
- case "source":
- sourceRemoteConfig.Value = val
- case "source-cred-cmd":
- sourceRemoteConfig.PasswordCmd = val
- case "outgoing":
- account.Outgoing.Value = val
- case "outgoing-cred-cmd":
- account.Outgoing.PasswordCmd = val
- case "outgoing-cred-cmd-cache":
- cache, err := strconv.ParseBool(val)
- if err != nil {
- return fmt.Errorf("%s=%s %w", key, val, err)
- }
- account.Outgoing.CacheCmd = cache
- case "from":
- addr, err := mail.ParseAddress(val)
- if err != nil {
- return fmt.Errorf("%s=%s %w", key, val, err)
- }
- account.From = addr
- case "aliases":
- addrs, err := mail.ParseAddressList(val)
- if err != nil {
- return fmt.Errorf("%s=%s %w", key, val, err)
- }
- account.Aliases = addrs
- case "subject-re-pattern":
- re, err := regexp.Compile(val)
- if err != nil {
- return fmt.Errorf("%s=%s %w", key, val, err)
- }
- account.LocalizedRe = re
- case "pgp-error-level":
- switch strings.ToLower(val) {
- case "none":
- account.PgpErrorLevel = PgpErrorLevelNone
- case "warn":
- account.PgpErrorLevel = PgpErrorLevelWarn
- case "error":
- account.PgpErrorLevel = PgpErrorLevelError
- default:
- return fmt.Errorf("unknown pgp-error-level: %s", val)
- }
- default:
- backendSpecific := true
- typ := reflect.TypeOf(account)
- for i := 0; i < typ.NumField(); i++ {
- field := typ.Field(i)
- if field.Tag.Get("ini") == key {
- backendSpecific = false
- break
- }
- }
- if backendSpecific {
- account.Params[key] = val
+ backendSpecific := true
+ typ := reflect.TypeOf(account)
+ for i := 0; i < typ.NumField(); i++ {
+ field := typ.Field(i)
+ if field.Tag.Get("ini") == key {
+ backendSpecific = false
+ break
}
}
+ if backendSpecific {
+ account.Params[key] = val
+ }
}
- source, err := sourceRemoteConfig.ConnectionString()
- if err != nil {
- return fmt.Errorf("Invalid source credentials for %s: %w", _sec, err)
- }
- account.Source = source
-
if account.Source == "" {
return fmt.Errorf("Expected source for account %s", _sec)
}
@@ -229,11 +168,6 @@ func parseAccounts(root string, accts []string) error {
return fmt.Errorf("Expected from for account %s", _sec)
}
- _, err = account.Outgoing.parseValue()
- if err != nil {
- return fmt.Errorf("Invalid outgoing credentials for %s: %w", _sec, err)
- }
-
log.Debugf("accounts.conf: [%s] from = %s", account.Name, account.From)
Accounts = append(Accounts, &account)
}
@@ -251,6 +185,48 @@ func parseAccounts(root string, accts []string) error {
return nil
}
+func (a *AccountConfig) ParseSource(sec *ini.Section, key *ini.Key) (string, error) {
+ var remote RemoteConfig
+ remote.Value = key.String()
+ if k, err := sec.GetKey("source-cred-cmd"); err == nil {
+ remote.PasswordCmd = k.String()
+ }
+ return remote.ConnectionString()
+}
+
+func (a *AccountConfig) ParseOutgoing(sec *ini.Section, key *ini.Key) (RemoteConfig, error) {
+ var remote RemoteConfig
+ remote.Value = key.String()
+ if k, err := sec.GetKey("outgoing-cred-cmd"); err == nil {
+ remote.PasswordCmd = k.String()
+ }
+ if k, err := sec.GetKey("outgoing-cred-cmd-cache"); err == nil {
+ cache, err := k.Bool()
+ if err != nil {
+ return remote, err
+ }
+ remote.CacheCmd = cache
+ }
+ _, err := remote.parseValue()
+ return remote, err
+}
+
+func (a *AccountConfig) ParsePgpErrorLevel(sec *ini.Section, key *ini.Key) (int, error) {
+ var level int
+ var err error
+ switch strings.ToLower(key.String()) {
+ case "none":
+ level = PgpErrorLevelNone
+ case "warn":
+ level = PgpErrorLevelWarn
+ case "error":
+ level = PgpErrorLevelError
+ default:
+ err = fmt.Errorf("unknown level: %s", key.String())
+ }
+ return level, err
+}
+
// checkConfigPerms checks for too open permissions
// printing the fix on stdout and returning an error
func checkConfigPerms(filename string) error {