diff --git a/internal/adapters/adapter.go b/internal/adapters/adapter.go index 2cc5627..8e4c5b1 100644 --- a/internal/adapters/adapter.go +++ b/internal/adapters/adapter.go @@ -27,5 +27,6 @@ func GetAdapters() []Adapter { &DeletePropertyAdapter{}, &FilterAdapter{}, &PrintAdapter{}, + &MergeAdapter{}, } } diff --git a/internal/adapters/merge.go b/internal/adapters/merge.go new file mode 100644 index 0000000..45ec7c3 --- /dev/null +++ b/internal/adapters/merge.go @@ -0,0 +1,56 @@ +package adapters + +import ( + "os" + + "github.com/akamensky/argparse" + "github.com/dploeger/icarus/v2/pkg/processors" + "github.com/emersion/go-ical" +) + +// The FilterAdapter filters the calendar for selected events +type MergeAdapter struct { + toolbox processors.Toolbox + mergeCalendar *os.File + overwrite *bool + mergeProps *[]string +} + +func (a *MergeAdapter) Initialize(parser *argparse.Parser) (*argparse.Command, error) { + command := parser.NewCommand("merge", "Merge another calendar into the input calendar. The filter options work on the additional calendar for this comand.") + a.mergeCalendar = command.File("I", "merge-calendar", os.O_RDONLY, 0600, &argparse.Options{ + Help: "Calendar to merge into the input calendar", + Required: true, + }) + a.overwrite = command.Flag("O", "overwrite", &argparse.Options{ + Help: "Overwrite events in the input calendar with those from the other calendar", + }) + a.mergeProps = command.StringList("P", "merge-props", &argparse.Options{ + Help: "The ical event properties which decide if two events are the same", + Default: []string{ical.PropSummary, ical.PropDateTimeStart, ical.PropDateTimeEnd}, + }) + return command, nil +} + +func (a *MergeAdapter) Process(input ical.Calendar, output *ical.Calendar) error { + var mergeCalendar ical.Calendar + dec := ical.NewDecoder(a.mergeCalendar) + if cal, err := dec.Decode(); err != nil { + return err + } else { + mergeCalendar = *cal + } + p := processors.NewMergeProcessor(mergeCalendar) + p.MergeProps = *a.mergeProps + if *a.overwrite { + p.MergeOption = processors.MergeOptionOverWrite + } + p.SetToolbox(a.toolbox) + return p.Process(input, output) +} + +func (f *MergeAdapter) SetToolbox(toolbox processors.Toolbox) { + f.toolbox = toolbox +} + +var _ Adapter = &MergeAdapter{} diff --git a/internal/adapters/testdata/script/filter.txtar b/internal/adapters/testdata/script/filter.txtar index b8d4f41..f7c3ce5 100644 --- a/internal/adapters/testdata/script/filter.txtar +++ b/internal/adapters/testdata/script/filter.txtar @@ -1,7 +1,7 @@ stdin example.ics exec icarus filter -s Test2 ! stderr . -! stdout SUMMARY:Test$ +! stdout 'SUMMARY:Test\r\n' -- example.ics -- BEGIN:VCALENDAR diff --git a/internal/adapters/testdata/script/filter_dates_start.txtar b/internal/adapters/testdata/script/filter_dates_start.txtar index 719a09b..4dc9b23 100644 --- a/internal/adapters/testdata/script/filter_dates_start.txtar +++ b/internal/adapters/testdata/script/filter_dates_start.txtar @@ -1,7 +1,7 @@ stdin example.ics exec icarus filter -b 2024-01-24T00:00:00Z ! stderr . -! stdout SUMMARY:Test$ +! stdout 'SUMMARY:Test\r\n' -- example.ics -- BEGIN:VCALENDAR diff --git a/internal/adapters/testdata/script/merge.txtar b/internal/adapters/testdata/script/merge.txtar new file mode 100644 index 0000000..8bb1077 --- /dev/null +++ b/internal/adapters/testdata/script/merge.txtar @@ -0,0 +1,108 @@ +stdin example.ics +exec icarus merge -I examplemerge.ics +! stderr . +stdout 'SUMMARY:Test\r\n' +stdout 'SUMMARY:Test3\r\n' + +-- example.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240123T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240123T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +BEGIN:VEVENT +CREATED:20240123T214829Z +DTEND;VALUE=DATE:20240125 +DTSTAMP:20240123T214844Z +DTSTART;VALUE=DATE:20240124 +LAST-MODIFIED:20240123T214829Z +SEQUENCE:0 +SUMMARY:Test2 +TRANSP:TRANSPARENT +UID:87C40F70-6E3F-4409-B8DC-874BA54C4048 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR + +-- examplemerge.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240223T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240223T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test3 +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/internal/adapters/testdata/script/merge_filter.txtar b/internal/adapters/testdata/script/merge_filter.txtar new file mode 100644 index 0000000..7f0063c --- /dev/null +++ b/internal/adapters/testdata/script/merge_filter.txtar @@ -0,0 +1,126 @@ +stdin example.ics +exec icarus merge -I examplemerge.ics -s Test3 +! stderr . +stdout 'SUMMARY:Test\r\n' +stdout 'SUMMARY:Test3\r\n' +! stdout 'SUMMARY:Test4\r\n' + +-- example.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240123T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240123T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +BEGIN:VEVENT +CREATED:20240123T214829Z +DTEND;VALUE=DATE:20240125 +DTSTAMP:20240123T214844Z +DTSTART;VALUE=DATE:20240124 +LAST-MODIFIED:20240123T214829Z +SEQUENCE:0 +SUMMARY:Test2 +TRANSP:TRANSPARENT +UID:87C40F70-6E3F-4409-B8DC-874BA54C4048 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR + +-- examplemerge.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240223T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240223T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test3 +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240223T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240223T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test4 +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/internal/adapters/testdata/script/merge_overwrite.txtar b/internal/adapters/testdata/script/merge_overwrite.txtar new file mode 100644 index 0000000..3e5720c --- /dev/null +++ b/internal/adapters/testdata/script/merge_overwrite.txtar @@ -0,0 +1,111 @@ +stdin example.ics +exec icarus merge -I examplemerge.ics -O +! stderr . +stdout 'SUMMARY:Test\r\n' +stdout 'COMMENT:Merge\r\n' +! stdout 'COMMENT:Input\r\n' + +-- example.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240123T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240123T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test +COMMENT:Input +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +BEGIN:VEVENT +CREATED:20240123T214829Z +DTEND;VALUE=DATE:20240125 +DTSTAMP:20240123T214844Z +DTSTART;VALUE=DATE:20240124 +LAST-MODIFIED:20240123T214829Z +SEQUENCE:0 +SUMMARY:Test2 +TRANSP:TRANSPARENT +UID:87C40F70-6E3F-4409-B8DC-874BA54C4048 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR + +-- examplemerge.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240123T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240123T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test +COMMENT:Merge +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/internal/adapters/testdata/script/merge_skip.txtar b/internal/adapters/testdata/script/merge_skip.txtar new file mode 100644 index 0000000..11cd335 --- /dev/null +++ b/internal/adapters/testdata/script/merge_skip.txtar @@ -0,0 +1,111 @@ +stdin example.ics +exec icarus merge -I examplemerge.ics +! stderr . +stdout 'SUMMARY:Test\r\n' +! stdout 'COMMENT:Merge\r\n' +stdout 'COMMENT:Input\r\n' + +-- example.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240123T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240123T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test +COMMENT:Input +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +BEGIN:VEVENT +CREATED:20240123T214829Z +DTEND;VALUE=DATE:20240125 +DTSTAMP:20240123T214844Z +DTSTART;VALUE=DATE:20240124 +LAST-MODIFIED:20240123T214829Z +SEQUENCE:0 +SUMMARY:Test2 +TRANSP:TRANSPARENT +UID:87C40F70-6E3F-4409-B8DC-874BA54C4048 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR + +-- examplemerge.ics -- +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Apple Inc.//macOS 14.2.1//EN +VERSION:2.0 +X-APPLE-CALENDAR-COLOR:#1BADF8 +X-WR-CALNAME:example +BEGIN:VTIMEZONE +TZID:Europe/Berlin +BEGIN:DAYLIGHT +DTSTART:19810329T020000 +RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU +TZNAME:MESZ +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +END:DAYLIGHT +BEGIN:STANDARD +DTSTART:19961027T030000 +RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU +TZNAME:MEZ +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +CREATED:20240123T214815Z +DTEND;TZID=Europe/Berlin:20240123T113000 +DTSTAMP:20240123T214844Z +DTSTART;TZID=Europe/Berlin:20240123T101500 +LAST-MODIFIED:20240123T214815Z +SEQUENCE:0 +SUMMARY:Test +COMMENT:Merge +TRANSP:OPAQUE +UID:B9243EBE-1F38-477B-A21C-D9C0BD61C025 +X-APPLE-CREATOR-IDENTITY:com.apple.calendar +X-APPLE-CREATOR-TEAM-IDENTITY:0000000000 +BEGIN:VALARM +ACTION:NONE +TRIGGER;VALUE=DATE-TIME:19760401T005545Z +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/pkg/processors/merge.go b/pkg/processors/merge.go new file mode 100644 index 0000000..67abc17 --- /dev/null +++ b/pkg/processors/merge.go @@ -0,0 +1,82 @@ +package processors + +import ( + "strings" + + "github.com/emersion/go-ical" + "github.com/thoas/go-funk" +) + +type MergeOption int + +const ( + MergeOptionOverWrite MergeOption = iota + MergeOptionSkip +) + +// The AddAlarmProcessor adds an alarm definition to all selected events +type MergeProcessor struct { + MergeCalendar ical.Calendar + MergeOption MergeOption + MergeProps []string + toolbox Toolbox +} + +func NewMergeProcessor(calendar ical.Calendar) MergeProcessor { + return MergeProcessor{ + MergeCalendar: calendar, + MergeOption: MergeOptionSkip, + MergeProps: []string{ical.PropSummary, ical.PropDateTimeStart, ical.PropDateTimeEnd}, + } +} + +func (a *MergeProcessor) SetToolbox(toolbox Toolbox) { + a.toolbox = toolbox +} + +func (a *MergeProcessor) eventMatches(eva ical.Event, evb ical.Event) bool { + return a.getEventID(eva) == a.getEventID(evb) +} + +func (a *MergeProcessor) getEventID(ev ical.Event) string { + var propValues []string + for _, mergeProp := range a.MergeProps { + propValue := ev.Props.Get(mergeProp) + if propValue != nil { + propValues = append(propValues, propValue.Value) + } + } + return strings.Join(propValues, ":") +} + +func (a *MergeProcessor) Process(input ical.Calendar, output *ical.Calendar) error { + var skipEventsInput []string + var skipEventsMerge []string + for _, mergeEvent := range a.MergeCalendar.Events() { + if a.toolbox.EventMatchesSelector(mergeEvent) { + for _, inputEvent := range input.Events() { + if a.eventMatches(mergeEvent, inputEvent) { + if a.MergeOption == MergeOptionOverWrite { + skipEventsInput = append(skipEventsInput, a.getEventID(inputEvent)) + } else { + skipEventsMerge = append(skipEventsMerge, a.getEventID(mergeEvent)) + } + + } + } + } + } + for _, event := range input.Events() { + if !funk.ContainsString(skipEventsInput, a.getEventID(event)) { + output.Children = append(output.Children, event.Component) + } + } + for _, event := range a.MergeCalendar.Events() { + if a.toolbox.EventMatchesSelector(event) && !funk.ContainsString(skipEventsMerge, a.getEventID(event)) { + output.Children = append(output.Children, event.Component) + } + } + return nil +} + +var _ BaseProcessor = &AddAlarmProcessor{} diff --git a/pkg/processors/merge_test.go b/pkg/processors/merge_test.go new file mode 100644 index 0000000..bcd6cfa --- /dev/null +++ b/pkg/processors/merge_test.go @@ -0,0 +1,122 @@ +package processors + +import ( + "regexp" + "testing" + "time" + + "github.com/emersion/go-ical" + "github.com/stretchr/testify/assert" +) + +func TestMergeProcessor_Process(t *testing.T) { + event1 := ical.NewEvent() + event1.Props.SetText(ical.PropSummary, "test") + now := time.Now().In(time.UTC).Truncate(time.Second) + event1.Props.SetDate(ical.PropDateTimeStart, now) + event1.Props.SetDate(ical.PropDateTimeEnd, now) + input := ical.NewCalendar() + input.Children = append(input.Children, event1.Component) + event2 := ical.NewEvent() + event2.Props.SetText(ical.PropSummary, "test2") + now = time.Now().In(time.UTC).Truncate(time.Second) + event2.Props.SetDate(ical.PropDateTimeStart, now) + event2.Props.SetDate(ical.PropDateTimeEnd, now) + merge := ical.NewCalendar() + merge.Children = append(merge.Children, event2.Component) + subject := NewMergeProcessor(*merge) + subject.SetToolbox(NewToolbox()) + output := ical.NewCalendar() + err := subject.Process(*input, output) + if assert.NoError(t, err, "Process got an error") { + assert.Len(t, output.Children, 2, "Invalid number of events") + assert.Equal(t, output.Children[0].Props.Get(ical.PropSummary).Value, "test", "Invalid first event") + assert.Equal(t, output.Children[1].Props.Get(ical.PropSummary).Value, "test2", "Invalid second event") + } +} + +func TestMergeProcessor_Overwrite(t *testing.T) { + event1 := ical.NewEvent() + event1.Props.SetText(ical.PropSummary, "test") + now := time.Now().In(time.UTC).Truncate(time.Second) + event1.Props.SetDate(ical.PropDateTimeStart, now) + event1.Props.SetDate(ical.PropDateTimeEnd, now) + event1.Props.SetText(ical.PropComment, "input") + input := ical.NewCalendar() + input.Children = append(input.Children, event1.Component) + event2 := ical.NewEvent() + event2.Props.SetText(ical.PropSummary, "test") + event2.Props.SetDate(ical.PropDateTimeStart, now) + event2.Props.SetDate(ical.PropDateTimeEnd, now) + event2.Props.SetText(ical.PropComment, "merge") + merge := ical.NewCalendar() + merge.Children = append(merge.Children, event2.Component) + subject := NewMergeProcessor(*merge) + subject.SetToolbox(NewToolbox()) + subject.MergeOption = MergeOptionOverWrite + output := ical.NewCalendar() + err := subject.Process(*input, output) + if assert.NoError(t, err, "Process got an error") { + assert.Len(t, output.Children, 1, "Invalid number of events") + assert.Equal(t, output.Children[0].Props.Get(ical.PropComment).Value, "merge", "Event from wrong calendar") + } +} + +func TestMergeProcessor_Skip(t *testing.T) { + event1 := ical.NewEvent() + event1.Props.SetText(ical.PropSummary, "test") + now := time.Now().In(time.UTC).Truncate(time.Second) + event1.Props.SetDate(ical.PropDateTimeStart, now) + event1.Props.SetDate(ical.PropDateTimeEnd, now) + event1.Props.SetText(ical.PropComment, "input") + input := ical.NewCalendar() + input.Children = append(input.Children, event1.Component) + event2 := ical.NewEvent() + event2.Props.SetText(ical.PropSummary, "test") + event2.Props.SetDate(ical.PropDateTimeStart, now) + event2.Props.SetDate(ical.PropDateTimeEnd, now) + event2.Props.SetText(ical.PropComment, "merge") + merge := ical.NewCalendar() + merge.Children = append(merge.Children, event2.Component) + subject := NewMergeProcessor(*merge) + subject.SetToolbox(NewToolbox()) + output := ical.NewCalendar() + err := subject.Process(*input, output) + if assert.NoError(t, err, "Process got an error") { + assert.Len(t, output.Children, 1, "Invalid number of events") + assert.Equal(t, output.Children[0].Props.Get(ical.PropComment).Value, "input", "Event from wrong calendar") + } +} + +func TestMergeProcessor_Filter(t *testing.T) { + event1 := ical.NewEvent() + event1.Props.SetText(ical.PropSummary, "test") + now := time.Now().In(time.UTC).Truncate(time.Second) + event1.Props.SetDate(ical.PropDateTimeStart, now) + event1.Props.SetDate(ical.PropDateTimeEnd, now) + input := ical.NewCalendar() + input.Children = append(input.Children, event1.Component) + event2 := ical.NewEvent() + event2.Props.SetText(ical.PropSummary, "test2") + now = time.Now().In(time.UTC).Truncate(time.Second) + event2.Props.SetDate(ical.PropDateTimeStart, now) + event2.Props.SetDate(ical.PropDateTimeEnd, now) + event3 := ical.NewEvent() + event3.Props.SetText(ical.PropSummary, "test3") + now = time.Now().In(time.UTC).Truncate(time.Second) + event3.Props.SetDate(ical.PropDateTimeStart, now) + event3.Props.SetDate(ical.PropDateTimeEnd, now) + merge := ical.NewCalendar() + merge.Children = append(merge.Children, event2.Component, event3.Component) + subject := NewMergeProcessor(*merge) + toolbox := NewToolbox() + toolbox.TextSelectorPattern = regexp.MustCompile("test[^3]") + subject.SetToolbox(toolbox) + output := ical.NewCalendar() + err := subject.Process(*input, output) + if assert.NoError(t, err, "Process got an error") { + assert.Len(t, output.Children, 2, "Invalid number of events") + assert.Equal(t, output.Children[0].Props.Get(ical.PropSummary).Value, "test", "Invalid first event") + assert.Equal(t, output.Children[1].Props.Get(ical.PropSummary).Value, "test2", "Invalid second event") + } +} diff --git a/pkg/processors/toolbox.go b/pkg/processors/toolbox.go index 92cb71f..11d43f1 100644 --- a/pkg/processors/toolbox.go +++ b/pkg/processors/toolbox.go @@ -1,10 +1,11 @@ package processors import ( - "github.com/emersion/go-ical" - "github.com/sirupsen/logrus" "regexp" "time" + + "github.com/emersion/go-ical" + "github.com/sirupsen/logrus" ) // A Toolbox of common functions @@ -36,10 +37,15 @@ func (t Toolbox) EventMatchesSelector(event ical.Event) bool { } if t.TextSelectorPattern != nil { + logrus.Debug("Selecting events") for _, prop := range t.TextSelectorProps { - if event.Props.Get(prop) != nil && t.TextSelectorPattern.MatchString(event.Props.Get(prop).Value) { - logrus.Debugf("Event %s matched selector", event.Name) - return true + eventProp := event.Props.Get(prop) + if eventProp != nil { + logrus.Debugf("Testing event prop %s (%s) on pattern %s", prop, eventProp.Value, t.TextSelectorPattern) + if t.TextSelectorPattern.MatchString(event.Props.Get(prop).Value) { + logrus.Infof("Event %s matched selector", event.Name) + return true + } } } }