115 lines
2.7 KiB
Go
115 lines
2.7 KiB
Go
package ical
|
|
|
|
import (
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"usoscal/internal/store"
|
|
)
|
|
|
|
const (
|
|
stamp = "20060102T150405Z"
|
|
lineLimit = 75
|
|
)
|
|
|
|
func Summary(event store.Event) string {
|
|
if event.Kind == "" || event.Kind == event.Title {
|
|
return event.Title
|
|
}
|
|
return event.Title + " (" + event.Kind + ")"
|
|
}
|
|
|
|
func Location(event store.Event) string {
|
|
place := event.Room
|
|
switch {
|
|
case place != "" && event.Building != "":
|
|
place += " (" + event.Building + ")"
|
|
case place == "":
|
|
place = event.Building
|
|
}
|
|
return joinNonEmpty(", ", place, event.Address)
|
|
}
|
|
|
|
func Description(event store.Event) string {
|
|
about := event.Kind
|
|
if event.Group != "" {
|
|
about = joinNonEmpty(", ", about, "group "+event.Group)
|
|
}
|
|
lecturers := ""
|
|
if event.Lecturers != "" {
|
|
lecturers = "Lecturer: " + event.Lecturers
|
|
}
|
|
return joinNonEmpty("\n", about, lecturers, event.URL)
|
|
}
|
|
|
|
func Write(w io.Writer, name, host string, events []store.Event) error {
|
|
var b strings.Builder
|
|
line(&b, "BEGIN:VCALENDAR")
|
|
line(&b, "VERSION:2.0")
|
|
line(&b, "PRODID:-//usoscal//EN")
|
|
line(&b, "CALSCALE:GREGORIAN")
|
|
line(&b, "METHOD:PUBLISH")
|
|
line(&b, "X-WR-CALNAME:"+escape(name))
|
|
line(&b, "X-WR-TIMEZONE:Europe/Warsaw")
|
|
line(&b, "REFRESH-INTERVAL;VALUE=DURATION:PT6H")
|
|
line(&b, "X-PUBLISHED-TTL:PT6H")
|
|
for _, event := range events {
|
|
line(&b, "BEGIN:VEVENT")
|
|
line(&b, "UID:"+event.UID+"@"+host)
|
|
line(&b, "DTSTAMP:"+utc(event.UpdatedAt))
|
|
line(&b, "LAST-MODIFIED:"+utc(event.UpdatedAt))
|
|
line(&b, "DTSTART:"+utc(event.Start))
|
|
line(&b, "DTEND:"+utc(event.End))
|
|
line(&b, "SUMMARY:"+escape(Summary(event)))
|
|
if location := Location(event); location != "" {
|
|
line(&b, "LOCATION:"+escape(location))
|
|
}
|
|
if description := Description(event); description != "" {
|
|
line(&b, "DESCRIPTION:"+escape(description))
|
|
}
|
|
if event.URL != "" {
|
|
line(&b, "URL:"+event.URL)
|
|
}
|
|
line(&b, "END:VEVENT")
|
|
}
|
|
line(&b, "END:VCALENDAR")
|
|
_, err := io.WriteString(w, b.String())
|
|
return err
|
|
}
|
|
|
|
func utc(t time.Time) string {
|
|
return t.UTC().Format(stamp)
|
|
}
|
|
|
|
func joinNonEmpty(separator string, parts ...string) string {
|
|
kept := parts[:0:0]
|
|
for _, part := range parts {
|
|
if part != "" {
|
|
kept = append(kept, part)
|
|
}
|
|
}
|
|
return strings.Join(kept, separator)
|
|
}
|
|
|
|
var escaper = strings.NewReplacer(`\`, `\\`, ";", `\;`, ",", `\,`, "\r\n", `\n`, "\n", `\n`, "\r", "")
|
|
|
|
func escape(text string) string {
|
|
return escaper.Replace(text)
|
|
}
|
|
|
|
func line(b *strings.Builder, content string) {
|
|
for len(content) > lineLimit {
|
|
cut := lineLimit
|
|
for cut > 0 && !utf8.RuneStart(content[cut]) {
|
|
cut--
|
|
}
|
|
b.WriteString(content[:cut])
|
|
b.WriteString("\r\n ")
|
|
content = content[cut:]
|
|
}
|
|
b.WriteString(content)
|
|
b.WriteString("\r\n")
|
|
}
|