feat: add hhu calendar

This commit is contained in:
cato-001 2026-01-02 22:47:28 +01:00
commit 3e4c6c77cb
35 changed files with 4116 additions and 0 deletions

1
src/vendor/github.com/jordic/goics/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
.idea

17
src/vendor/github.com/jordic/goics/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,17 @@
language: go
matrix:
include:
- go: "tip"
- go: "1.x"
- go: "1.16"
- go: "1.15"
- go: "1.14"
- go: "1.13"
- go: "1.12"
- go: "1.11"
- go: "1.10"
- go: "1.9"
- go: "1.8"
allow_failures:
- go: tip

22
src/vendor/github.com/jordic/goics/LICENSE generated vendored Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Jordi Collell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

44
src/vendor/github.com/jordic/goics/README.md generated vendored Normal file
View file

@ -0,0 +1,44 @@
### A go toolkit for decoding, encoding icalendar ics ical files
Alpha status
[![Build Status](https://travis-ci.org/jordic/goics.svg?branch=master)](https://travis-ci.org/jordic/goics)
After trying to decode some .ics files and review available go packages, I decided to start writing this pacakge.
First attempt was from a fixed structure, similar to that needed. Later, I started to investigate the format and discovered that it is a pain, and has many variants, depending on who implements it. For this reason I evolved it to a tookit for decode and encode the format.
**Check examples dir for user cases:**
[Demo app encoding an ical, using a mysql DB source](examples/mysqlsource)
Features implemented:
- Parsing and writing of vevent, not completly..
- No recursive events,
- And no alarms inside events
Follows:
http://tools.ietf.org/html/rfc5545
TODO
--
Integrate testing from:
https://github.com/libical/libical
CHANGELOG:
--
- v00. First api traial
- v01. Api evolves to a ical toolkit
- v02. Added example of integration with a mysql db.
Thanks to:
Joe Shaw Reviewing first revision.

178
src/vendor/github.com/jordic/goics/decoder.go generated vendored Normal file
View file

@ -0,0 +1,178 @@
package goics
import (
"bufio"
"errors"
"io"
"strings"
)
const (
keySep = ":"
vBegin = "BEGIN"
vCalendar = "VCALENDAR"
vEnd = "END"
vEvent = "VEVENT"
maxLineRead = 65
)
// Errors
var (
ErrCalendarNotFound = errors.New("vCalendar not found")
ErrParseEndCalendar = errors.New("wrong format END:VCALENDAR not Found")
)
type decoder struct {
scanner *bufio.Scanner
err error
Calendar *Calendar
currentEvent *Event
nextFn stateFn
prevFn stateFn
current string
buffered string
line int
}
type stateFn func(*decoder)
// NewDecoder creates an instance of de decoder
func NewDecoder(r io.Reader) *decoder {
d := &decoder{
scanner: bufio.NewScanner(r),
nextFn: decodeInit,
line: 0,
buffered: "",
}
return d
}
func (d *decoder) Decode(c ICalConsumer) error {
d.next()
if d.Calendar == nil {
d.err = ErrCalendarNotFound
d.Calendar = &Calendar{}
}
// If theres no error but, nextFn is not reset
// last element not closed
if d.nextFn != nil && d.err == nil {
d.err = ErrParseEndCalendar
}
if d.err != nil {
return d.err
}
d.err = c.ConsumeICal(d.Calendar, d.err)
return d.err
}
// Lines processed. If Decoder reports an error.
// Error
func (d *decoder) Lines() int {
return d.line
}
// Current Line content
func (d *decoder) CurrentLine() string {
return d.current
}
// Advances a new line in the decoder
// And calls the next stateFunc
// checks if next line is continuation line
func (d *decoder) next() {
// If there's not buffered line
if d.buffered == "" {
res := d.scanner.Scan()
if true != res {
d.err = d.scanner.Err()
return
}
d.line++
d.current = d.scanner.Text()
} else {
d.current = d.buffered
d.buffered = ""
}
for d.scanner.Scan() {
d.line++
line := d.scanner.Text()
if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
d.current = d.current + line[1:]
} else {
// If is not a continuation line, buffer it, for the
// next call.
d.buffered = line
break
}
}
d.err = d.scanner.Err()
if d.nextFn != nil {
d.nextFn(d)
}
}
func decodeInit(d *decoder) {
node := DecodeLine(d.current)
if node.Key == vBegin && node.Val == vCalendar {
d.Calendar = &Calendar{
Data: make(map[string]*IcsNode),
}
d.prevFn = decodeInit
d.nextFn = decodeInsideCal
d.next()
return
}
d.next()
}
func decodeInsideCal(d *decoder) {
node := DecodeLine(d.current)
switch {
case node.Key == vBegin && node.Val == vEvent:
d.currentEvent = &Event{
Data: make(map[string]*IcsNode),
List: make(map[string][]*IcsNode),
}
d.nextFn = decodeInsideEvent
d.prevFn = decodeInsideCal
case node.Key == vEnd && node.Val == vCalendar:
d.nextFn = nil
default:
d.Calendar.Data[node.Key] = node
}
d.next()
}
func decodeInsideEvent(d *decoder) {
node := DecodeLine(d.current)
if node.Key == vEnd && node.Val == vEvent {
// Come back to parent node
d.nextFn = d.prevFn
d.Calendar.Events = append(d.Calendar.Events, d.currentEvent)
d.next()
return
}
//@todo handle Valarm
//@todo handle error if we found a startevent without closing pass one
// #2 handle multiple equal keys. ej. Attendee
// List keys already set
if _, ok := d.currentEvent.List[node.Key]; ok {
d.currentEvent.List[node.Key] = append(d.currentEvent.List[node.Key], node)
} else {
// Multiple key detected...
if val, ok := d.currentEvent.Data[node.Key]; ok {
d.currentEvent.List[node.Key] = []*IcsNode{val, node}
delete(d.currentEvent.Data, node.Key)
} else {
d.currentEvent.Data[node.Key] = node
}
}
d.next()
}

120
src/vendor/github.com/jordic/goics/decoder_line.go generated vendored Normal file
View file

@ -0,0 +1,120 @@
package goics
import (
"strings"
)
const (
vParamSep = ";"
)
// IcsNode is a basic token.., with, key, val, and extra params
// to define the type of val.
type IcsNode struct {
Key string
Val string
Params map[string]string
}
// ParamsLen returns how many params has a token
func (n *IcsNode) ParamsLen() int {
if n.Params == nil {
return 0
}
return len(n.Params)
}
// GetOneParam resturns the first param found
// usefull when you know that there is a only
// one param token
func (n *IcsNode) GetOneParam() (string, string) {
if n.ParamsLen() == 0 {
return "", ""
}
var key, val string
for k, v := range n.Params {
key, val = k, v
break
}
return key, val
}
// DecodeLine extracts key, val and extra params from a line
func DecodeLine(line string) *IcsNode {
if strings.Contains(line, keySep) == false {
return &IcsNode{}
}
key, val := getKeyVal(line)
//@todo test if val containes , multipleparams
if strings.Contains(key, vParamSep) == false {
return &IcsNode{
Key: key,
Val: val,
}
}
// Extract key
firstParam := strings.Index(key, vParamSep)
realkey := key[0:firstParam]
n := &IcsNode{
Key: realkey,
Val: val,
}
// Extract params
params := key[firstParam+1:]
n.Params = decodeParams(params)
return n
}
// decode extra params linked in key val in the form
// key;param1=val1:val
func decodeParams(arr string) map[string]string {
p := make(map[string]string)
var isQuoted = false
var isParam = true
var curParam string
var curVal string
for _, c := range arr {
switch {
// if string is quoted, wait till next quote
// and capture content
case c == '"':
if isQuoted == false {
isQuoted = true
} else {
p[curParam] = curVal
isQuoted = false
}
case c == '=' && isQuoted == false:
isParam = false
case c == ';' && isQuoted == false:
isParam = true
p[curParam] = curVal
curParam = ""
curVal = ""
default:
if isParam {
curParam = curParam + string(c)
} else {
curVal = curVal + string(c)
}
}
}
p[curParam] = curVal
return p
}
// Returns a key, val... for a line..
func getKeyVal(s string) (key, value string) {
p := strings.SplitN(s, keySep, 2)
return p[0], icsReplacer.Replace(p[1])
}
var icsReplacer = strings.NewReplacer(
`\,`, ",",
`\;`, ";",
`\\`, `\`,
`\n`, "\n",
`\N`, "\n",
)

49
src/vendor/github.com/jordic/goics/decoder_types.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
package goics
import (
"time"
)
// DateDecode Decodes a date in the distincts formats
func (n *IcsNode) DateDecode() (time.Time, error) {
// DTEND;VALUE=DATE:20140406
if val, ok := n.Params["VALUE"]; ok {
switch {
case val == "DATE":
t, err := time.Parse("20060102", n.Val)
if err != nil {
return time.Time{}, err
}
return t, nil
case val == "DATE-TIME":
t, err := time.Parse("20060102T150405", n.Val)
if err != nil {
return time.Time{}, err
}
return t, nil
}
}
// DTSTART;TZID=Europe/Paris:20140116T120000
if val, ok := n.Params["TZID"]; ok {
loc, err := time.LoadLocation(val)
if err != nil {
return time.Time{}, err
}
t, err := time.ParseInLocation("20060102T150405", n.Val, loc)
if err != nil {
return time.Time{}, err
}
return t, nil
}
//DTSTART:19980119T070000Z utf datetime
if len(n.Val) == 16 {
t, err := time.Parse("20060102T150405Z", n.Val)
if err != nil {
return time.Time{}, err
}
return t, nil
}
return time.Time{}, nil
}

98
src/vendor/github.com/jordic/goics/doc.go generated vendored Normal file
View file

@ -0,0 +1,98 @@
/*
Package goics is a toolkit for encoding and decoding ics/Ical/icalendar files.
This is a work in progress project, that will try to incorporate as many exceptions and variants of the format.
This is a toolkit because user has to define the way it needs the data. The idea is builded with something similar to the consumer/provider pattern.
We want to decode a stream of vevents from a .ics file into a custom type Events
type Event struct {
Start, End time.Time
Id, Summary string
}
type Events []Event
func (e *Events) ConsumeICal(c *goics.Calendar, err error) error {
for _, el := range c.Events {
node := el.Data
dtstart, err := node["DTSTART"].DateDecode()
if err != nil {
return err
}
dtend, err := node["DTEND"].DateDecode()
if err != nil {
return err
}
d := Event{
Start: dtstart,
End: dtend,
Id: node["UID"].Val,
Summary: node["SUMMARY"].Val,
}
*e = append(*e, d)
}
return nil
}
Our custom type, will need to implement ICalConsumer interface, where,
the type will pick up data from the format.
The decoding process will be somthing like this:
d := goics.NewDecoder(strings.NewReader(testConsumer))
consumer := Events{}
err := d.Decode(&consumer)
I have choosed this model, because, this format is a pain and also I don't like a lot the reflect package.
For encoding objects to iCal format, something similar has to be done:
The object emitting elements for the encoder, will have to implement the ICalEmiter, returning a Component structure to be encoded.
This also had been done, because every package could require to encode vals and keys their way. Just for encoding time, I found more than
three types of lines.
type EventTest struct {
component goics.Componenter
}
func (evt *EventTest) EmitICal() goics.Componenter {
return evt.component
}
The Componenter, is an interface that every Component that can be encoded to ical implements.
c := goics.NewComponent()
c.SetType("VCALENDAR")
c.AddProperty("CALSCAL", "GREGORIAN")
c.AddProperty("PRODID", "-//tmpo.io/src/goics")
m := goics.NewComponent()
m.SetType("VEVENT")
m.AddProperty("UID", "testing")
c.AddComponent(m)
Properties, had to be stored as strings, the conversion from origin type to string format, must be done,
on the emmiter. There are some helpers for date conversion and on the future I will add more, for encoding
params on the string, and also for handling lists and recurrent events.
A simple example not functional used for testing:
c := goics.NewComponent()
c.SetType("VCALENDAR")
c.AddProperty("CALSCAL", "GREGORIAN")
ins := &EventTest{
component: c,
}
w := &bytes.Buffer{}
enc := goics.NewICalEncode(w)
enc.Encode(ins)
*/
package goics

151
src/vendor/github.com/jordic/goics/encoder.go generated vendored Normal file
View file

@ -0,0 +1,151 @@
package goics
import (
"io"
"sort"
"strings"
"time"
)
// Line endings
const (
CRLF = "\r\n"
CRLFSP = "\r\n "
)
// NewComponent returns a new Component and setups
// and setups Properties map for the component
// and also allows more Components inside it.
// VCALENDAR is a Component that has VEVENTS,
// VEVENTS can hold VALARMS
func NewComponent() *Component {
return &Component{
Elements: make([]Componenter, 0),
Properties: make(map[string][]string),
}
}
// Component is the base type for holding a
// ICal datatree before serilizing it
type Component struct {
Tipo string
Elements []Componenter
Properties map[string][]string
}
// Writes the component to the Writer
func (c *Component) Write(w *ICalEncode) {
w.WriteLine("BEGIN:" + c.Tipo + CRLF)
// Iterate over component properties
var keys []string
for k := range c.Properties {
keys = append(keys, k)
}
sort.Strings(keys)
for _, key := range keys {
vals := c.Properties[key]
for _, val := range vals {
w.WriteLine(WriteStringField(key, val))
}
}
for _, xc := range c.Elements {
xc.Write(w)
}
w.WriteLine("END:" + c.Tipo + CRLF)
}
// SetType of the component, as
// VCALENDAR VEVENT...
func (c *Component) SetType(t string) {
c.Tipo = t
}
// AddComponent to the base component, just for building
// the component tree
func (c *Component) AddComponent(cc Componenter) {
c.Elements = append(c.Elements, cc)
}
// AddProperty adds a property to the component.
func (c *Component) AddProperty(key string, val string) {
c.Properties[key] = append(c.Properties[key], val)
}
// ICalEncode is the real writer, that wraps every line,
// in 75 chars length... Also gets the component from the emmiter
// and starts the iteration.
type ICalEncode struct {
w io.Writer
}
// NewICalEncode generates a new encoder, and needs a writer
func NewICalEncode(w io.Writer) *ICalEncode {
return &ICalEncode{
w: w,
}
}
// Encode the Component into the ical format
func (enc *ICalEncode) Encode(c ICalEmiter) {
component := c.EmitICal()
component.Write(enc)
}
// LineSize of the ics format
var LineSize = 75
// WriteLine in ics format max length = LineSize
// continuation lines start with a space.
func (enc *ICalEncode) WriteLine(s string) {
if len(s) <= LineSize {
io.WriteString(enc.w, s)
return
}
// The first line does not begin with a space.
firstLine := truncateString(s, LineSize-len(CRLF))
io.WriteString(enc.w, firstLine+CRLF)
// Reserve three bytes for space + CRLF.
lines := splitLength(s[len(firstLine):], LineSize-len(CRLFSP))
for i, line := range lines {
if i < len(lines)-1 {
io.WriteString(enc.w, " "+line+CRLF)
} else {
// This is the last line, don't append CRLF.
io.WriteString(enc.w, " "+line)
}
}
}
// FormatDateField returns a formated date: "DTEND;VALUE=DATE:20140406"
func FormatDateField(key string, val time.Time) (string, string) {
return key + ";VALUE=DATE", val.Format("20060102")
}
// FormatDateTimeField in the form "X-MYDATETIME;VALUE=DATE-TIME:20120901T130000"
func FormatDateTimeField(key string, val time.Time) (string, string) {
return key + ";VALUE=DATE-TIME", val.Format("20060102T150405")
}
// FormatDateTime as "DTSTART:19980119T070000Z"
func FormatDateTime(key string, val time.Time) (string, string) {
return key, val.UTC().Format("20060102T150405Z")
}
// WriteStringField UID:asdfasdfаs@dfasdf.com
func WriteStringField(key string, val string) string {
return strings.ToUpper(key) + ":" + quoteString(val) + CRLF
}
func quoteString(s string) string {
s = strings.Replace(s, "\\", "\\\\", -1)
s = strings.Replace(s, ";", "\\;", -1)
s = strings.Replace(s, ",", "\\,", -1)
s = strings.Replace(s, "\n", "\\n", -1)
return s
}

46
src/vendor/github.com/jordic/goics/goics.go generated vendored Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2015 jordi collell j@tmpo.io. All rights reserved
// Package goics implements an ical encoder and decoder.
// First release will include decode and encoder of Event types
package goics
// ICalConsumer is the realy important part of the decoder lib
// The decoder is organized around the Provider/Consumer pattern.
// the decoder acts as a consummer producing IcsNode's and
// Every data type that wants to receive data, must implement
// the consumer pattern.
type ICalConsumer interface {
ConsumeICal(d *Calendar, err error) error
}
// ICalEmiter must be implemented in order to allow objects to be serialized
// It should return a *goics.Calendar and optional a map of fields and
// their serializers, if no serializer is defined, it will serialize as
// string..
type ICalEmiter interface {
EmitICal() Componenter
}
// Componenter defines what should be a component that can be rendered with
// others components inside and some properties
// CALENDAR >> VEVENT ALARM VTODO
type Componenter interface {
Write(w *ICalEncode)
AddComponent(c Componenter)
SetType(t string)
AddProperty(string, string)
}
// Calendar holds the base struct for a Component VCALENDAR
type Calendar struct {
Data map[string]*IcsNode // map of every property found on ics file
Events []*Event // slice of events founds in file
}
// Event holds the base struct for a Event Component during decoding
type Event struct {
Data map[string]*IcsNode
Alarms []*map[string]*IcsNode
// Stores multiple keys for the same property... ( a list )
List map[string][]*IcsNode
}

40
src/vendor/github.com/jordic/goics/string.go generated vendored Normal file
View file

@ -0,0 +1,40 @@
package goics
// splitLength returns a slice of strings, each string is at most length bytes long.
// Does not break UTF-8 codepoints.
func splitLength(s string, length int) []string {
var ret []string
for len(s) > 0 {
tmp := truncateString(s, length)
if len(tmp) == 0 {
// too short length, or invalid UTF-8 string
break
}
ret = append(ret, tmp)
s = s[len(tmp):]
}
return ret
}
// truncateString truncates s to a maximum of length bytes without breaking UTF-8 codepoints.
func truncateString(s string, length int) string {
if len(s) <= length {
return s
}
// UTF-8 continuation bytes start with 10xx xxxx:
// 0xc0 = 1100 0000
// 0x80 = 1000 0000
cutoff := length
for s[cutoff]&0xc0 == 0x80 {
cutoff--
if cutoff < 0 {
cutoff = 0
break
}
}
return s[0:cutoff]
}