Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Format #13

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package ical

import (
"bytes"
"io"
"strconv"
)

// Format writes the calendar to the provided io.Writer.
func Format(w io.Writer, cal *Calendar) error {
var props []*Property
if cal.Prodid != "" {
props = append(props, &Property{Name: "PRODID", Value: cal.Prodid})
}
if cal.Version != "" {
props = append(props, &Property{Name: "VERSION", Value: cal.Version})
}
if cal.Calscale != "" {
props = append(props, &Property{Name: "CALSCALE", Value: cal.Calscale})
}
if cal.Method != "" {
props = append(props, &Property{Name: "METHOD", Value: cal.Method})
}
cal.Properties = setProperties(cal.Properties, props)

if _, err := io.WriteString(w, beginVCalendar+crlf); err != nil {
return err
}

if err := formatPropertiesList(w, cal.Properties); err != nil {
return err
}

for _, event := range cal.Events {
if err := formatEvent(w, event); err != nil {
return err
}
}

_, err := io.WriteString(w, endVCalendar+crlf)
return err
}

func formatEvent(w io.Writer, event *Event) error {
var props []*Property
if event.UID != "" {
props = append(props, &Property{Name: "UID", Value: event.UID})
}
// TODO: add TZID if necessary
if !event.Timestamp.IsZero() {
props = append(props, &Property{
Name: "DTSTAMP",
Value: event.Timestamp.Format(dateTimeLayoutUTC),
})
}
if !event.StartDate.IsZero() {
props = append(props, &Property{
Name: "DTSTART",
Value: event.StartDate.Format(dateTimeLayoutUTC),
})
}
if !event.EndDate.IsZero() {
props = append(props, &Property{
Name: "DTEND",
Value: event.EndDate.Format(dateTimeLayoutUTC),
})
}
if event.Summary != "" {
props = append(props, &Property{Name: "SUMMARY", Value: event.Summary})
}
if event.Description != "" {
props = append(props, &Property{Name: "DESCRIPTION", Value: event.Description})
}
event.Properties = setProperties(event.Properties, props)

if _, err := io.WriteString(w, beginVEvent+crlf); err != nil {
return err
}

if err := formatPropertiesList(w, event.Properties); err != nil {
return err
}

for _, alarm := range event.Alarms {
if err := formatAlarm(w, alarm); err != nil {
return err
}
}

_, err := io.WriteString(w, endVEvent+crlf)
return err
}

func formatAlarm(w io.Writer, alarm *Alarm) error {
var props []*Property
if alarm.Action != "" {
props = append(props, &Property{Name: "ACTION", Value: alarm.Action})
}
if alarm.Trigger != "" {
props = append(props, &Property{Name: "TRIGGER", Value: alarm.Trigger})
}
alarm.Properties = setProperties(alarm.Properties, props)

if _, err := io.WriteString(w, beginValarm+crlf); err != nil {
return err
}

if err := formatPropertiesList(w, alarm.Properties); err != nil {
return err
}

_, err := io.WriteString(w, endVAlarm+crlf)
return err
}

func formatPropertiesList(w io.Writer, props []*Property) error {
for _, prop := range props {
if err := formatProperty(w, prop); err != nil {
return err
}
}
return nil
}

func formatProperty(w io.Writer, prop *Property) error {
var buf bytes.Buffer
buf.WriteString(prop.Name)

for name, param := range prop.Params {
buf.WriteString(";")
buf.WriteString(name)
buf.WriteString("=")
for i, v := range param.Values {
if i > 0 {
buf.WriteString(",")
}
buf.WriteString(strconv.Quote(v))
}
}

buf.WriteString(":")
buf.WriteString(prop.Value)
buf.WriteString(crlf)

_, err := buf.WriteTo(w)
return err
}

func setProperties(l []*Property, newProps []*Property) []*Property {
m := make(map[string]*Property, len(newProps))
for _, newProp := range newProps {
m[newProp.Name] = newProp
}

for i, prop := range l {
newProp, ok := m[prop.Name]
if ok {
l[i] = newProp
delete(m, prop.Name)
}
}

for _, newProp := range newProps {
if _, ok := m[newProp.Name]; ok {
l = append(l, newProp)
}
}

return l
}
42 changes: 42 additions & 0 deletions format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ical

import (
"bytes"
"strings"
"testing"
"time"
)

