1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
package activity
import (
"context"
"encoding/json"
"os/exec"
"strings"
"time"
)
// Event is one calendar event from the cal-events helper.
type Event struct {
Start, End time.Time
Title, Calendar string
}
// CalendarFilter selects calendars by name, case-insensitively. Non-empty
// Show is an allowlist; Hide is a blocklist applied after Show. Zero value
// passes everything.
type CalendarFilter struct {
Show []string
Hide []string
}
// Allow reports whether events from the named calendar should appear.
func (f CalendarFilter) Allow(name string) bool {
n := strings.ToLower(name)
if len(f.Show) > 0 {
ok := false
for _, s := range f.Show {
if strings.ToLower(s) == n {
ok = true
break
}
}
if !ok {
return false
}
}
for _, h := range f.Hide {
if strings.ToLower(h) == n {
return false
}
}
return true
}
// rawEvent mirrors one cal-events JSON object.
type rawEvent struct {
Title string `json:"title"`
Start string `json:"start"`
End string `json:"end"`
Calendar string `json:"calendar"`
}
// parseEvents decodes the cal-events JSON array, keeping events that pass
// filter. Any decode error yields an empty slice (tolerant).
func parseEvents(b []byte, filter CalendarFilter) []Event {
var raws []rawEvent
if err := json.Unmarshal(b, &raws); err != nil {
return nil
}
var events []Event
for _, r := range raws {
start, err := time.Parse(time.RFC3339, r.Start)
if err != nil {
continue
}
end, err := time.Parse(time.RFC3339, r.End)
if err != nil {
continue
}
if !filter.Allow(r.Calendar) {
continue
}
events = append(events, Event{Start: start, End: end, Title: r.Title, Calendar: r.Calendar})
}
return events
}
// LoadCalendar runs the cal-events helper for date (YYYY-MM-DD) and filters
// the result. Missing helper, denied access, or bad output ⇒ empty slice, no
// error — the timeline still renders without calendar data.
func LoadCalendar(bin, date string, filter CalendarFilter) ([]Event, error) {
if bin == "" {
return nil, nil
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, bin, date).Output()
if err != nil {
return nil, nil
}
return parseEvents(out, filter), nil
}
|