aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKoni Marti <koni.marti@gmail.com>2023-07-14 20:14:37 +0200
committerRobin Jarry <robin@jarry.cc>2023-07-14 23:11:42 +0200
commita791da7ba45d44b5e9d66d67558fc725977b7345 (patch)
treed79d4c862a0798397a7c1cd84fd2e1a11774d408
parent21cc3ce129b716f14b229b34dde64026b1f7549f (diff)
downloadaerc-a791da7ba45d44b5e9d66d67558fc725977b7345.tar.gz
aerc-a791da7ba45d44b5e9d66d67558fc725977b7345.zip
foldermap: make folder-map syntax more expressive
Improve the folder mapping syntax so that prefixes can be removed completely. The following line in the folder-map file * = INBOX/* will strip the INBOX/ prefix from all subfolders of INBOX, e.g. map "INBOX/Project1" to "Project1". To prevent a key collision with multiple "*" keys (the folder mapping is stored as a map internally), a group of "*" will be condensed to one "*", e.g. ** = INBOX/* has the same meaning as above. Also, adjust name translation for folder creation and add tests. Fixes: https://todo.sr.ht/~rjarry/aerc/176 Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc> Tested-by: Bence Ferdinandy <bence@ferdinandy.com>
-rw-r--r--doc/aerc-accounts.5.scd5
-rw-r--r--worker/middleware/foldermapper.go45
-rw-r--r--worker/middleware/foldermapper_test.go103
3 files changed, 142 insertions, 11 deletions
diff --git a/doc/aerc-accounts.5.scd b/doc/aerc-accounts.5.scd
index 0cf7071b..6fe558cd 100644
--- a/doc/aerc-accounts.5.scd
+++ b/doc/aerc-accounts.5.scd
@@ -105,6 +105,11 @@ Note that many of these configuration options are written for you, such as
G = [Gmail]\*
```
+ Remove a prefix for all subfolders:
+ ```
+ * = [Gmail]/\*
+ ```
+
Remap all subfolders and avoid a folder collision:
```
Archive/existing = Archive\*
diff --git a/worker/middleware/foldermapper.go b/worker/middleware/foldermapper.go
index c78833a2..ee57b9c8 100644
--- a/worker/middleware/foldermapper.go
+++ b/worker/middleware/foldermapper.go
@@ -1,6 +1,7 @@
package middleware
import (
+ "fmt"
"strings"
"sync"
@@ -28,7 +29,10 @@ func NewFolderMapper(base types.WorkerInteractor, mapping map[string]string,
func (f *folderMapper) incoming(msg types.WorkerMessage, dir string) string {
f.Lock()
defer f.Unlock()
- mapped := f.table[dir]
+ mapped, ok := f.table[dir]
+ if !ok {
+ return dir
+ }
return mapped
}
@@ -52,13 +56,16 @@ func (f *folderMapper) store(s string) {
f.Tracef("store display folder '%s' to '%s'", display, s)
}
-func (f *folderMapper) create(s string) string {
+func (f *folderMapper) create(s string) (string, error) {
f.Lock()
defer f.Unlock()
- backend := f.fm.Create(s)
+ backend := createFolder(f.table, s)
+ if _, exists := f.table[s]; exists {
+ return s, fmt.Errorf("folder already exists: %s", s)
+ }
f.table[s] = backend
f.Tracef("create display folder '%s' as '%s'", s, backend)
- return backend
+ return backend, nil
}
func (f *folderMapper) ProcessAction(msg types.WorkerMessage) types.WorkerMessage {
@@ -74,7 +81,11 @@ func (f *folderMapper) ProcessAction(msg types.WorkerMessage) types.WorkerMessag
case *types.MoveMessages:
msg.Destination = f.incoming(msg, msg.Destination)
case *types.CreateDirectory:
- msg.Directory = f.create(msg.Directory)
+ var err error
+ msg.Directory, err = f.create(msg.Directory)
+ if err != nil {
+ f.Errorf("error creating new directory: %v", err)
+ }
case *types.RemoveDirectory:
msg.Directory = f.incoming(msg, msg.Directory)
case *types.OpenDirectory:
@@ -105,6 +116,10 @@ func (f *folderMapper) PostMessage(msg types.WorkerMessage, cb func(m types.Work
case *types.OpenDirectory:
msg.Directory = f.outgoing(msg, msg.Directory)
}
+ case *types.CheckMailDirectories:
+ for i := range msg.Directories {
+ msg.Directories[i] = f.outgoing(msg, msg.Directories[i])
+ }
case *types.Directory:
f.store(msg.Dir.Name)
msg.Dir.Name = f.outgoing(msg, msg.Dir.Name)
@@ -130,23 +145,31 @@ func (f *folderMap) Apply(s string) string {
strict = false
}
if (strings.HasPrefix(s, v) && !strict) || (s == v && strict) {
- s = k + strings.TrimPrefix(s, v)
+ term := strings.TrimPrefix(s, v)
+ if strings.Contains(k, "*") && !strict {
+ prefix := k
+ for strings.Contains(prefix, "**") {
+ prefix = strings.ReplaceAll(prefix, "**", "*")
+ }
+ s = strings.Replace(prefix, "*", term, 1)
+ } else {
+ s = k + term
+ }
}
}
return s
}
-// Create reverses the mapping of a new folder name
-func (f *folderMap) Create(s string) string {
+// createFolder reverses the mapping of a new folder name
+func createFolder(table map[string]string, s string) string {
max, key := 0, ""
- for k := range f.mapping {
+ for k := range table {
if strings.HasPrefix(s, k) && len(k) > max {
max, key = len(k), k
}
}
if max > 0 && key != "" {
- s = strings.TrimSuffix(f.mapping[key], "*") +
- strings.TrimPrefix(s, key)
+ s = table[key] + strings.TrimPrefix(s, key)
}
return s
}
diff --git a/worker/middleware/foldermapper_test.go b/worker/middleware/foldermapper_test.go
new file mode 100644
index 00000000..0b4f6dba
--- /dev/null
+++ b/worker/middleware/foldermapper_test.go
@@ -0,0 +1,103 @@
+package middleware
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestFolderMap_Apply(t *testing.T) {
+ tests := []struct {
+ name string
+ mapping map[string]string
+ order []string
+ input []string
+ want []string
+ }{
+ {
+ name: "strict single folder mapping",
+ mapping: map[string]string{"Drafts": "INBOX/Drafts"},
+ order: []string{"Drafts"},
+ input: []string{"INBOX/Drafts"},
+ want: []string{"Drafts"},
+ },
+ {
+ name: "prefix mapping with * suffix",
+ mapping: map[string]string{"Prefix/": "INBOX/*"},
+ order: []string{"Prefix/"},
+ input: []string{"INBOX", "INBOX/Test1", "INBOX/Test2", "Archive"},
+ want: []string{"INBOX", "Prefix/Test1", "Prefix/Test2", "Archive"},
+ },
+ {
+ name: "remove prefix with * in key",
+ mapping: map[string]string{"*": "INBOX/*"},
+ order: []string{"*"},
+ input: []string{"INBOX", "INBOX/Test1", "INBOX/Test2", "Archive"},
+ want: []string{"INBOX", "Test1", "Test2", "Archive"},
+ },
+ {
+ name: "remove two prefixes with * in keys",
+ mapping: map[string]string{
+ "*": "INBOX/*",
+ "**": "PROJECT/*",
+ },
+ order: []string{"*", "**"},
+ input: []string{"INBOX", "INBOX/Test1", "INBOX/Test2", "Archive", "PROJECT/sub1", "PROJECT/sub2"},
+ want: []string{"INBOX", "Test1", "Test2", "Archive", "sub1", "sub2"},
+ },
+ {
+ name: "multiple, sequential mappings",
+ mapping: map[string]string{
+ "Archive/existing": "Archive*",
+ "Archive": "Archivum*",
+ },
+ order: []string{"Archive/existing", "Archive"},
+ input: []string{"Archive", "Archive/sub", "Archivum", "Archivum/year1"},
+ want: []string{"Archive/existing", "Archive/existing/sub", "Archive", "Archive/year1"},
+ },
+ }
+
+ for i, test := range tests {
+ fm := &folderMap{
+ mapping: test.mapping,
+ order: test.order,
+ }
+ var result []string
+ for _, in := range test.input {
+ result = append(result, fm.Apply(in))
+ }
+ if !reflect.DeepEqual(result, test.want) {
+ t.Errorf("test (%d: %s) failed: want '%v' but got '%v'",
+ i, test.name, test.want, result)
+ }
+ }
+}
+
+func TestFolderMap_createFolder(t *testing.T) {
+ tests := []struct {
+ name string
+ table map[string]string
+ input string
+ want string
+ }{
+ {
+ name: "create normal folder",
+ table: map[string]string{"Drafts": "INBOX/Drafts"},
+ input: "INBOX/Drafts2",
+ want: "INBOX/Drafts2",
+ },
+ {
+ name: "create mapped folder",
+ table: map[string]string{"Drafts": "INBOX/Drafts"},
+ input: "Drafts/Sub",
+ want: "INBOX/Drafts/Sub",
+ },
+ }
+
+ for i, test := range tests {
+ result := createFolder(test.table, test.input)
+ if result != test.want {
+ t.Errorf("test (%d: %s) failed: want '%v' but got '%v'",
+ i, test.name, test.want, result)
+ }
+ }
+}