-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuild.go
More file actions
172 lines (156 loc) · 4.07 KB
/
Copy pathbuild.go
File metadata and controls
172 lines (156 loc) · 4.07 KB
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package icalendar
import (
"fmt"
"strings"
"time"
ics "github.com/arran4/golang-ical"
)
// defaultProdID identifies this library as the producer in generated calendars.
const defaultProdID = "-//floatpane//go-icalendar//EN"
// NewCalendar returns an empty PUBLISH calendar ready for [Calendar.Add].
func NewCalendar() *Calendar {
return &Calendar{
Method: string(MethodPublish),
ProdID: defaultProdID,
Version: "2.0",
}
}
// Add appends events to the calendar and returns it, for chaining.
func (c *Calendar) Add(events ...*Event) *Calendar {
c.Events = append(c.Events, events...)
return c
}
// NewRequest builds a METHOD:REQUEST calendar inviting attendees to e — the
// message an organizer sends to schedule a meeting. The event's Status defaults
// to CONFIRMED when unset.
func NewRequest(e *Event) *Calendar {
if e.Status == "" {
e.Status = string(StatusConfirmed)
}
return methodCalendar(MethodRequest, e)
}
// NewCancel builds a METHOD:CANCEL calendar for e, marking it CANCELLED and
// bumping its SEQUENCE so recipients treat it as a newer update than the
// original invite (RFC 5546 §3.2.5).
func NewCancel(e *Event) *Calendar {
e.Status = string(StatusCancelled)
e.Sequence++
return methodCalendar(MethodCancel, e)
}
func methodCalendar(m Method, e *Event) *Calendar {
return &Calendar{
Method: string(m),
ProdID: defaultProdID,
Version: "2.0",
Events: []*Event{e},
}
}
// Serialize renders the calendar as RFC 5545 bytes (CRLF-folded). It fills in
// sensible defaults for an empty ProdID/Version, and stamps any event missing a
// DTSTAMP with the current time.
func (c *Calendar) Serialize() ([]byte, error) {
cal := ics.NewCalendar()
cal.SetProductId(orDefault(c.ProdID, defaultProdID))
cal.SetVersion(orDefault(c.Version, "2.0"))
if c.Method != "" {
cal.SetMethod(ics.Method(c.Method))
}
for _, e := range c.Events {
if err := writeVEvent(cal, e); err != nil {
return nil, err
}
}
return []byte(cal.Serialize()), nil
}
// writeVEvent materializes one Event onto the ics calendar.
func writeVEvent(cal *ics.Calendar, e *Event) error {
if e.UID == "" {
return fmt.Errorf("event %q: UID is required", e.Summary)
}
ve := cal.AddEvent(e.UID)
stamp := e.Stamp
if stamp.IsZero() {
stamp = time.Now().UTC()
}
ve.SetDtStampTime(stamp)
if !e.Start.IsZero() {
if e.AllDay {
ve.SetAllDayStartAt(e.Start)
} else {
ve.SetStartAt(e.Start)
}
}
if !e.End.IsZero() {
if e.AllDay {
ve.SetAllDayEndAt(e.End)
} else {
ve.SetEndAt(e.End)
}
}
if e.Summary != "" {
ve.SetSummary(e.Summary)
}
if e.Description != "" {
ve.SetDescription(e.Description)
}
if e.Location != "" {
ve.SetLocation(e.Location)
}
if e.URL != "" {
ve.SetURL(e.URL)
}
if e.Status != "" {
ve.SetStatus(ics.ObjectStatus(strings.ToUpper(e.Status)))
}
if e.Sequence > 0 {
ve.SetSequence(e.Sequence)
}
if len(e.Categories) > 0 {
ve.SetProperty(ics.ComponentPropertyCategories, strings.Join(e.Categories, ","))
}
if !e.Created.IsZero() {
ve.SetCreatedTime(e.Created)
}
if !e.Modified.IsZero() {
ve.SetModifiedAt(e.Modified)
}
if e.Organizer != "" {
if e.OrganizerName != "" {
ve.SetOrganizer("mailto:"+e.Organizer, ics.WithCN(e.OrganizerName))
} else {
ve.SetOrganizer("mailto:" + e.Organizer)
}
}
for _, a := range e.Attendees {
var params []ics.PropertyParameter
if a.Name != "" {
params = append(params, ics.WithCN(a.Name))
}
if a.Role != "" {
params = append(params, &ics.KeyValues{Key: "ROLE", Value: []string{a.Role}})
}
if a.PartStat != "" {
params = append(params, &ics.KeyValues{Key: "PARTSTAT", Value: []string{string(a.PartStat)}})
}
if a.RSVP {
params = append(params, ics.WithRSVP(true))
}
ve.AddAttendee("mailto:"+a.Email, params...)
}
if e.Recurrence != nil {
ve.AddRrule(e.Recurrence.String())
}
for _, t := range e.RDates {
ve.AddRdate(t.UTC().Format("20060102T150405Z"))
}
for _, t := range e.ExDates {
ve.SetProperty(ics.ComponentPropertyExdate, t.UTC().Format("20060102T150405Z"))
}
return nil
}
func orDefault(s, def string) string {
if s == "" {
return def
}
return s
}