From 5c29e49f9f422c8889ce2ed6b5b5a2914b55cace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20=27Necoro=27=20Neumann?= Date: Sun, 17 Oct 2021 17:04:06 +0200 Subject: Support feed targets per feed --- pkg/config/url.go | 46 ++++++++++++++++++++--- pkg/config/yaml.go | 45 ++++++++++++++++++++--- pkg/config/yaml_test.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 177 insertions(+), 12 deletions(-) (limited to 'pkg') diff --git a/pkg/config/url.go b/pkg/config/url.go index 39cce16..7e91411 100644 --- a/pkg/config/url.go +++ b/pkg/config/url.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/url" + "strings" "gopkg.in/yaml.v3" ) @@ -63,7 +64,7 @@ func (u *Url) UnmarshalYAML(value *yaml.Node) (err error) { return nil } -func (u *Url) String() string { +func (u Url) String() string { scheme := u.Scheme + "://" var pwd string @@ -86,12 +87,28 @@ func (u *Url) HostPort() string { } const ( - imapsPort = "993" - imapPort = "143" - imapsSchema = "imaps" - imapSchema = "imap" + imapsPort = "993" + imapPort = "143" + imapsSchema = "imaps" + imapsSchemaFull = imapsSchema + "://" + imapSchema = "imap" + imapSchemaFull = imapSchema + "://" + maildirSchemaFull = "maildir://" ) +func isRecognizedUrl(s string) bool { + return isImapUrl(s) || isMaildirUrl(s) + +} + +func isImapUrl(s string) bool { + return strings.HasPrefix(s, imapsSchemaFull) || strings.HasPrefix(s, imapSchemaFull) +} + +func isMaildirUrl(s string) bool { + return strings.HasPrefix(s, maildirSchemaFull) +} + func (u *Url) ForceTLS() bool { return u.Scheme == imapsSchema || u.Port == imapsPort } @@ -132,3 +149,22 @@ func (u *Url) validate() (errors []string) { return } + +func (u Url) BaseUrl() Url { + // 'u' is not a pointer and thus a copy, modification is fine + u.Root = "" + return u +} + +func (u *Url) CommonBaseUrl(other Url) bool { + other.Root = "" + return other == u.BaseUrl() +} + +func (u *Url) RootPath() []string { + path := u.Root + if path[0] == '/' { + path = path[1:] + } + return strings.Split(path, "/") +} diff --git a/pkg/config/yaml.go b/pkg/config/yaml.go index 48269ba..57991eb 100644 --- a/pkg/config/yaml.go +++ b/pkg/config/yaml.go @@ -118,7 +118,7 @@ func (cfg *Config) parse(in io.Reader) error { cfg.fixGlobalOptions(parsedCfg.GlobalConfig) - if err := buildFeeds(parsedCfg.Feeds, []string{}, cfg.Feeds, &cfg.FeedOptions, cfg.AutoTarget); err != nil { + if err := buildFeeds(parsedCfg.Feeds, []string{}, cfg.Feeds, &cfg.FeedOptions, cfg.AutoTarget, &cfg.Target); err != nil { return fmt.Errorf("while parsing: %w", err) } @@ -182,12 +182,45 @@ func buildOptions(globalFeedOptions *Options, options Map) (feedOptions Options, } // Fetch the group structure and populate the `targetStr` fields in the feeds -func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, globalFeedOptions *Options, autoTarget bool) error { +func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, + globalFeedOptions *Options, autoTarget bool, globalTarget *Url) error { + for _, f := range cfg { - target := appTarget(target, f.target(autoTarget)) + var fTarget []string + + rawTarget := f.target(autoTarget) + if isRecognizedUrl(rawTarget) { + // this whole block is solely for compatibility with old feed2imap + // there it was common to specify the whole URL for each feed + if isMaildirUrl(rawTarget) { + // old feed2imap supported maildir, we don't + return fmt.Errorf("Line %d: Maildir is not supported.", f.Target.Line) + } + + url := Url{} + if err := url.UnmarshalYAML(&f.Target); err != nil { + return err + } + + if globalTarget.Empty() { + // assign first feed as global url + *globalTarget = url.BaseUrl() + } else if !globalTarget.CommonBaseUrl(url) { + // if we have a url, it must be the same prefix as the global url + return fmt.Errorf("Line %d: Given URL endpoint '%s' does not match previous endpoint '%s'.", + f.Target.Line, + url.BaseUrl(), + globalTarget.BaseUrl()) + } + + fTarget = url.RootPath() // we are given the absolute path, so now appending trickery + } else { + fTarget = appTarget(target, rawTarget) + } + switch { case f.isFeed() && f.isGroup(): - return fmt.Errorf("Entry with targetStr %s is both a Feed and a group", target) + return fmt.Errorf("Entry with targetStr %s is both a Feed and a group", fTarget) case f.isFeed(): name := f.Feed.Name @@ -211,7 +244,7 @@ func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, globalFeedO Url: f.Feed.Url, Exec: f.Feed.Exec, Options: opt, - Target: target, + Target: fTarget, } case f.isGroup(): @@ -221,7 +254,7 @@ func buildFeeds(cfg []configGroupFeed, target []string, feeds Feeds, globalFeedO log.Warnf("Unknown option '%s' for group '%s'. Ignored!", optName, f.Group.Group) } - if err := buildFeeds(f.Group.Feeds, target, feeds, &opt, autoTarget); err != nil { + if err := buildFeeds(f.Group.Feeds, fTarget, feeds, &opt, autoTarget, globalTarget); err != nil { return err } } diff --git a/pkg/config/yaml_test.go b/pkg/config/yaml_test.go index e649175..78a3243 100644 --- a/pkg/config/yaml_test.go +++ b/pkg/config/yaml_test.go @@ -148,6 +148,29 @@ func TestBuildFeeds(tst *testing.T) { "bar": &Feed{Name: "bar", Target: t("moep.bar")}, }, }, + {name: "URL Target", wantErr: false, target: "", + feeds: []configGroupFeed{ + {Target: n("imap://foo.bar:443/INBOX/Feed"), Feed: feed{Name: "muh"}}, + }, + result: Feeds{"muh": &Feed{Name: "muh", Target: t("INBOX.Feed")}}, + }, + {name: "Multiple URL Targets", wantErr: false, target: "", + feeds: []configGroupFeed{ + {Target: n("imap://foo.bar:443/INBOX/Feed"), Feed: feed{Name: "muh"}}, + {Target: n("imap://foo.bar:443/INBOX/Feed2"), Feed: feed{Name: "bar"}}, + }, + result: Feeds{ + "muh": &Feed{Name: "muh", Target: t("INBOX.Feed")}, + "bar": &Feed{Name: "bar", Target: t("INBOX.Feed2")}, + }, + }, + {name: "Mixed URL Targets", wantErr: true, target: "", + feeds: []configGroupFeed{ + {Target: n("imap://foo.bar:443/INBOX/Feed"), Feed: feed{Name: "muh"}}, + {Target: n("imap://other.bar:443/INBOX/Feed"), Feed: feed{Name: "bar"}}, + }, + result: Feeds{}, + }, {name: "Empty Group", wantErr: false, target: "", feeds: []configGroupFeed{ {Group: group{Group: "G1"}}, @@ -206,7 +229,8 @@ func TestBuildFeeds(tst *testing.T) { tst.Run(tt.name, func(tst *testing.T) { var feeds = Feeds{} var opts = Options{} - err := buildFeeds(tt.feeds, t(tt.target), feeds, &opts, !tt.noAutoTarget) + var globalTarget = Url{} + err := buildFeeds(tt.feeds, t(tt.target), feeds, &opts, !tt.noAutoTarget, &globalTarget) if (err != nil) != tt.wantErr { tst.Errorf("buildFeeds() error = %v, wantErr %v", err, tt.wantErr) return @@ -447,3 +471,75 @@ feeds: } } } + +func TestURLFeedWithoutGlobalTarget(tst *testing.T) { + inp := ` +feeds: + - name: Foo + target: imap://foo.bar:443/INBOX/Feed +` + res := Feeds{ + "Foo": &Feed{Name: "Foo", Target: t("INBOX.Feed")}, + } + + c := WithDefault() + c.FeedOptions = Options{} + + if err := c.parse(strings.NewReader(inp)); err != nil { + tst.Error(err) + } else { + if diff := cmp.Diff(res, c.Feeds); diff != "" { + tst.Error(diff) + } + if diff := cmp.Diff("imap://foo.bar:443", c.Target.String()); diff != "" { + tst.Error(diff) + } + } +} + +func TestURLFeedWithGlobalTarget(tst *testing.T) { + inp := ` +target: imaps://foo.bar/INBOX/Feeds +feeds: + - name: Foo + target: imaps://foo.bar:993/Some/Other/Path +` + res := Feeds{ + "Foo": &Feed{Name: "Foo", Target: t("Some.Other.Path")}, + } + + c := WithDefault() + c.FeedOptions = Options{} + + if err := c.parse(strings.NewReader(inp)); err != nil { + tst.Error(err) + } else { + if diff := cmp.Diff(res, c.Feeds); diff != "" { + tst.Error(diff) + } + if diff := cmp.Diff("imaps://foo.bar:993/INBOX/Feeds", c.Target.String()); diff != "" { + tst.Error(diff) + } + } +} + +func TestURLFeedWithDifferentGlobalTarget(tst *testing.T) { + inp := ` +target: imaps://foo.bar/INBOX/Feeds +feeds: + - name: Foo + target: imaps://other.bar/INBOX/Feeds +` + errorText := "while parsing: Line 5: Given URL endpoint 'imaps://other.bar:993' does not match previous endpoint 'imaps://foo.bar:993'." + c := WithDefault() + c.FeedOptions = Options{} + + err := c.parse(strings.NewReader(inp)) + if err == nil { + tst.Error("Expected error.") + } else { + if diff := cmp.Diff(errorText, err.Error()); diff != "" { + tst.Error(diff) + } + } +} \ No newline at end of file -- cgit v1.2.3