func TestFormat(t *testing.T) {
event := NewEvent()
event.UID = "[email protected]"
event.Timestamp = time.Date(2020, 2, 11, 0, 0, 0, 0, time.UTC)
event.Summary = "Test event"

cal := NewCalendar()
cal.Events = []*Event{event}
cal.Prodid = "-//ABC Corporation//NONSGML My Product//EN"
cal.Version = "2.0"

want := `BEGIN:VCALENDAR
PRODID:-//ABC Corporation//NONSGML My Product//EN
VERSION:2.0
CALSCALE:GREGORIAN
BEGIN:VEVENT
UID:[email protected]
DTSTAMP:20200211T000000Z
SUMMARY:Test event
END:VEVENT
END:VCALENDAR
`
want = strings.Replace(want, "\n", "\r\n", -1)

var buf bytes.Buffer
if err := Format(&buf, cal); err != nil {
t.Fatalf("Format() = %v", err)
}

if s := buf.String(); s != want {
t.Errorf("Format() = \n%v\n but want \n%v", s, want)
}
}
88 changes: 88 additions & 0 deletions ical.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Package ical implements an iCalendar parser and formatter.
//
// iCalendar is defined in RFC 5545.
package ical

import (
"time"
)

// A Calendar represents the whole iCalendar
type Calendar struct {
Properties []*Property
Events []*Event
Prodid string
Version string
Calscale string
Method string
}

// An Event represent a VEVENT component in an iCalendar
type Event struct {
Properties []*Property
Alarms []*Alarm
UID string
Timestamp time.Time
StartDate time.Time
EndDate time.Time
Summary string
Description string
}

// An Alarm represent a VALARM component in an iCalendar
type Alarm struct {
Properties []*Property
Action string
Trigger string
}

// A Property represent an unparsed property in an iCalendar component
type Property struct {
Name string
Params map[string]*Param
Value string
}

// A Param represent a list of param for a property
type Param struct {
Values []string
}

// NewCalendar creates an empty Calendar
func NewCalendar() *Calendar {
c := &Calendar{
Calscale: "GREGORIAN",
}
c.Properties = make([]*Property, 0)
c.Events = make([]*Event, 0)
return c
}

// NewProperty creates an empty Property
func NewProperty() *Property {
p := &Property{}
p.Params = make(map[string]*Param)
return p
}

// NewEvent creates an empty Event
func NewEvent() *Event {
v := &Event{}
v.Properties = make([]*Property, 0)
v.Alarms = make([]*Alarm, 0)
return v
}

// NewAlarm creates an empty Alarm
func NewAlarm() *Alarm {
a := &Alarm{}
a.Properties = make([]*Property, 0)
return a
}

// NewParam creates an empty Param
func NewParam() *Param {
p := &Param{}
p.Values = make([]string, 0)
return p
}
80 changes: 0 additions & 80 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,6 @@ import (
"time"
)

// A Calendar represents the whole iCalendar
type Calendar struct {
Properties []*Property
Events []*Event
Prodid string
Version string
Calscale string
Method string
}

// An Event represent a VEVENT component in an iCalendar
type Event struct {
Properties []*Property
Alarms []*Alarm
UID string
Timestamp time.Time
StartDate time.Time
EndDate time.Time
Summary string
Description string
}

// An Alarm represent a VALARM component in an iCalendar
type Alarm struct {
Properties []*Property
Action string
Trigger string
}

// A Property represent an unparsed property in an iCalendar component
type Property struct {
Name string
Params map[string]*Param
Value string
}

// A Param represent a list of param for a property
type Param struct {
Values []string
}

type parser struct {
lex *lexer
token [2]item
Expand Down Expand Up @@ -85,45 +44,6 @@ func Parse(r io.Reader, l *time.Location) (*Calendar, error) {
return p.parse()
}

// NewCalendar creates an empty Calendar
func NewCalendar() *Calendar {
c := &Calendar{
Calscale: "GREGORIAN",
}
c.Properties = make([]*Property, 0)
c.Events = make([]*Event, 0)
return c
}

// NewProperty creates an empty Property
func NewProperty() *Property {
p := &Property{}
p.Params = make(map[string]*Param)
return p
}

// NewEvent creates an empty Event
func NewEvent() *Event {
v := &Event{}
v.Properties = make([]*Property, 0)
v.Alarms = make([]*Alarm, 0)
return v
}

// NewAlarm creates an empty Alarm
func NewAlarm() *Alarm {
a := &Alarm{}
a.Properties = make([]*Property, 0)
return a
}

// NewParam creates an empty Param
func NewParam() *Param {
p := &Param{}
p.Values = make([]string, 0)
return p
}

// unfold convert multiple line value to one line
func unfold(text string) string {
return strings.Replace(text, "\r\n ", "", -1)
Expand Down