aboutsummaryrefslogtreecommitdiffstats
path: root/config/ui.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/ui.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/ui.go')
-rw-r--r--config/ui.go261
1 files changed, 56 insertions, 205 deletions
diff --git a/config/ui.go b/config/ui.go
index 64469643..f52cf7f8 100644
--- a/config/ui.go
+++ b/config/ui.go
@@ -17,17 +17,14 @@ import (
)
type UIConfig struct {
- IndexColumns []*ColumnDef `ini:"-"`
- ColumnSeparator string `ini:"column-separator"`
- // deprecated
- IndexFormat string `ini:"index-format"`
+ IndexColumns []*ColumnDef `ini:"index-columns" parse:"ParseIndexColumns" default:"date<20,name<17,flags>4,subject<*"`
+ ColumnSeparator string `ini:"column-separator" default:" "`
- DirListFormat string `ini:"dirlist-format"` // deprecated
- DirListLeft *template.Template `ini:"-"`
- DirListRight *template.Template `ini:"-"`
+ DirListLeft *template.Template `ini:"dirlist-left" default:"{{.Folder}}"`
+ DirListRight *template.Template `ini:"dirlist-right" default:"{{if .Unread}}{{humanReadable .Unread}}/{{end}}{{if .Exists}}{{humanReadable .Exists}}{{end}}"`
- AutoMarkRead bool `ini:"auto-mark-read"`
- TimestampFormat string `ini:"timestamp-format"`
+ AutoMarkRead bool `ini:"auto-mark-read" default:"true"`
+ TimestampFormat string `ini:"timestamp-format" default:"2006-01-02 03:04 PM"`
ThisDayTimeFormat string `ini:"this-day-time-format"`
ThisWeekTimeFormat string `ini:"this-week-time-format"`
ThisYearTimeFormat string `ini:"this-year-time-format"`
@@ -35,49 +32,48 @@ type UIConfig struct {
MessageViewThisDayTimeFormat string `ini:"message-view-this-day-time-format"`
MessageViewThisWeekTimeFormat string `ini:"message-view-this-week-time-format"`
MessageViewThisYearTimeFormat string `ini:"message-view-this-year-time-format"`
- PinnedTabMarker string `ini:"pinned-tab-marker"`
- SidebarWidth int `ini:"sidebar-width"`
- PreviewHeight int `ini:"preview-height"`
- EmptyMessage string `ini:"empty-message"`
- EmptyDirlist string `ini:"empty-dirlist"`
+ PinnedTabMarker string "ini:\"pinned-tab-marker\" default:\"`\""
+ SidebarWidth int `ini:"sidebar-width" default:"20"`
+ EmptyMessage string `ini:"empty-message" default:"(no messages)"`
+ EmptyDirlist string `ini:"empty-dirlist" default:"(no folders)"`
MouseEnabled bool `ini:"mouse-enabled"`
ThreadingEnabled bool `ini:"threading-enabled"`
ForceClientThreads bool `ini:"force-client-threads"`
- ClientThreadsDelay time.Duration `ini:"client-threads-delay"`
+ ClientThreadsDelay time.Duration `ini:"client-threads-delay" default:"50ms"`
FuzzyComplete bool `ini:"fuzzy-complete"`
- NewMessageBell bool `ini:"new-message-bell"`
- Spinner string `ini:"spinner"`
- SpinnerDelimiter string `ini:"spinner-delimiter"`
- SpinnerInterval time.Duration `ini:"spinner-interval"`
+ NewMessageBell bool `ini:"new-message-bell" default:"true"`
+ Spinner string `ini:"spinner" default:"[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] "`
+ SpinnerDelimiter string `ini:"spinner-delimiter" default:","`
+ SpinnerInterval time.Duration `ini:"spinner-interval" default:"200ms"`
IconUnencrypted string `ini:"icon-unencrypted"`
- IconEncrypted string `ini:"icon-encrypted"`
- IconSigned string `ini:"icon-signed"`
+ IconEncrypted string `ini:"icon-encrypted" default:"[e]"`
+ IconSigned string `ini:"icon-signed" default:"[s]"`
IconSignedEncrypted string `ini:"icon-signed-encrypted"`
- IconUnknown string `ini:"icon-unknown"`
- IconInvalid string `ini:"icon-invalid"`
- IconAttachment string `ini:"icon-attachment"`
- DirListDelay time.Duration `ini:"dirlist-delay"`
+ IconUnknown string `ini:"icon-unknown" default:"[s?]"`
+ IconInvalid string `ini:"icon-invalid" default:"[s!]"`
+ IconAttachment string `ini:"icon-attachment" default:"a"`
+ DirListDelay time.Duration `ini:"dirlist-delay" default:"200ms"`
DirListTree bool `ini:"dirlist-tree"`
DirListCollapse int `ini:"dirlist-collapse"`
- Sort []string `delim:" "`
+ Sort []string `ini:"sort" delim:" "`
NextMessageOnDelete bool `ini:"next-message-on-delete"`
- CompletionDelay time.Duration `ini:"completion-delay"`
- CompletionMinChars int `ini:"completion-min-chars"`
- CompletionPopovers bool `ini:"completion-popovers"`
+ CompletionDelay time.Duration `ini:"completion-delay" default:"250ms"`
+ CompletionMinChars int `ini:"completion-min-chars" default:"1"`
+ CompletionPopovers bool `ini:"completion-popovers" default:"true"`
StyleSetDirs []string `ini:"stylesets-dirs" delim:":"`
- StyleSetName string `ini:"styleset-name"`
+ StyleSetName string `ini:"styleset-name" default:"default"`
style StyleSet
// customize border appearance
- BorderCharVertical rune `ini:"-"`
- BorderCharHorizontal rune `ini:"-"`
+ BorderCharVertical rune `ini:"border-char-vertical" default:" " type:"rune"`
+ BorderCharHorizontal rune `ini:"border-char-horizontal" default:" " type:"rune"`
ReverseOrder bool `ini:"reverse-msglist-order"`
ReverseThreadOrder bool `ini:"reverse-thread-order"`
SortThreadSiblings bool `ini:"sort-thread-siblings"`
// Tab Templates
- TabTitleAccount *template.Template `ini:"-"`
- TabTitleComposer *template.Template `ini:"-"`
+ TabTitleAccount *template.Template `ini:"tab-title-account" default:"{{.Account}}"`
+ TabTitleComposer *template.Template `ini:"tab-title-composer" default:"{{.Subject}}"`
// private
contextualUis []*UiConfigContext
@@ -106,95 +102,14 @@ type uiContextKey struct {
const unreadExists string = `{{if .Unread}}{{humanReadable .Unread}}/{{end}}{{if .Exists}}{{humanReadable .Exists}}{{end}}`
-func defaultUiConfig() *UIConfig {
- date, _ := templates.ParseTemplate("column-date", "{{.DateAutoFormat .Date.Local}}")
- name, _ := templates.ParseTemplate("column-name", "{{index (.From | names) 0}}")
- flags, _ := templates.ParseTemplate("column-flags", `{{.Flags | join ""}}`)
- subject, _ := templates.ParseTemplate("column-subject", "{{.Subject}}")
- left, _ := templates.ParseTemplate("folder", "{{.Folder}}")
- right, _ := templates.ParseTemplate("ue", unreadExists)
- tabTitleAccount, _ := templates.ParseTemplate("tab-title-account", "{{.Account}}")
- tabTitleComposer, _ := templates.ParseTemplate("tab-title-composer", "{{.Subject}}")
- return &UIConfig{
- IndexFormat: "", // deprecated
- DirListFormat: "", // deprecated
- IndexColumns: []*ColumnDef{
- {
- Name: "date",
- Width: 20,
- Flags: ALIGN_LEFT | WIDTH_EXACT,
- Template: date,
- },
- {
- Name: "name",
- Width: 17,
- Flags: ALIGN_LEFT | WIDTH_EXACT,
- Template: name,
- },
- {
- Name: "flags",
- Width: 4,
- Flags: ALIGN_RIGHT | WIDTH_EXACT,
- Template: flags,
- },
- {
- Name: "subject",
- Flags: ALIGN_LEFT | WIDTH_AUTO,
- Template: subject,
- },
- },
- DirListLeft: left,
- DirListRight: right,
- ColumnSeparator: " ",
- AutoMarkRead: true,
- TimestampFormat: "2006-01-02 03:04 PM",
- ThisDayTimeFormat: "",
- ThisWeekTimeFormat: "",
- ThisYearTimeFormat: "",
- PinnedTabMarker: "`",
- SidebarWidth: 20,
- PreviewHeight: 12,
- EmptyMessage: "(no messages)",
- EmptyDirlist: "(no folders)",
- MouseEnabled: false,
- ClientThreadsDelay: 50 * time.Millisecond,
- NewMessageBell: true,
- TabTitleAccount: tabTitleAccount,
- TabTitleComposer: tabTitleComposer,
- FuzzyComplete: false,
- Spinner: "[..] , [..] , [..] , [..] , [..], [..] , [..] , [..] ",
- SpinnerDelimiter: ",",
- SpinnerInterval: 200 * time.Millisecond,
- IconUnencrypted: "",
- IconSigned: "[s]",
- IconEncrypted: "[e]",
- IconSignedEncrypted: "",
- IconUnknown: "[s?]",
- IconInvalid: "[s!]",
- IconAttachment: "a",
- DirListDelay: 200 * time.Millisecond,
- NextMessageOnDelete: true,
- CompletionDelay: 250 * time.Millisecond,
- CompletionMinChars: 1,
- CompletionPopovers: true,
- StyleSetDirs: []string{},
- StyleSetName: "default",
- // border defaults
- BorderCharVertical: ' ',
- BorderCharHorizontal: ' ',
- // private
- contextualCache: make(map[uiContextKey]*UIConfig),
- contextualCounts: make(map[uiContextType]int),
- }
+var Ui = &UIConfig{
+ contextualCounts: make(map[uiContextType]int),
+ contextualCache: make(map[uiContextKey]*UIConfig),
}
-var Ui = defaultUiConfig()
-
func parseUi(file *ini.File) error {
- if ui, err := file.GetSection("ui"); err == nil {
- if err := Ui.parse(ui); err != nil {
- return err
- }
+ if err := Ui.parse(file.Section("ui")); err != nil {
+ return err
}
for _, sectionName := range file.SectionStrings() {
@@ -283,64 +198,16 @@ func parseUi(file *ini.File) error {
}
func (config *UIConfig) parse(section *ini.Section) error {
- if err := section.MapTo(config); err != nil {
+ if err := MapToStruct(section, config, section.Name() == "ui"); err != nil {
return err
}
- if key, err := section.GetKey("border-char-vertical"); err == nil {
- chars := []rune(key.String())
- if len(chars) != 1 {
- return fmt.Errorf("%v must be one and only one character", key)
- }
- config.BorderCharVertical = chars[0]
- }
- if key, err := section.GetKey("border-char-horizontal"); err == nil {
- chars := []rune(key.String())
- if len(chars) != 1 {
- return fmt.Errorf("%v must be one and only one character", key)
- }
- config.BorderCharHorizontal = chars[0]
- }
-
- // Values with type=time.Duration must be explicitly set. If these
- // values are given a default in the struct passed to ui.MapTo, which
- // they are, a zero-value in the config won't overwrite the default.
- if key, err := section.GetKey("dirlist-delay"); err == nil {
- dur, err := key.Duration()
- if err != nil {
- return err
- }
- config.DirListDelay = dur
- }
- if key, err := section.GetKey("completion-delay"); err == nil {
- dur, err := key.Duration()
- if err != nil {
- return err
- }
- config.CompletionDelay = dur
- }
-
if config.MessageViewTimestampFormat == "" {
config.MessageViewTimestampFormat = config.TimestampFormat
}
- if config.MessageViewThisDayTimeFormat == "" {
- config.MessageViewThisDayTimeFormat = config.TimestampFormat
- }
- if config.MessageViewThisWeekTimeFormat == "" {
- config.MessageViewThisWeekTimeFormat = config.TimestampFormat
- }
- if config.MessageViewThisDayTimeFormat == "" {
- config.MessageViewThisDayTimeFormat = config.TimestampFormat
- }
- if key, err := section.GetKey("index-columns"); err == nil {
- columns, err := ParseColumnDefs(key, section)
- if err != nil {
- return err
- }
- config.IndexColumns = columns
- } else if config.IndexFormat != "" {
- columns, err := convertIndexFormat(config.IndexFormat)
+ if key, err := section.GetKey("index-format"); err == nil {
+ columns, err := convertIndexFormat(key.String())
if err != nil {
return err
}
@@ -366,24 +233,8 @@ index-format will be removed in aerc 0.17.
}
Warnings = append(Warnings, w)
}
- left, _ := section.GetKey("dirlist-left")
- if left != nil {
- t, err := templates.ParseTemplate(left.String(), left.String())
- if err != nil {
- return err
- }
- config.DirListLeft = t
- }
- right, _ := section.GetKey("dirlist-right")
- if right != nil {
- t, err := templates.ParseTemplate(right.String(), right.String())
- if err != nil {
- return err
- }
- config.DirListRight = t
- }
- if left == nil && right == nil && config.DirListFormat != "" {
- left, right := convertDirlistFormat(config.DirListFormat)
+ if key, err := section.GetKey("dirlist-format"); err == nil {
+ left, right := convertDirlistFormat(key.String())
l, err := templates.ParseTemplate(left, left)
if err != nil {
return err
@@ -418,26 +269,26 @@ dirlist-format will be removed in aerc 0.17.
}
Warnings = append(Warnings, w)
}
- if key, err := section.GetKey("tab-title-account"); err == nil {
- val := key.Value()
- tmpl, err := templates.ParseTemplate("tab-title-account", val)
- if err != nil {
- return err
- }
- config.TabTitleAccount = tmpl
- }
- if key, err := section.GetKey("tab-title-composer"); err == nil {
- val := key.Value()
- tmpl, err := templates.ParseTemplate("tab-title-composer", val)
- if err != nil {
- return err
- }
- config.TabTitleComposer = tmpl
- }
return nil
}
+func (*UIConfig) ParseIndexColumns(section *ini.Section, key *ini.Key) ([]*ColumnDef, error) {
+ if !section.HasKey("column-date") {
+ _, _ = section.NewKey("column-date", `{{.DateAutoFormat .Date.Local}}`)
+ }
+ if !section.HasKey("column-name") {
+ _, _ = section.NewKey("column-name", `{{index (.From | names) 0}}`)
+ }
+ if !section.HasKey("column-flags") {
+ _, _ = section.NewKey("column-flags", `{{.Flags | join ""}}`)
+ }
+ if !section.HasKey("column-subject") {
+ _, _ = section.NewKey("column-subject", `{{.Subject}}`)
+ }
+ return ParseColumnDefs(key, section)
+}
+
var indexFmtRegexp = regexp.MustCompile(`%(-?\d+)?(\.\d+)?([ACDFRTZadfgilnrstuv])`)
func convertIndexFormat(indexFormat string) ([]*ColumnDef, error) {