Skip to content

Commit

Permalink
Merge pull request #106 from arran4/issue97
Browse files Browse the repository at this point in the history
Issue 97: Support for Alternative Representations
  • Loading branch information
arran4 authored Oct 17, 2024
2 parents 2467de0 + 0d0e9ff commit 4b57639
Show file tree
Hide file tree
Showing 13 changed files with 661 additions and 172 deletions.
74 changes: 67 additions & 7 deletions calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"net/http"
"reflect"
"strings"
"time"
)

Expand Down Expand Up @@ -169,6 +170,10 @@ func (cp ComponentProperty) Multiple(c Component) bool {
return false
}

func ComponentPropertyExtended(s string) ComponentProperty {
return ComponentProperty("X-" + strings.TrimPrefix("X-", s))
}

type Property string

const (
Expand Down Expand Up @@ -232,6 +237,14 @@ const (

type Parameter string

func (p Parameter) IsQuoted() bool {
switch p {
case ParameterAltrep:
return true
}
return false
}

const (
ParameterAltrep Parameter = "ALTREP"
ParameterCn Parameter = "CN"
Expand Down Expand Up @@ -404,25 +417,72 @@ func NewCalendarFor(service string) *Calendar {
return c
}

func (cal *Calendar) Serialize() string {
func (cal *Calendar) Serialize(ops ...any) string {
b := bytes.NewBufferString("")
// We are intentionally ignoring the return value. _ used to communicate this to lint.
_ = cal.SerializeTo(b)
_ = cal.SerializeTo(b, ops...)
return b.String()
}

func (cal *Calendar) SerializeTo(w io.Writer) error {
_, _ = fmt.Fprint(w, "BEGIN:VCALENDAR", "\r\n")
type WithLineLength int
type WithNewLine string

func (cal *Calendar) SerializeTo(w io.Writer, ops ...any) error {
serializeConfig, err := parseSerializeOps(ops)
if err != nil {
return err
}
_, _ = fmt.Fprint(w, "BEGIN:VCALENDAR", serializeConfig.NewLine)
for _, p := range cal.CalendarProperties {
p.serialize(w)
err := p.serialize(w, serializeConfig)
if err != nil {
return err
}
}
for _, c := range cal.Components {
c.SerializeTo(w)
err := c.SerializeTo(w, serializeConfig)
if err != nil {
return err
}
}
_, _ = fmt.Fprint(w, "END:VCALENDAR", "\r\n")
_, _ = fmt.Fprint(w, "END:VCALENDAR", serializeConfig.NewLine)
return nil
}

type SerializationConfiguration struct {
MaxLength int
NewLine string
PropertyMaxLength int
}

func parseSerializeOps(ops []any) (*SerializationConfiguration, error) {
serializeConfig := defaultSerializationOptions()
for opi, op := range ops {
switch op := op.(type) {
case WithLineLength:
serializeConfig.MaxLength = int(op)
case WithNewLine:
serializeConfig.NewLine = string(op)
case *SerializationConfiguration:
return op, nil
case error:
return nil, op
default:
return nil, fmt.Errorf("unknown op %d of type %s", opi, reflect.TypeOf(op))
}
}
return serializeConfig, nil
}

func defaultSerializationOptions() *SerializationConfiguration {
serializeConfig := &SerializationConfiguration{
MaxLength: 75,
PropertyMaxLength: 75,
NewLine: string(NewLine),
}
return serializeConfig
}

func (cal *Calendar) SetMethod(method Method, params ...PropertyParameter) {
cal.setProperty(PropertyMethod, string(method), params...)
}
Expand Down
7 changes: 4 additions & 3 deletions calendar_serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ func TestCalendar_ReSerialization(t *testing.T) {
}

for _, filename := range testFileNames {
t.Run(fmt.Sprintf("compare serialized -> deserialized -> serialized: %s", filename), func(t *testing.T) {
fp := filepath.Join(testDir, filename)
t.Run(fmt.Sprintf("compare serialized -> deserialized -> serialized: %s", fp), func(t *testing.T) {
//given
originalSeriailizedCal, err := os.ReadFile(filepath.Join(testDir, filename))
originalSeriailizedCal, err := os.ReadFile(fp)
require.NoError(t, err)

//when
deserializedCal, err := ParseCalendar(bytes.NewReader(originalSeriailizedCal))
require.NoError(t, err)
serializedCal := deserializedCal.Serialize()
serializedCal := deserializedCal.Serialize(WithNewLineWindows)

//then
expectedCal, err := os.ReadFile(filepath.Join(expectedDir, filename))
Expand Down
62 changes: 56 additions & 6 deletions calendar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package ics

import (
"bytes"
"embed"
_ "embed"
"github.com/google/go-cmp/cmp"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
Expand All @@ -16,8 +18,13 @@ import (
"github.com/stretchr/testify/assert"
)

var (
//go:embed testdata/*
TestData embed.FS
)

func TestTimeParsing(t *testing.T) {
calFile, err := os.OpenFile("./testdata/timeparsing.ics", os.O_RDONLY, 0400)
calFile, err := TestData.Open("testdata/timeparsing.ics")
if err != nil {
t.Errorf("read file: %v", err)
}
Expand Down Expand Up @@ -165,12 +172,15 @@ CLASS:PUBLIC
func TestRfc5545Sec4Examples(t *testing.T) {
rnReplace := regexp.MustCompile("\r?\n")

err := filepath.Walk("./testdata/rfc5545sec4/", func(path string, info os.FileInfo, _ error) error {
err := fs.WalkDir(TestData, "testdata/rfc5545sec4", func(path string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}

inputBytes, err := os.ReadFile(path)
inputBytes, err := fs.ReadFile(TestData, path)
if err != nil {
return err
}
Expand Down Expand Up @@ -389,13 +399,13 @@ END:VCALENDAR
}

func TestIssue52(t *testing.T) {
err := filepath.Walk("./testdata/issue52/", func(path string, info os.FileInfo, _ error) error {
err := fs.WalkDir(TestData, "testdata/issue52", func(path string, info fs.DirEntry, _ error) error {
if info.IsDir() {
return nil
}
_, fn := filepath.Split(path)
t.Run(fn, func(t *testing.T) {
f, err := os.Open(path)
f, err := TestData.Open(path)
if err != nil {
t.Fatalf("Error reading file: %s", err)
}
Expand All @@ -414,6 +424,46 @@ func TestIssue52(t *testing.T) {
}
}

func TestIssue97(t *testing.T) {
err := fs.WalkDir(TestData, "testdata/issue97", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
if !strings.HasSuffix(d.Name(), ".ics") && !strings.HasSuffix(d.Name(), ".ics_disabled") {
return nil
}
t.Run(path, func(t *testing.T) {
if strings.HasSuffix(d.Name(), ".ics_disabled") {
t.Skipf("Test disabled")
}
b, err := TestData.ReadFile(path)
if err != nil {
t.Fatalf("Error reading file: %s", err)
}
ics, err := ParseCalendar(bytes.NewReader(b))
if err != nil {
t.Fatalf("Error parsing file: %s", err)
}

got := ics.Serialize(WithLineLength(74))
if diff := cmp.Diff(string(b), got, cmp.Transformer("ToUnixText", func(a string) string {
return strings.ReplaceAll(a, "\r\n", "\n")
})); diff != "" {
t.Errorf("ParseCalendar() mismatch (-want +got):\n%s", diff)
t.Errorf("Complete got:\b%s", got)
}
})
return nil
})

if err != nil {
t.Fatalf("cannot read test directory: %v", err)
}
}

type MockHttpClient struct {
Response *http.Response
Error error
Expand Down
34 changes: 34 additions & 0 deletions cmd/issues/test97_1/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"fmt"
ics "github.com/arran4/golang-ical"
"net/url"
)

func main() {
i := ics.NewCalendarFor("Mozilla.org/NONSGML Mozilla Calendar V1.1")
tz := i.AddTimezone("Europe/Berlin")
tz.AddProperty(ics.ComponentPropertyExtended("TZINFO"), "Europe/Berlin[2024a]")
tzstd := tz.AddStandard()
tzstd.AddProperty(ics.ComponentProperty(ics.PropertyTzoffsetto), "+010000")
tzstd.AddProperty(ics.ComponentProperty(ics.PropertyTzoffsetfrom), "+005328")
tzstd.AddProperty(ics.ComponentProperty(ics.PropertyTzname), "Europe/Berlin(STD)")
tzstd.AddProperty(ics.ComponentProperty(ics.PropertyDtstart), "18930401T000000")
tzstd.AddProperty(ics.ComponentProperty(ics.PropertyRdate), "18930401T000000")
vEvent := i.AddEvent("d23cef0d-9e58-43c4-9391-5ad8483ca346")
vEvent.AddProperty(ics.ComponentPropertyCreated, "20240929T120640Z")
vEvent.AddProperty(ics.ComponentPropertyLastModified, "20240929T120731Z")
vEvent.AddProperty(ics.ComponentPropertyDtstamp, "20240929T120731Z")
vEvent.AddProperty(ics.ComponentPropertySummary, "Test Event")
vEvent.AddProperty(ics.ComponentPropertyDtStart, "20240929T144500", ics.WithTZID("Europe/Berlin"))
vEvent.AddProperty(ics.ComponentPropertyDtEnd, "20240929T154500", ics.WithTZID("Europe/Berlin"))
vEvent.AddProperty(ics.ComponentPropertyTransp, "OPAQUE")
vEvent.AddProperty(ics.ComponentPropertyLocation, "Github")
uri := &url.URL{
Scheme: "data",
Opaque: "text/html,I%20want%20a%20custom%20linkout%20for%20Thunderbird.%3Cbr%3EThis%20is%20the%20Github%20%3Ca%20href%3D%22https%3A%2F%2Fgit.luolix.top%2Farran4%2Fgolang-ical%2Fissues%2F97%22%3EIssue%3C%2Fa%3E.",
}
vEvent.AddProperty(ics.ComponentPropertyDescription, "I want a custom linkout for Thunderbird.\nThis is the Github Issue.", ics.WithAlternativeRepresentation(uri))
fmt.Println(i.Serialize())
}
Loading

0 comments on commit 4b57639

Please sign in to comment.