diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d60549 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# IntelliJ files +.idea/* + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Test Output +tmp/ +testingTempDir/ \ No newline at end of file diff --git a/calendar.go b/calendar.go index ab8a0b8..ac512ee 100644 --- a/calendar.go +++ b/calendar.go @@ -80,6 +80,12 @@ func (c *Calendar) GetTimezone() time.Location { // add event to the calendar func (c *Calendar) SetEvent(event Event) (*Calendar, error) { + return c.addEvent(event), nil +} + +// add event to the calendar +func (c *Calendar) addEvent(event Event) *Calendar { + // lock so that the events array doesn't change its size from other goruote mutex.Lock() @@ -113,7 +119,7 @@ func (c *Calendar) SetEvent(event Event) (*Calendar, error) { } mutex.Unlock() - return c, nil + return c } // get event by id diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6b9e74e --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/PuloV/ics-golang + +go 1.13 + +require ( + github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 + github.com/stretchr/testify v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0c9ad5a --- /dev/null +++ b/go.sum @@ -0,0 +1,13 @@ +github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61 h1:o64h9XF42kVEUuhuer2ehqrlX8rZmvQSU0+Vpj1rF6Q= +github.com/channelmeter/iso8601duration v0.0.0-20150204201828-8da3af7a2a61/go.mod h1:Rp8e0DCtEKwXFOC6JPJQVTz8tuGoGvw6Xfexggh/ed0= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/parse.go b/parse.go index 21e9d63..28a42e0 100644 --- a/parse.go +++ b/parse.go @@ -46,16 +46,25 @@ func New() *Parser { // buffers the events output chan go func() { + MainLoop: for { if len(p.parsedEvents) > 0 { select { case p.outputChan <- p.parsedEvents[0]: p.parsedEvents = p.parsedEvents[1:] - case event := <-p.bufferedChan: + case event, more := <-p.bufferedChan: + if !more { + close(p.outputChan) + break MainLoop + } p.parsedEvents = append(p.parsedEvents, event) } } else { - event := <-p.bufferedChan + event, more := <-p.bufferedChan + if !more { + close(p.outputChan) + break MainLoop + } p.parsedEvents = append(p.parsedEvents, event) } } @@ -64,7 +73,11 @@ func New() *Parser { go func(input chan string) { // endless loop for getting the ics urls for { - link := <-input + link, more := <-input + + if !more { + break + } // mark calendar in the wait group as not parsed p.wg.Add(1) @@ -90,7 +103,16 @@ func New() *Parser { } // parse the ICal calendar - p.parseICalContent(iCalContent, link) + calendar, events, err := ParseICalContent(iCalContent, link) + + p.parsedCalendars = append(p.parsedCalendars, calendar) + if err != nil { + p.errorsOccured = append(p.errorsOccured, err) + } + + for _, v := range events { + p.bufferedChan <- v + } mutex.Lock() // marks that we have parsed 1 calendar and we have statusCalendars -1 left to be parsed @@ -107,7 +129,14 @@ func New() *Parser { // Load calender from content func (p *Parser) Load(iCalContent string) { - p.parseICalContent(iCalContent, "") + cal, events, err := ParseICalContent(iCalContent, "") + p.parsedCalendars = append(p.parsedCalendars, cal) + for _, v := range events { + p.bufferedChan <- v + } + if err != nil { + p.errorsOccured = append(p.errorsOccured, err) + } } // returns the chan for calendar urls @@ -120,7 +149,7 @@ func (p *Parser) GetOutputChan() chan *Event { return p.outputChan } -// returns the chan where will be received events +// returns Calendars that were parsed func (p *Parser) GetCalendars() ([]*Calendar, error) { if !p.Done() { return nil, errors.New("Calendars not parsed") @@ -146,9 +175,16 @@ func (p *Parser) Wait() { p.wg.Wait() } +// wait until everything is parsed and close channels / close routines +func (p *Parser) WaitAndClose() { + p.Wait() + close(p.inputChan) + close(p.bufferedChan) +} + // get the data from the calendar func (p *Parser) getICal(url string) (string, error) { - re, _ := regexp.Compile(`http(s){0,1}:\/\/`) + re := regexp.MustCompile(`http(s){0,1}:\/\/`) var fileName string var errDownload error @@ -189,61 +225,67 @@ func (p *Parser) getICal(url string) (string, error) { // ======================== CALENDAR PARSING =================== // parses the iCal formated string to a calendar object -func (p *Parser) parseICalContent(iCalContent, url string) { +func ParseICalContent(iCalContent, url string) (*Calendar, []*Event, error) { ical := NewCalendar() - p.parsedCalendars = append(p.parsedCalendars, ical) // split the data into calendar info and events data eventsData, calInfo := explodeICal(iCalContent) idCounter++ // fill the calendar fields - ical.SetName(p.parseICalName(calInfo)) - ical.SetDesc(p.parseICalDesc(calInfo)) - ical.SetVersion(p.parseICalVersion(calInfo)) - ical.SetTimezone(p.parseICalTimezone(calInfo)) + ical.SetName(parseICalName(calInfo)) + ical.SetDesc(parseICalDesc(calInfo)) + ical.SetVersion(parseICalVersion(calInfo)) + tz, err := parseICalTimezone(calInfo) + ical.SetTimezone(tz) ical.SetUrl(url) // parse the events and add them to ical - p.parseEvents(ical, eventsData) + events := parseEvents(ical, eventsData) + return ical, events, err } +var reEvents = regexp.MustCompile(`(BEGIN:VEVENT(.*\n)*?END:VEVENT\r?\n)`) + // explodes the ICal content to array of events and calendar info func explodeICal(iCalContent string) ([]string, string) { - reEvents, _ := regexp.Compile(`(BEGIN:VEVENT(.*\n)*?END:VEVENT\r?\n)`) allEvents := reEvents.FindAllString(iCalContent, len(iCalContent)) calInfo := reEvents.ReplaceAllString(iCalContent, "") return allEvents, calInfo } +var reICalName = regexp.MustCompile(`X-WR-CALNAME:.*?\n`) + // parses the iCal Name -func (p *Parser) parseICalName(iCalContent string) string { - re, _ := regexp.Compile(`X-WR-CALNAME:.*?\n`) - result := re.FindString(iCalContent) +func parseICalName(iCalContent string) string { + result := reICalName.FindString(iCalContent) return trimField(result, "X-WR-CALNAME:") } +var reICalDesc = regexp.MustCompile(`X-WR-CALDESC:.*?\n`) + // parses the iCal description -func (p *Parser) parseICalDesc(iCalContent string) string { - re, _ := regexp.Compile(`X-WR-CALDESC:.*?\n`) - result := re.FindString(iCalContent) +func parseICalDesc(iCalContent string) string { + result := reICalDesc.FindString(iCalContent) return trimField(result, "X-WR-CALDESC:") } +var reICalVersion = regexp.MustCompile(`VERSION:.*?\n`) + // parses the iCal version -func (p *Parser) parseICalVersion(iCalContent string) float64 { - re, _ := regexp.Compile(`VERSION:.*?\n`) - result := re.FindString(iCalContent) +func parseICalVersion(iCalContent string) float64 { + result := reICalVersion.FindString(iCalContent) // parse the version result to float ver, _ := strconv.ParseFloat(trimField(result, "VERSION:"), 64) return ver } +var reICalTimezone = regexp.MustCompile(`X-WR-TIMEZONE:.*?\n`) + // parses the iCal timezone -func (p *Parser) parseICalTimezone(iCalContent string) time.Location { - re, _ := regexp.Compile(`X-WR-TIMEZONE:.*?\n`) - result := re.FindString(iCalContent) +func parseICalTimezone(iCalContent string) (time.Location, error) { + result := reICalTimezone.FindString(iCalContent) // parse the timezone result to time.Location timezone := trimField(result, "X-WR-TIMEZONE:") @@ -252,58 +294,72 @@ func (p *Parser) parseICalTimezone(iCalContent string) time.Location { // if fails with the timezone => go Local if err != nil { - p.errorsOccured = append(p.errorsOccured, err) loc, _ = time.LoadLocation("UTC") } - return *loc + return *loc, err } // ======================== EVENTS PARSING =================== +var ( + reUntil = regexp.MustCompile(`UNTIL=(\d)*T(\d)*Z(;){0,1}`) + csUntil = `(UNTIL=|;)` + reInterval = regexp.MustCompile(`INTERVAL=(\d)*(;){0,1}`) + csInterval = `(INTERVAL=|;)` + reCount = regexp.MustCompile(`COUNT=(\d)*(;){0,1}`) + csCount = `(COUNT=|;)` + reFr = regexp.MustCompile(`FREQ=[^;]*(;){0,1}`) + csFr = `(FREQ=|;)` + reBM = regexp.MustCompile(`BYMONTH=[^;]*(;){0,1}`) + csBM = `(BYMONTH=|;)` + reBD = regexp.MustCompile(`BYDAY=[^;]*(;){0,1}`) + csBD = `(BYDAY=|;)` +) + // parses the iCal events Data -func (p *Parser) parseEvents(cal *Calendar, eventsData []string) { - for _, eventData := range eventsData { +func parseEvents(cal *Calendar, eventsData []string) []*Event { + events := make([]*Event, len(eventsData)) + for i, eventData := range eventsData { event := NewEvent() - start, startTZID := p.parseEventStart(eventData) - end, endTZID := p.parseEventEnd(eventData) - duration := p.parseEventDuration(eventData) + start, startTZID := parseEventStart(eventData) + end, endTZID := parseEventEnd(eventData) + eventDuration := parseEventDuration(eventData) if end.Before(start) { - end = start.Add(duration) + end = start.Add(eventDuration) } // whole day event when both times are 00:00:00 wholeDay := start.Hour() == 0 && end.Hour() == 0 && start.Minute() == 0 && end.Minute() == 0 && start.Second() == 0 && end.Second() == 0 event.SetStartTZID(startTZID) event.SetEndTZID(endTZID) - event.SetStatus(p.parseEventStatus(eventData)) - event.SetSummary(p.parseEventSummary(eventData)) - event.SetDescription(p.parseEventDescription(eventData)) - event.SetImportedID(p.parseEventId(eventData)) - event.SetClass(p.parseEventClass(eventData)) - event.SetSequence(p.parseEventSequence(eventData)) - event.SetCreated(p.parseEventCreated(eventData)) - event.SetLastModified(p.parseEventModified(eventData)) - event.SetRRule(p.parseEventRRule(eventData)) - event.SetLocation(p.parseEventLocation(eventData)) - event.SetGeo(p.parseEventGeo(eventData)) + event.SetStatus(parseEventStatus(eventData)) + event.SetSummary(parseEventSummary(eventData)) + event.SetDescription(parseEventDescription(eventData)) + event.SetImportedID(parseEventId(eventData)) + event.SetClass(parseEventClass(eventData)) + event.SetSequence(parseEventSequence(eventData)) + event.SetCreated(parseEventCreated(eventData)) + event.SetLastModified(parseEventModified(eventData)) + event.SetRRule(parseEventRRule(eventData)) + event.SetLocation(parseEventLocation(eventData)) + event.SetGeo(parseEventGeo(eventData)) event.SetStart(start) event.SetEnd(end) event.SetWholeDayEvent(wholeDay) - event.SetAttendees(p.parseEventAttendees(eventData)) - event.SetOrganizer(p.parseEventOrganizer(eventData)) + event.SetAttendees(parseEventAttendees(eventData)) + event.SetOrganizer(parseEventOrganizer(eventData)) event.SetCalendar(cal) event.SetID(event.GenerateEventId()) - cal.SetEvent(*event) - p.bufferedChan <- event + cal.addEvent(*event) + events[i] = event if RepeatRuleApply && event.GetRRule() != "" { // until field - reUntil, _ := regexp.Compile(`UNTIL=(\d)*T(\d)*Z(;){0,1}`) - untilString := trimField(reUntil.FindString(event.GetRRule()), `(UNTIL=|;)`) + untilString := trimField(reUntil.FindString(event.GetRRule()), csUntil) // set until date var until *time.Time if untilString == "" { @@ -314,8 +370,7 @@ func (p *Parser) parseEvents(cal *Calendar, eventsData []string) { } // INTERVAL field - reInterval, _ := regexp.Compile(`INTERVAL=(\d)*(;){0,1}`) - intervalString := trimField(reInterval.FindString(event.GetRRule()), `(INTERVAL=|;)`) + intervalString := trimField(reInterval.FindString(event.GetRRule()), csInterval) interval, _ := strconv.Atoi(intervalString) if interval == 0 { @@ -323,24 +378,20 @@ func (p *Parser) parseEvents(cal *Calendar, eventsData []string) { } // count field - reCount, _ := regexp.Compile(`COUNT=(\d)*(;){0,1}`) - countString := trimField(reCount.FindString(event.GetRRule()), `(COUNT=|;)`) + countString := trimField(reCount.FindString(event.GetRRule()), csCount) count, _ := strconv.Atoi(countString) if count == 0 { count = MaxRepeats } // freq field - reFr, _ := regexp.Compile(`FREQ=[^;]*(;){0,1}`) - freq := trimField(reFr.FindString(event.GetRRule()), `(FREQ=|;)`) + freq := trimField(reFr.FindString(event.GetRRule()), csFr) // by month field - reBM, _ := regexp.Compile(`BYMONTH=[^;]*(;){0,1}`) - bymonth := trimField(reBM.FindString(event.GetRRule()), `(BYMONTH=|;)`) + bymonth := trimField(reBM.FindString(event.GetRRule()), csBM) // by day field - reBD, _ := regexp.Compile(`BYDAY=[^;]*(;){0,1}`) - byday := trimField(reBD.FindString(event.GetRRule()), `(BYDAY=|;)`) + byday := trimField(reBD.FindString(event.GetRRule()), csBD) // fmt.Printf("%#v \n", reBD.FindString(event.GetRRule())) // fmt.Println("untilString", reUntil.FindString(event.GetRRule())) @@ -436,73 +487,82 @@ func (p *Parser) parseEvents(cal *Calendar, eventsData []string) { } } + + return events } +var reEventSummary = regexp.MustCompile(`SUMMARY(?:;LANGUAGE=[a-zA-Z\-]+)?.*?\n`) + // parses the event summary -func (p *Parser) parseEventSummary(eventData string) string { - re, _ := regexp.Compile(`SUMMARY(?:;LANGUAGE=[a-zA-Z\-]+)?.*?\n`) - result := re.FindString(eventData) +func parseEventSummary(eventData string) string { + result := reEventSummary.FindString(eventData) return trimField(result, `SUMMARY(?:;LANGUAGE=[a-zA-Z\-]+)?:`) } +var reEventStatus = regexp.MustCompile(`STATUS:.*?\n`) + // parses the event status -func (p *Parser) parseEventStatus(eventData string) string { - re, _ := regexp.Compile(`STATUS:.*?\n`) - result := re.FindString(eventData) +func parseEventStatus(eventData string) string { + result := reEventStatus.FindString(eventData) return trimField(result, "STATUS:") } +var reEventDescription = regexp.MustCompile(`DESCRIPTION:.*?\n(?:\s+.*?\n)*`) + // parses the event description -func (p *Parser) parseEventDescription(eventData string) string { - re, _ := regexp.Compile(`DESCRIPTION:.*?\n(?:\s+.*?\n)*`) - result := re.FindString(eventData) +func parseEventDescription(eventData string) string { + result := reEventDescription.FindString(eventData) return trimField(strings.Replace(result, "\r\n ", "", -1), "DESCRIPTION:") } +var reEventId = regexp.MustCompile(`UID:.*?\n`) + // parses the event id provided form google -func (p *Parser) parseEventId(eventData string) string { - re, _ := regexp.Compile(`UID:.*?\n`) - result := re.FindString(eventData) +func parseEventId(eventData string) string { + result := reEventId.FindString(eventData) return trimField(result, "UID:") } +var reEeventClass = regexp.MustCompile(`CLASS:.*?\n`) + // parses the event class -func (p *Parser) parseEventClass(eventData string) string { - re, _ := regexp.Compile(`CLASS:.*?\n`) - result := re.FindString(eventData) +func parseEventClass(eventData string) string { + result := reEeventClass.FindString(eventData) return trimField(result, "CLASS:") } +var reEventSequence = regexp.MustCompile(`SEQUENCE:.*?\n`) + // parses the event sequence -func (p *Parser) parseEventSequence(eventData string) int { - re, _ := regexp.Compile(`SEQUENCE:.*?\n`) - result := re.FindString(eventData) +func parseEventSequence(eventData string) int { + result := reEventSequence.FindString(eventData) sq, _ := strconv.Atoi(trimField(result, "SEQUENCE:")) return sq } +var reEventCreated = regexp.MustCompile(`CREATED:.*?\n`) + // parses the event created time -func (p *Parser) parseEventCreated(eventData string) time.Time { - re, _ := regexp.Compile(`CREATED:.*?\n`) - result := re.FindString(eventData) +func parseEventCreated(eventData string) time.Time { + result := reEventCreated.FindString(eventData) created := trimField(result, "CREATED:") t, _ := time.Parse(IcsFormat, created) return t } +var reEventModified = regexp.MustCompile(`LAST-MODIFIED:.*?\n`) + // parses the event modified time -func (p *Parser) parseEventModified(eventData string) time.Time { - re, _ := regexp.Compile(`LAST-MODIFIED:.*?\n`) - result := re.FindString(eventData) +func parseEventModified(eventData string) time.Time { + result := reEventModified.FindString(eventData) modified := trimField(result, "LAST-MODIFIED:") t, _ := time.Parse(IcsFormat, modified) return t } // parses the event start time -func (p *Parser) parseTimeField(fieldName string, eventData string) (time.Time, string) { - reWholeDay, _ := regexp.Compile(fmt.Sprintf(`%s;VALUE=DATE:.*?\n`, fieldName)) - re, _ := regexp.Compile(fmt.Sprintf(`%s(;TZID=(.*?))?(;VALUE=DATE-TIME)?:(.*?)\n`, fieldName)) +func parseTimeField(fieldName string, eventData string) (time.Time, string) { + reWholeDay := regexp.MustCompile(fmt.Sprintf(`%s;VALUE=DATE:.*?\n`, fieldName)) resultWholeDay := reWholeDay.FindString(eventData) var t time.Time var tzID string @@ -513,6 +573,7 @@ func (p *Parser) parseTimeField(fieldName string, eventData string) (time.Time, t, _ = time.Parse(IcsFormatWholeDay, modified) } else { // event that has start hour and minute + re := regexp.MustCompile(fmt.Sprintf(`%s(;TZID=(.*?))?(;VALUE=DATE-TIME)?:(.*?)\n`, fieldName)) result := re.FindStringSubmatch(eventData) if result == nil || len(result) < 4 { return t, tzID @@ -529,18 +590,19 @@ func (p *Parser) parseTimeField(fieldName string, eventData string) (time.Time, } // parses the event start time -func (p *Parser) parseEventStart(eventData string) (time.Time, string) { - return p.parseTimeField("DTSTART", eventData) +func parseEventStart(eventData string) (time.Time, string) { + return parseTimeField("DTSTART", eventData) } // parses the event end time -func (p *Parser) parseEventEnd(eventData string) (time.Time, string) { - return p.parseTimeField("DTEND", eventData) +func parseEventEnd(eventData string) (time.Time, string) { + return parseTimeField("DTEND", eventData) } -func (p *Parser) parseEventDuration(eventData string) time.Duration { - reDuration, _ := regexp.Compile(`DURATION:.*?\n`) - result := reDuration.FindString(eventData) +var reEventDuration = regexp.MustCompile(`DURATION:.*?\n`) + +func parseEventDuration(eventData string) time.Duration { + result := reEventDuration.FindString(eventData) trimmed := trimField(result, "DURATION:") parsedDuration, err := duration.FromString(trimmed) var output time.Duration @@ -552,24 +614,27 @@ func (p *Parser) parseEventDuration(eventData string) time.Duration { return output } +var reEventRRule = regexp.MustCompile(`RRULE:.*?\n`) + // parses the event RRULE (the repeater) -func (p *Parser) parseEventRRule(eventData string) string { - re, _ := regexp.Compile(`RRULE:.*?\n`) - result := re.FindString(eventData) +func parseEventRRule(eventData string) string { + result := reEventRRule.FindString(eventData) return trimField(result, "RRULE:") } +var reEventLocation = regexp.MustCompile(`LOCATION:.*?\n`) + // parses the event LOCATION -func (p *Parser) parseEventLocation(eventData string) string { - re, _ := regexp.Compile(`LOCATION:.*?\n`) - result := re.FindString(eventData) +func parseEventLocation(eventData string) string { + result := reEventLocation.FindString(eventData) return trimField(result, "LOCATION:") } +var reEventGeo = regexp.MustCompile(`GEO:.*?\n`) + // parses the event GEO -func (p *Parser) parseEventGeo(eventData string) *Geo { - re, _ := regexp.Compile(`GEO:.*?\n`) - result := re.FindString(eventData) +func parseEventGeo(eventData string) *Geo { + result := reEventGeo.FindString(eventData) value := trimField(result, "GEO:") values := strings.Split(value, ";") @@ -582,17 +647,18 @@ func (p *Parser) parseEventGeo(eventData string) *Geo { // ======================== ATTENDEE PARSING =================== +var reEventAttendees = regexp.MustCompile(`ATTENDEE(:|;)(.*?\r?\n)(\s.*?\r?\n)*`) + // parses the event attendees -func (p *Parser) parseEventAttendees(eventData string) []*Attendee { +func parseEventAttendees(eventData string) []*Attendee { attendeesObj := []*Attendee{} - re, _ := regexp.Compile(`ATTENDEE(:|;)(.*?\r?\n)(\s.*?\r?\n)*`) - attendees := re.FindAllString(eventData, len(eventData)) + attendees := reEventAttendees.FindAllString(eventData, len(eventData)) for _, attendeeData := range attendees { if attendeeData == "" { continue } - attendee := p.parseAttendee(strings.Replace(strings.Replace(attendeeData, "\r", "", 1), "\n ", "", 1)) + attendee := parseAttendee(strings.Replace(strings.Replace(attendeeData, "\r", "", 1), "\n ", "", 1)) // check for any fields set if attendee.GetEmail() != "" || attendee.GetName() != "" || attendee.GetRole() != "" || attendee.GetStatus() != "" || attendee.GetType() != "" { attendeesObj = append(attendeesObj, attendee) @@ -601,56 +667,58 @@ func (p *Parser) parseEventAttendees(eventData string) []*Attendee { return attendeesObj } -// parses the event organizer -func (p *Parser) parseEventOrganizer(eventData string) *Attendee { +var reEventOrganizer = regexp.MustCompile(`ORGANIZER(:|;)(.*?\r?\n)(\s.*?\r?\n)*`) - re, _ := regexp.Compile(`ORGANIZER(:|;)(.*?\r?\n)(\s.*?\r?\n)*`) - organizerData := re.FindString(eventData) +// parses the event organizer +func parseEventOrganizer(eventData string) *Attendee { + organizerData := reEventOrganizer.FindString(eventData) if organizerData == "" { return nil } organizerDataFormated := strings.Replace(strings.Replace(organizerData, "\r", "", 1), "\n ", "", 1) a := NewAttendee() - a.SetEmail(p.parseAttendeeMail(organizerDataFormated)) - a.SetName(p.parseOrganizerName(organizerDataFormated)) + a.SetEmail(parseAttendeeMail(organizerDataFormated)) + a.SetName(parseOrganizerName(organizerDataFormated)) return a } // parse attendee properties -func (p *Parser) parseAttendee(attendeeData string) *Attendee { - +func parseAttendee(attendeeData string) *Attendee { a := NewAttendee() - a.SetEmail(p.parseAttendeeMail(attendeeData)) - a.SetName(p.parseAttendeeName(attendeeData)) - a.SetRole(p.parseAttendeeRole(attendeeData)) - a.SetStatus(p.parseAttendeeStatus(attendeeData)) - a.SetType(p.parseAttendeeType(attendeeData)) + a.SetEmail(parseAttendeeMail(attendeeData)) + a.SetName(parseAttendeeName(attendeeData)) + a.SetRole(parseAttendeeRole(attendeeData)) + a.SetStatus(parseAttendeeStatus(attendeeData)) + a.SetType(parseAttendeeType(attendeeData)) return a } +var reAttendeeMail = regexp.MustCompile(`mailto:.*?\n`) + // parses the attendee email -func (p *Parser) parseAttendeeMail(attendeeData string) string { - re, _ := regexp.Compile(`mailto:.*?\n`) - result := re.FindString(attendeeData) +func parseAttendeeMail(attendeeData string) string { + result := reAttendeeMail.FindString(attendeeData) return trimField(result, "mailto:") } +var reAttendeeStatus = regexp.MustCompile(`PARTSTAT=.*?;`) + // parses the attendee status -func (p *Parser) parseAttendeeStatus(attendeeData string) string { - re, _ := regexp.Compile(`PARTSTAT=.*?;`) - result := re.FindString(attendeeData) +func parseAttendeeStatus(attendeeData string) string { + result := reAttendeeStatus.FindString(attendeeData) if result == "" { return "" } return trimField(result, `(PARTSTAT=|;)`) } +var reAttendeeRole = regexp.MustCompile(`ROLE=.*?;`) + // parses the attendee role -func (p *Parser) parseAttendeeRole(attendeeData string) string { - re, _ := regexp.Compile(`ROLE=.*?;`) - result := re.FindString(attendeeData) +func parseAttendeeRole(attendeeData string) string { + result := reAttendeeRole.FindString(attendeeData) if result == "" { return "" @@ -658,30 +726,33 @@ func (p *Parser) parseAttendeeRole(attendeeData string) string { return trimField(result, `(ROLE=|;)`) } +var reAttendeeName = regexp.MustCompile(`CN=.*?;`) + // parses the attendee Name -func (p *Parser) parseAttendeeName(attendeeData string) string { - re, _ := regexp.Compile(`CN=.*?;`) - result := re.FindString(attendeeData) +func parseAttendeeName(attendeeData string) string { + result := reAttendeeName.FindString(attendeeData) if result == "" { return "" } return trimField(result, `(CN=|;)`) } +var reOrganizerName = regexp.MustCompile(`CN=.*?:`) + // parses the organizer Name -func (p *Parser) parseOrganizerName(orgData string) string { - re, _ := regexp.Compile(`CN=.*?:`) - result := re.FindString(orgData) +func parseOrganizerName(orgData string) string { + result := reOrganizerName.FindString(orgData) if result == "" { return "" } return trimField(result, `(CN=|:)`) } +var reAttendeeType = regexp.MustCompile(`CUTYPE=.*?;`) + // parses the attendee type -func (p *Parser) parseAttendeeType(attendeeData string) string { - re, _ := regexp.Compile(`CUTYPE=.*?;`) - result := re.FindString(attendeeData) +func parseAttendeeType(attendeeData string) string { + result := reAttendeeType.FindString(attendeeData) if result == "" { return "" } diff --git a/parse_test.go b/parse_test.go index 0b9d30b..a540a33 100644 --- a/parse_test.go +++ b/parse_test.go @@ -62,7 +62,7 @@ func TestNewParserChans(t *testing.T) { func TestParsing0Calendars(t *testing.T) { parser := New() - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -78,7 +78,7 @@ func TestParsing1Calendars(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/2eventsCal.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -106,7 +106,7 @@ func TestParsing2Calendars(t *testing.T) { input := parser.GetInputChan() input <- "testCalendars/2eventsCal.ics" input <- "testCalendars/3eventsNoAttendee.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -133,7 +133,7 @@ func TestParsingNotExistingCalendar(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/notFound.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -151,7 +151,7 @@ func TestParsingNotExistingAndExistingCalendars(t *testing.T) { input := parser.GetInputChan() input <- "testCalendars/3eventsNoAttendee.ics" input <- "testCalendars/notFound.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -177,7 +177,7 @@ func TestParsingWrongCalendarUrls(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "http://localhost/goTestFails" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -204,7 +204,7 @@ func TestCreatingTempDir(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "https://www.google.com/calendar/ical/yordanpulov%40gmail.com/private-81525ac0eb14cdc2e858c15e1b296a1c/basic.ics" - parser.Wait() + parser.WaitAndClose() _, err := os.Stat(FilePath) if err != nil { t.Errorf("Failed to create %s", FilePath) @@ -219,7 +219,7 @@ func TestCalendarInfo(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/2eventsCal.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -296,7 +296,7 @@ func TestOutlookCalendarEventTimes(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/outlook.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -309,7 +309,7 @@ func TestOutlookCalendarEventTimes(t *testing.T) { calendars, errCal := parser.GetCalendars() if errCal != nil { - t.Fatalf("Failed to retrieve calendars: %s", err.Error()) + t.Fatalf("Failed to retrieve calendars: %s", errCal) } if len(calendars) < 1 { t.Fatalf("The test calendar file should have at least included one calendar") @@ -353,7 +353,7 @@ func TestCalendarEvents(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/2eventsCal.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -478,7 +478,7 @@ func TestCalendarEventAttendees(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/2eventsCal.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -566,7 +566,7 @@ func TestCalendarMultidayEvent(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/multiday.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() if err != nil { @@ -623,7 +623,7 @@ func TestCalendarMultidayEventWithDurationInsteadOfEndDate(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/multiday_duration.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() if err != nil { @@ -681,7 +681,7 @@ func TestPagerDutyCalendarEventTimes(t *testing.T) { parser := New() input := parser.GetInputChan() input <- "testCalendars/pagerduty.ics" - parser.Wait() + parser.WaitAndClose() parseErrors, err := parser.GetErrors() @@ -694,7 +694,7 @@ func TestPagerDutyCalendarEventTimes(t *testing.T) { calendars, errCal := parser.GetCalendars() if errCal != nil { - t.Fatalf("Failed to retrieve calendars: %s", err.Error()) + t.Fatalf("Failed to retrieve calendars: %s", errCal) } if len(calendars) < 1 { t.Fatalf("The test calendar file should have at least included one calendar")