summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoritz Poldrack <git@moritz.sh>2023-03-15 22:40:37 +0100
committerRobin Jarry <robin@jarry.cc>2023-04-01 01:01:09 +0200
commit2fef0f4a60c8026c156ad8d75078bccde6a540d8 (patch)
treecff6819cd0950294ee5e7c20b57582c23d628b4e
parent088d63ce934c34e113a5b3154dfcf91b49132067 (diff)
downloadaerc-2fef0f4a60c8026c156ad8d75078bccde6a540d8.tar.gz
aerc-2fef0f4a60c8026c156ad8d75078bccde6a540d8.zip
config: replace triggers with hooks
Deprecate triggers and replace them with hooks. Now that aerc supports running arbitrary ex commands over IPC, it is possible to run internal aerc commands *and* shell commands via external shell scripts. Hooks only allow running shell commands. Hooks info is passed via environment variables. Implements: https://todo.sr.ht/~rjarry/aerc/136 Signed-off-by: Moritz Poldrack <git@moritz.sh> Signed-off-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
-rw-r--r--CHANGELOG.md4
-rw-r--r--config/aerc.conf9
-rw-r--r--config/config.go2
-rw-r--r--config/hooks.go62
-rw-r--r--config/triggers.go61
-rw-r--r--doc/aerc-config.5.scd27
-rw-r--r--lib/hooks/exec.go22
-rw-r--r--lib/hooks/interface.go6
-rw-r--r--lib/hooks/mail-received.go25
-rw-r--r--lib/msgstore.go2
-rw-r--r--widgets/account.go13
11 files changed, 143 insertions, 90 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf3c7067..4d5b15b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
`aerc.conf`.
- Allow basic shell globbing in `[openers]` MIME types.
- Dynamic `msglist_*` styling based on email header values in stylesets.
+- Add `mail-received` hook.
### Changed
@@ -38,11 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Deprecated
- `[ui].index-format` setting has been replaced by `index-columns`.
-- `[triggers].new-email` now needs to use `aerc-templates(7)` syntax instead
- of the (now deprecated) `index-format` placeholders.
- `[statusline].render-format` has been replaced by `status-columns`.
- Removed support for go < 1.17.
- Removed support for `[ui:subject...]` contextual sections in `aerc.conf`.
+- `[triggers]` setting has been replaced by `[hooks]`.
## [0.14.0](https://git.sr.ht/~rjarry/aerc/refs/0.14.0) - 2023-01-04
diff --git a/config/aerc.conf b/config/aerc.conf
index 02ea96fe..5ff252f5 100644
--- a/config/aerc.conf
+++ b/config/aerc.conf
@@ -506,16 +506,13 @@ message/rfc822=colorize
# text/plain=gvim {} +125
# message/rfc822=thunderbird
-[triggers]
+[hooks]
#
-# Triggers specify commands to execute when certain events occur.
-#
-# Example:
-# new-email=exec notify-send "New email from %n" "%s"
+# Hooks are triggered whenever the associated event occurs.
#
# Executed when a new email arrives in the selected folder
-#new-email=
+#mail-received=notify-send "New mail from $AERC_FROM_NAME" "$AERC_SUBJECT"
[templates]
# Templates are used to populate email bodies automatically.
diff --git a/config/config.go b/config/config.go
index de8cd0d0..d70bcfe0 100644
--- a/config/config.go
+++ b/config/config.go
@@ -132,7 +132,7 @@ func LoadConfigFromFile(root *string, accts []string) error {
if err := parseOpeners(file); err != nil {
return err
}
- if err := parseTriggers(file); err != nil {
+ if err := parseHooks(file); err != nil {
return err
}
if err := parseUi(file); err != nil {
diff --git a/config/hooks.go b/config/hooks.go
new file mode 100644
index 00000000..9bbd9031
--- /dev/null
+++ b/config/hooks.go
@@ -0,0 +1,62 @@
+package config
+
+import (
+ "strings"
+
+ "git.sr.ht/~rjarry/aerc/log"
+ "github.com/go-ini/ini"
+)
+
+type HooksConfig struct {
+ MailReceived string `ini:"mail-received"`
+}
+
+var Hooks HooksConfig
+
+func parseHooks(file *ini.File) error {
+ err := MapToStruct(file.Section("hooks"), &Hooks, true)
+ if err != nil {
+ return err
+ }
+
+ newEmail := file.Section("triggers").Key("new-email").String()
+ if Hooks.MailReceived == "" && newEmail != "" {
+ Hooks.MailReceived = convertNewEmailTrigger(newEmail)
+ Warnings = append(Warnings, Warning{
+ Title: "DEPRECATION NOTICE: [triggers].new-email",
+ Body: `
+The new-email trigger has been replaced by [hooks].email-received.
+
+Your configuration in this instance was automatically converted to:
+
+[hooks]
+mail-received = ` + Hooks.MailReceived + `
+
+Please verify the accuracy of the above translation.
+
+Your configuration file was not changed. To make this change permanent and to
+dismiss this deprecation warning on launch, copy the above lines into aerc.conf
+and remove new-email from it. See aerc-config(5) for more details.
+`,
+ })
+ }
+
+ log.Debugf("aerc.conf: [hooks] %#v", Hooks)
+ return nil
+}
+
+func convertNewEmailTrigger(old string) string {
+ translations := map[string]string{
+ "%a": "$AERC_FROM_ADDRESS",
+ "%n": "$AERC_FROM_NAME",
+ "%s": "$AERC_SUBJECT",
+ "%f": "$AERC_FROM_NAME <$AERC_FROM_ADDRESS>",
+ "%u": `$(echo "$AERC_FROM_ADDRESS" | cut -d@ -f1)`,
+ "%v": `$(echo "$AERC_FROM_NAME" | cut -d' ' -f1)`,
+ }
+ for replace, with := range translations {
+ old = strings.ReplaceAll(old, replace, with)
+ }
+ old = strings.TrimPrefix(old, "exec ")
+ return strings.ReplaceAll(old, "%%", "%")
+}
diff --git a/config/triggers.go b/config/triggers.go
deleted file mode 100644
index 82750c2d..00000000
--- a/config/triggers.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package config
-
-import (
- "github.com/go-ini/ini"
- "github.com/google/shlex"
-
- "git.sr.ht/~rjarry/aerc/lib/format"
- "git.sr.ht/~rjarry/aerc/log"
-)
-
-type TriggersConfig struct {
- NewEmail []string `ini:"new-email" parse:"ParseNewEmail"`
-}
-
-var Triggers = new(TriggersConfig)
-
-func parseTriggers(file *ini.File) error {
- if err := MapToStruct(file.Section("triggers"), Triggers, true); err != nil {
- return err
- }
- log.Debugf("aerc.conf: [triggers] %#v", Triggers)
- return nil
-}
-
-func (t *TriggersConfig) ParseNewEmail(_ *ini.Section, key *ini.Key) ([]string, error) {
- cmd := indexFmtRegexp.ReplaceAllStringFunc(
- key.String(),
- func(s string) string {
- runes := []rune(s)
- t, _ := indexVerbToTemplate(runes[len(runes)-1])
- return t
- },
- )
- args, err := shlex.Split(cmd)
- if err != nil {
- return nil, err
- }
- if cmd != key.String() {
- log.Warnf("%s %s",
- "The new-email trigger now uses templates instead of %-based placeholders.",
- "Backward compatibility will be removed in aerc 0.17.")
- Warnings = append(Warnings, Warning{
- Title: "FORMAT CHANGED: [triggers].new-email",
- Body: `
-The new-email trigger now uses templates instead of %-based placeholders.
-
-Your configuration in this instance was automatically converted to:
-
-[triggers]
-new-email = ` + format.ShellQuote(args) + `
-
-Your configuration file was not changed. To make this change permanent and to
-dismiss this warning on launch, replace the above line into aerc.conf. See
-aerc-config(5) for more details.
-
-The automatic conversion of new-email will be removed in aerc 0.17.
-`,
- })
- }
- return args, nil
-}
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index 54c7df7f..9e11abfe 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -819,23 +819,26 @@ text/plain=gvim {} +125
message/rfc822=thunderbird
```
-# TRIGGERS
+## HOOKS
-Triggers specify commands to execute when certain events occur. They are
-configured in the *[triggers]* section of _aerc.conf_.
+Hooks are triggered whenever the associated event occurs. The commands are run
+in a shell environment with information added to environment variables.
-The commands are not shell commands (i.e. they are not executed with _sh -c_)
-and will be split in multiple arguments following basic shell quoting. They need
-to use one of the commands described in *aerc*(1) without the leading colon *:*
-(e.g. _exec foo bar_ instead of _:exec foo bar_).
+They are configured in the *[hooks]* section of aerc.conf.
-*new-email* = _<command>_
- Executed when a new email arrives in the selected folder. Example:
+*mail-received* = _<command>_
+ Executed when new mail is received in the selected folder. This will
+ only work reliably with maildir and some imap servers.
- exec notify-send 'New email from {{.From | names | join ", "}}' '{{.Subject}}'
+ Variables:
- Templates specifiers from *aerc-templates*(7) are expanded with respect
- to the new message.
+ - *AERC_FROM_NAME*
+ - *AERC_FROM_ADDRESS*
+ - *AERC_SUBJECT*
+
+ Example:
+
+ *mail-received* = _notify-send "New mail from $AERC_FROM_NAME" "$AERC_SUBJECT"_
# TEMPLATES
diff --git a/lib/hooks/exec.go b/lib/hooks/exec.go
new file mode 100644
index 00000000..bea35f32
--- /dev/null
+++ b/lib/hooks/exec.go
@@ -0,0 +1,22 @@
+package hooks
+
+import (
+ "os"
+ "os/exec"
+
+ "git.sr.ht/~rjarry/aerc/log"
+)
+
+func RunHook(h HookType) error {
+ cmd := h.Cmd()
+ if cmd == "" {
+ return nil
+ }
+ env := h.Env()
+ log.Debugf("hooks: running command %q (env %v)", cmd, env)
+
+ proc := exec.Command("sh", "-c", cmd)
+ proc.Env = os.Environ()
+ proc.Env = append(proc.Env, env...)
+ return proc.Run()
+}
diff --git a/lib/hooks/interface.go b/lib/hooks/interface.go
new file mode 100644
index 00000000..ed38c3ac
--- /dev/null
+++ b/lib/hooks/interface.go
@@ -0,0 +1,6 @@
+package hooks
+
+type HookType interface {
+ Cmd() string
+ Env() []string
+}
diff --git a/lib/hooks/mail-received.go b/lib/hooks/mail-received.go
new file mode 100644
index 00000000..66622e9e
--- /dev/null
+++ b/lib/hooks/mail-received.go
@@ -0,0 +1,25 @@
+package hooks
+
+import (
+ "fmt"
+
+ "git.sr.ht/~rjarry/aerc/config"
+ "git.sr.ht/~rjarry/aerc/models"
+)
+
+type MailReceived struct {
+ MsgInfo *models.MessageInfo
+}
+
+func (m *MailReceived) Cmd() string {
+ return config.Hooks.MailReceived
+}
+
+func (m *MailReceived) Env() []string {
+ from := m.MsgInfo.Envelope.From[0]
+ return []string{
+ fmt.Sprintf("AERC_FROM_NAME=%s", from.Name),
+ fmt.Sprintf("AERC_FROM_ADDRESS=%s", from.Address),
+ fmt.Sprintf("AERC_SUBJECT=%s", m.MsgInfo.Envelope.Subject),
+ }
+}
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 38fa82e6..5349aa62 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -271,7 +271,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
}
seen := msg.Info.Flags.Has(models.SeenFlag)
recent := msg.Info.Flags.Has(models.RecentFlag)
- if !seen && recent {
+ if !seen && recent && msg.Info.Envelope != nil {
store.triggerNewEmail(msg.Info)
}
if _, ok := store.pendingHeaders[msg.Info.Uid]; msg.Info.Envelope != nil && ok {
diff --git a/widgets/account.go b/widgets/account.go
index fdd33301..fece0846 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -11,6 +11,7 @@ import (
"git.sr.ht/~rjarry/aerc/config"
"git.sr.ht/~rjarry/aerc/lib"
+ "git.sr.ht/~rjarry/aerc/lib/hooks"
"git.sr.ht/~rjarry/aerc/lib/marker"
"git.sr.ht/~rjarry/aerc/lib/sort"
"git.sr.ht/~rjarry/aerc/lib/state"
@@ -288,14 +289,12 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
acct.dirlist.UiConfig(name).ReverseThreadOrder,
acct.dirlist.UiConfig(name).SortThreadSiblings,
func(msg *models.MessageInfo) {
- if len(config.Triggers.NewEmail) == 0 {
- return
- }
- err := acct.aerc.cmd(
- config.Triggers.NewEmail,
- acct.acct, msg)
+ err := hooks.RunHook(&hooks.MailReceived{
+ MsgInfo: msg,
+ })
if err != nil {
- acct.aerc.PushError(err.Error())
+ msg := fmt.Sprintf("mail-received hook: %s", err)
+ acct.aerc.PushError(msg)
}
}, func() {
if acct.dirlist.UiConfig(name).NewMessageBell {