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
|
package cal
import (
"bufio"
"fmt"
"strconv"
"strings"
"time"
"github.com/emersion/go-ical"
)
// Compose builds an event from the same key: value form the tree emits.
//
// summary: lunch, then a walk; maybe
// start: 1787500800
// end: 1787504400
// location: the wire
// attendee: bob@example.com
// attendee: carol@example.com
// organizer: you@example.com
// description: anything
//
// Accepting this rather than raw icalendar keeps the escaping where the
// parser already lives. A shell script writing SUMMARY: by hand has to
// know that a comma means something, and will eventually forget; here
// it writes what it means and go-ical spells it.
//
// Times are epoch seconds, because that is what date(1) hands a script.
func Compose(r *bufio.Reader, uid string) (*ical.Calendar, error) {
f := map[string][]string{}
for {
line, err := r.ReadString('\n')
if line = strings.TrimRight(line, "\r\n"); line != "" {
k, v, ok := strings.Cut(line, ":")
if !ok {
return nil, fmt.Errorf("compose: %q is not key: value", line)
}
k = strings.ToLower(strings.TrimSpace(k))
f[k] = append(f[k], strings.TrimSpace(v))
}
if err != nil {
break
}
}
one := func(k string) string {
if v := f[k]; len(v) > 0 {
return v[0]
}
return ""
}
at := func(k string) (time.Time, error) {
v := one(k)
if v == "" {
return time.Time{}, fmt.Errorf("compose: no %s", k)
}
n, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("compose: %s: want epoch seconds, got %q", k, v)
}
return time.Unix(n, 0).UTC(), nil
}
if one("summary") == "" {
return nil, fmt.Errorf("compose: no summary")
}
start, err := at("start")
if err != nil {
return nil, err
}
end, err := at("end")
if err != nil {
return nil, err
}
ev := ical.NewEvent()
ev.Props.SetText(ical.PropUID, uid)
ev.Props.SetDateTime(ical.PropDateTimeStamp, time.Now().UTC())
ev.Props.SetDateTime(ical.PropDateTimeStart, start)
ev.Props.SetDateTime(ical.PropDateTimeEnd, end)
ev.Props.SetText(ical.PropSummary, one("summary"))
if v := one("location"); v != "" {
ev.Props.SetText(ical.PropLocation, v)
}
if v := one("description"); v != "" {
ev.Props.SetText(ical.PropDescription, v)
}
if v := one("organizer"); v != "" {
p := ical.NewProp(ical.PropOrganizer)
p.Value = "mailto:" + v
ev.Props.Set(p)
// the organiser is attending their own meeting
a := ical.NewProp(ical.PropAttendee)
a.Value = "mailto:" + v
a.Params.Set(ical.ParamParticipationStatus, "ACCEPTED")
ev.Props.Add(a)
}
for _, who := range f["attendee"] {
a := ical.NewProp(ical.PropAttendee)
a.Value = "mailto:" + who
a.Params.Set(ical.ParamParticipationStatus, "NEEDS-ACTION")
a.Params.Set("RSVP", "TRUE")
ev.Props.Add(a)
}
c := ical.NewCalendar()
c.Props.SetText(ical.PropProductID, "-//9front//pim//EN")
c.Props.SetText(ical.PropVersion, "2.0")
c.Children = append(c.Children, ev.Component)
return c, nil
}
|