Skip to content

Commit

Permalink
Merge branch 'master' into error-handling-conflicts-again
Browse files Browse the repository at this point in the history
* master:
  Duplication issue fixed.
  Test fixed.
  Resynced and moved some functions around #78
  added functionnal option pattern for url parsing
  usage example, README
  calendar parsing url support

# Conflicts:
#	calendar_test.go
  • Loading branch information
arran4 committed Oct 15, 2024
2 parents df1f583 + 7fb626c commit ecde28c
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 59 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ A ICS / ICal parser and serialiser for Golang.
Because the other libraries didn't quite do what I needed.

Usage, parsing:
```
```golang
cal, err := ParseCalendar(strings.NewReader(input))

```

Creating:
Usage, parsing from a URL :
```golang
cal, err := ParseCalendar("an-ics-url")
```

Creating:
```golang
cal := ics.NewCalendar()
cal.SetMethod(ics.MethodRequest)
event := cal.AddEvent(fmt.Sprintf("id@domain", p.SessionKey.IntID()))
Expand Down
105 changes: 105 additions & 0 deletions calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package ics
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"reflect"
"time"
)

Expand Down Expand Up @@ -412,6 +415,108 @@ func (cal *Calendar) setProperty(property Property, value string, params ...Prop
cal.CalendarProperties = append(cal.CalendarProperties, r)
}

func (calendar *Calendar) AddEvent(id string) *VEvent {
e := NewEvent(id)
calendar.Components = append(calendar.Components, e)
return e
}

func (calendar *Calendar) AddVEvent(e *VEvent) {
calendar.Components = append(calendar.Components, e)
}

func (calendar *Calendar) Events() (r []*VEvent) {
r = []*VEvent{}
for i := range calendar.Components {
switch event := calendar.Components[i].(type) {
case *VEvent:
r = append(r, event)
}
}
return
}

func (calendar *Calendar) RemoveEvent(id string) {
for i := range calendar.Components {
switch event := calendar.Components[i].(type) {
case *VEvent:
if event.Id() == id {
if len(calendar.Components) > i+1 {
calendar.Components = append(calendar.Components[:i], calendar.Components[i+1:]...)
} else {
calendar.Components = calendar.Components[:i]
}
return
}
}
}
}

func WithCustomClient(client *http.Client) *http.Client {
return client
}

func WithCustomRequest(request *http.Request) *http.Request {
return request
}

func ParseCalendarFromUrl(url string, opts ...any) (*Calendar, error) {
var ctx context.Context
var req *http.Request
var client HttpClientLike = http.DefaultClient
for opti, opt := range opts {
switch opt := opt.(type) {
case *http.Client:
client = opt
case HttpClientLike:
client = opt
case func() *http.Client:
client = opt()
case *http.Request:
req = opt
case func() *http.Request:
req = opt()
case context.Context:
ctx = opt
case func() context.Context:
ctx = opt()
default:
return nil, fmt.Errorf("unknown optional argument %d on ParseCalendarFromUrl: %s", opti, reflect.TypeOf(opt))
}
}
if ctx == nil {
ctx = context.Background()
}
if req == nil {
var err error
req, err = http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("creating http request: %w", err)
}
}
return parseCalendarFromHttpRequest(client, req)
}

type HttpClientLike interface {
Do(req *http.Request) (*http.Response, error)
}

func parseCalendarFromHttpRequest(client HttpClientLike, request *http.Request) (*Calendar, error) {
resp, err := client.Do(request)
if err != nil {
return nil, fmt.Errorf("http request: %w", err)
}
defer func(closer io.ReadCloser) {
if derr := closer.Close(); derr != nil && err == nil {
err = fmt.Errorf("http request close: %w", derr)
}
}(resp.Body)
var cal *Calendar
cal, err = ParseCalendar(resp.Body)
// This allows the defer func to change the error
return cal, err
}

func ParseCalendar(r io.Reader) (*Calendar, error) {
state := "begin"
c := &Calendar{}
Expand Down
33 changes: 33 additions & 0 deletions calendar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package ics
import (
"errors"
"github.com/stretchr/testify/assert"
"bytes"
_ "embed"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -411,3 +414,33 @@ func TestIssue52(t *testing.T) {
t.Fatalf("cannot read test directory: %v", err)
}
}

type MockHttpClient struct {
Response *http.Response
Error error
}

func (m *MockHttpClient) Do(req *http.Request) (*http.Response, error) {
return m.Response, m.Error
}

var (
_ HttpClientLike = &MockHttpClient{}
//go:embed "testdata/rfc5545sec4/input1.ics"
input1TestData []byte
)

func TestIssue77(t *testing.T) {
url := "https://proseconsult.umontpellier.fr/jsp/custom/modules/plannings/direct_cal.jsp?data=58c99062bab31d256bee14356aca3f2423c0f022cb9660eba051b2653be722c4c7f281e4e3ad06b85d3374100ac416a4dc5c094f7d1a811b903031bde802c7f50e0bd1077f9461bed8f9a32b516a3c63525f110c026ed6da86f487dd451ca812c1c60bb40b1502b6511435cf9908feb2166c54e36382c1aa3eb0ff5cb8980cdb,1"

_, err := ParseCalendarFromUrl(url, &MockHttpClient{
Response: &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewReader(input1TestData)),
},
})

