Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion cmd/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

func sendCmd() *cobra.Command {
var serverURL string
var recipientsFilter string

cmd := &cobra.Command{
Use: "send [content] [list]",
Expand All @@ -24,7 +25,7 @@ func sendCmd() *cobra.Command {
}

if u := serverURL; u == "" {
return mail.LoadAndSendCampaign(cfg, args[0], args[1])
return mail.LoadAndSendCampaign(cfg, args[0], args[1], recipientsFilter)
} else {
return client.New(cmd.Context(), u).Send(client.SendArgs{
ProjectPath: ".", // TODO: configurable
Expand All @@ -38,6 +39,7 @@ func sendCmd() *cobra.Command {

// Server to specify remote server
cmd.Flags().StringVar(&serverURL, "server", "", "URL of server")
cmd.Flags().StringVar(&recipientsFilter, "filter", "", "Recipients filter")

return cmd
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.24.1
require (
github.com/PuerkitoBio/goquery v1.10.3
github.com/bep/inflect v0.0.0-20160408190323-b896c45f5af9
github.com/casbin/govaluate v1.10.0
github.com/cenkalti/backoff/v5 v5.0.3
github.com/charmbracelet/glamour v0.10.0
github.com/chris-ramon/douceur v0.2.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bep/inflect v0.0.0-20160408190323-b896c45f5af9 h1:2ZyfRr6MKtNow0D0AbbVlzrS3OI6a+svlOHrtFYGI9Q=
github.com/bep/inflect v0.0.0-20160408190323-b896c45f5af9/go.mod h1:/fmCHLLmoBKSfptXUFVJZb7MMt7JCS4vm0vqQmAo3xE=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
Expand Down
2 changes: 1 addition & 1 deletion mail/campaign.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type tmplContext struct {
}

type Campaign struct {
Recipients []*ctxRecipient
Recipients CtxRecipients
EmailMeta *ctxCampaign
Email parser.Email

Expand Down
6 changes: 5 additions & 1 deletion mail/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ func newRecipient(data map[string]any) ctxRecipient {

// Campaign variable
type ctxCampaign struct {
From string
From string
// Filter recipients
Filter string
Params map[string]interface{}

// Original subject from frontmatter
Expand Down Expand Up @@ -73,6 +75,7 @@ func newCampaign(cfg *config.AConfig, data map[string]interface{}) ctxCampaign {
if c.From == "" {
c.From = cfg.From
}
c.Filter, _ = c.Params["filter"].(string)

// This will cast either an array or an invidivual string into an array.
// We remove blanks because an empty string will become []string{""}
Expand All @@ -86,6 +89,7 @@ func newCampaign(cfg *config.AConfig, data map[string]interface{}) ctxCampaign {
delete(c.Params, "subject")
delete(c.Params, "from")
delete(c.Params, "to")
delete(c.Params, "filter")
return c
}

Expand Down
41 changes: 41 additions & 0 deletions mail/ctx_recipients.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package mail

import (
"github.com/casbin/govaluate"
)

type CtxRecipients []*ctxRecipient

// Filter filters the recipients based on the provided filter expression.
// It evaluates the filter expression against each recipient's parameters
// and returns a slice of recipients that match the criteria.
//
// Parameters:
//
// filter: A string representing the filter expression to evaluate.
// The expression should be in a format compatible with
// the govaluate library.
//
// Returns:
//
// A slice of pointers to ctxRecipient that match the filter criteria,
// or an error if the evaluation of the expression fails or if any
// other error occurs during the filtering process.
func (cr CtxRecipients) Filter(filter string) ([]*ctxRecipient, error) {
expression, err := govaluate.NewEvaluableExpression(filter)
if err != nil {
return nil, err
}

var filteredRecipients []*ctxRecipient
for _, r := range cr {
result, err := expression.Evaluate(r.Params)
if err != nil {
return nil, err
}
if result == true {
filteredRecipients = append(filteredRecipients, r)
}
}
return filteredRecipients, nil
}
29 changes: 29 additions & 0 deletions mail/ctx_recipients_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package mail

import "testing"

func TestCtxRecipientsFilter(t *testing.T) {
var recipients CtxRecipients = []*ctxRecipient{
&ctxRecipient{
Name: "Name1",
Email: "name1@example.com",
Params: map[string]interface{}{
"class": "1",
},
},
&ctxRecipient{
Name: "Name2",
Email: "name2@example.com",
Params: map[string]interface{}{
"class": "2",
},
},
}
filtered, err := recipients.Filter("class == '1'")
if err != nil {
t.Errorf("Failed: %s", err)
}
if len(filtered) != 1 {
t.Errorf("Got %d", len(filtered))
}
}
15 changes: 14 additions & 1 deletion mail/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,26 @@ type sendConn interface {
Close() error
}

func LoadAndSendCampaign(cfg *config.AConfig, tmplFile, recipientFile string) error {
func LoadAndSendCampaign(cfg *config.AConfig, tmplFile, recipientFile string, filter string) error {
// Load up template and recipientswith frontmatter
c, err := LoadCampaign(cfg, tmplFile, recipientFile)
if err != nil {
return err
}

if filter == "" {
// Argument specified: use the possibly declared in Campaign
filter = c.EmailMeta.Filter
}
if filter != "" {
// Filter the recipients
filteredRecipients, err := c.Recipients.Filter(filter)
if err != nil {
return err
}
c.Recipients = filteredRecipients
}

return SendCampaign(cfg, c)
}

Expand Down
Binary file added paperboy
Binary file not shown.
2 changes: 1 addition & 1 deletion server/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,6 @@ func (r *Resolver) SendCampaign(ctx context.Context, args SendCampaignArgs) (boo
}

// Load campaign and recipient list, and send it 🚀
err = mail.LoadAndSendCampaign(cfg, args.Campaign, args.List)
err = mail.LoadAndSendCampaign(cfg, args.Campaign, args.List, "")
return err == nil, err
}