if err != nil {
t.Fatalf("Error reading file: %s", err)
}
}
77 changes: 20 additions & 57 deletions components.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,77 +475,40 @@ func NewEvent(uniqueId string) *VEvent {
return e
}

func (cal *Calendar) AddEvent(id string) *VEvent {
e := NewEvent(id)
cal.Components = append(cal.Components, e)
return e
func (event *VEvent) SetEndAt(t time.Time, props ...PropertyParameter) {
event.SetProperty(ComponentPropertyDtEnd, t.UTC().Format(icalTimestampFormatUtc), props...)
}

func (cal *Calendar) AddVEvent(e *VEvent) {
cal.Components = append(cal.Components, e)
func (event *VEvent) SetLastModifiedAt(t time.Time, props ...PropertyParameter) {
event.SetProperty(ComponentPropertyLastModified, t.UTC().Format(icalTimestampFormatUtc), props...)
}

func (cal *Calendar) RemoveEvent(id string) {
for i := range cal.Components {
switch event := cal.Components[i].(type) {
case *VEvent:
if event.Id() == id {
if len(cal.Components) > i+1 {
cal.Components = append(cal.Components[:i], cal.Components[i+1:]...)
} else {
cal.Components = cal.Components[:i]
}
return
}
}
}
func (event *VEvent) SetGeo(lat interface{}, lng interface{}, params ...PropertyParameter) {
event.setGeo(lat, lng, params...)
}

func (cal *Calendar) Events() []*VEvent {
var r []*VEvent
for i := range cal.Components {
switch event := cal.Components[i].(type) {
case *VEvent:
r = append(r, event)
}
}
return r
func (event *VEvent) SetPriority(p int, params ...PropertyParameter) {
event.setPriority(p, params...)
}

func (c *VEvent) SetEndAt(t time.Time, params ...PropertyParameter) {
c.SetProperty(ComponentPropertyDtEnd, t.UTC().Format(icalTimestampFormatUtc), params...)
func (event *VEvent) SetResources(r string, params ...PropertyParameter) {
event.setResources(r, params...)
}

func (c *VEvent) SetLastModifiedAt(t time.Time, params ...PropertyParameter) {
c.SetProperty(ComponentPropertyLastModified, t.UTC().Format(icalTimestampFormatUtc), params...)
func (event *VEvent) AddAlarm() *VAlarm {
return event.addAlarm()
}

func (c *VEvent) SetGeo(lat interface{}, lng interface{}, params ...PropertyParameter) {
c.setGeo(lat, lng, params...)
func (event *VEvent) AddVAlarm(a *VAlarm) {
event.addVAlarm(a)
}

func (c *VEvent) SetPriority(p int, params ...PropertyParameter) {
c.setPriority(p, params...)
}

func (c *VEvent) SetResources(r string, params ...PropertyParameter) {
c.setResources(r, params...)
}

func (c *VEvent) AddAlarm() *VAlarm {
return c.addAlarm()
}

func (c *VEvent) AddVAlarm(a *VAlarm) {
c.addVAlarm(a)
}

func (c *VEvent) Alarms() []*VAlarm {
return c.alarms()
func (event *VEvent) Alarms() []*VAlarm {
return event.alarms()
}

func (c *VEvent) GetAllDayEndAt() (time.Time, error) {
return c.getTimeProp(ComponentPropertyDtEnd, true)
func (event *VEvent) GetAllDayEndAt() (time.Time, error) {
return event.getTimeProp(ComponentPropertyDtEnd, true)
}

type TimeTransparency string
Expand All @@ -555,8 +518,8 @@ const (
TransparencyTransparent TimeTransparency = "TRANSPARENT"
)

func (c *VEvent) SetTimeTransparency(v TimeTransparency, params ...PropertyParameter) {
c.SetProperty(ComponentPropertyTransp, string(v), params...)
func (event *VEvent) SetTimeTransparency(v TimeTransparency, params ...PropertyParameter) {
event.SetProperty(ComponentPropertyTransp, string(v), params...)
}

type VTodo struct {
Expand Down

0 comments on commit ecde28c

Please sign in to comment.