8 Commits

158 changed files with 15189 additions and 128 deletions

View File

@ -1,101 +0,0 @@
package calendar
import (
"math"
"time"
)
type Calendar struct {
Location *time.Location
}
func (cal *Calendar) GetEasterDay(year int) time.Time {
g := float64(year % 19.0)
c := math.Floor(float64(year) / 100.0)
c4 := math.Floor(c / 4.0)
h := float64(int(19.0*g+c-c4-math.Floor((8.0*c+13)/25)+15) % 30.0)
k := math.Floor(h / 28.0)
i := (k*math.Floor(29./(h+1.))*math.Floor((21.-g)/11.)-1.)*k + h
// jour de Pâques (0=dimanche, 1=lundi....)
dayWeek := int(math.Floor(float64(year)/4.)+float64(year)+i+2+c4-c) % 7
// Jour de Pâques en jours enpartant de 1 = 1er mars
presJour := int(28 + int(i) - dayWeek)
// mois (0 = janvier, ... 2 = mars, 3 = avril)
month := 2
if presJour > 31 {
month = 3
}
// Mois dans l'année
month += 1
// jour du mois
day := presJour - 31
if month == 2 {
day = presJour
}
return time.Date(year, 3, 31, 0, 0, 0, 0, cal.Location).AddDate(0, 0, day)
}
func (cal *Calendar) GetHolidays(year int) *[]time.Time {
// Calcul du jour de pâques
paques := cal.GetEasterDay(year)
joursFeries := []time.Time{
// Jour de l'an
time.Date(year, time.January, 1, 0, 0, 0, 0, cal.Location),
// Easter
paques,
// 1 mai
time.Date(year, time.May, 1, 0, 0, 0, 0, cal.Location),
// 8 mai
time.Date(year, time.May, 8, 0, 0, 0, 0, cal.Location),
// Ascension
paques.AddDate(0, 0, 39),
// 14 juillet
time.Date(year, time.July, 14, 0, 0, 0, 0, cal.Location),
// 15 aout
time.Date(year, time.August, 15, 0, 0, 0, 0, cal.Location),
// Toussaint
time.Date(year, time.November, 1, 0, 0, 0, 0, cal.Location),
// 11 novembre
time.Date(year, time.November, 11, 0, 0, 0, 0, cal.Location),
// noël
time.Date(year, time.December, 25, 0, 0, 0, 0, cal.Location),
}
return &joursFeries
}
func (cal *Calendar) GetHolidaysSet(year int) *map[time.Time]bool {
holidays := cal.GetHolidays(year)
result := make(map[time.Time]bool, len(*holidays))
for _, h := range *holidays {
result[h] = true
}
return &result
}
func(cal *Calendar) IsHoliday(date time.Time) bool{
h := cal.GetHolidaysSet(date.Year())
d := date.In(cal.Location)
day := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, cal.Location)
return (*h)[day]
}
func (cal *Calendar) IsWorkingDay(date time.Time) bool {
return !cal.IsHoliday(date) && date.Weekday() >= time.Monday && date.Weekday() <= time.Friday
}
func (cal *Calendar) IsWorkingDayToday() bool {
return cal.IsWorkingDay(time.Now())
}
func (cal *Calendar) IsWeekDay(day time.Time) bool{
return day.Weekday() >= time.Monday && day.Weekday() <= time.Friday
}

View File

@ -2,22 +2,27 @@ package main
import (
"context"
"domogeek/calendar"
"domogeek/pkg/calendar"
"encoding/json"
"flag"
"fmt"
"github.com/hellofresh/health-go/v4"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"log"
"net/http"
"net/url"
"os"
"os/signal"
"syscall"
"time"
)
var (
cal calendar.Calendar
cal *calendar.Calendar
location *time.Location
calCounter *prometheus.CounterVec
calSummary *prometheus.SummaryVec
calHistogram *prometheus.HistogramVec
@ -26,9 +31,9 @@ var (
func init() {
loc, err := time.LoadLocation("Europe/Paris")
if err != nil {
log.Fatalf("unable to load time location: %v", err)
zap.S().Fatalf("unable to load time location: %v", err)
}
cal = calendar.Calendar{Location: loc}
location = loc
calCounter = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: "domogeek",
@ -67,25 +72,31 @@ type CalendarDay struct {
type CalendarHandler struct{}
func (c *CalendarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (c *CalendarHandler) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
now := time.Now()
calDavHolidays, err := cal.IsHolidaysFromCaldav(now)
if err != nil {
zap.S().Warnf("unable to read holiday status from caldav: %v", err)
calDavHolidays = false
}
cd := CalendarDay{
Day: now,
WorkingDay: cal.IsWorkingDay(now),
Ferie: cal.IsHoliday(now),
Holiday: cal.IsHoliday(now),
Holiday: calDavHolidays,
Weekday: cal.IsWeekDay(now),
}
content, err := json.Marshal(cd)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("unable to marshall response %v, %v", content, err)
zap.S().Errorf("unable to marshall response %v, %v", content, err)
} else {
_, err = w.Write(content)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
log.Printf("unable to marshall response %v, :%v", content, err)
zap.S().Errorf("unable to marshall response %v, :%v", content, err)
}
}
}
@ -93,13 +104,55 @@ func (c *CalendarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func main() {
var port int
var host string
var user, pwd string
var caldavUrl, caldavPath, caldavSummaryPattern string
flag.StringVar(&host, "host", "", "host to listen, default all addresses")
flag.IntVar(&port, "port", 8080, "port to listen")
flag.StringVar(&caldavUrl, "caldav-url", "", "caldav url to use to read holidays events")
flag.StringVar(&caldavPath, "caldav-path", "", "caldav path to use to read holidays events")
flag.StringVar(&caldavSummaryPattern, "caldav-summary-pattern", "Holidays", "Summary pattern that matches holidays event")
flag.StringVar(&user, "caldav-username", "", "Username credential")
flag.StringVar(&pwd, "caldav-password", "", "Password credential")
flag.Parse()
logLevel := zap.LevelFlag("log", zap.InfoLevel, "log level")
flag.Parse()
if len(os.Args) <= 1 {
flag.PrintDefaults()
os.Exit(1)
}
config := zap.NewDevelopmentConfig()
config.Level = zap.NewAtomicLevelAt(*logLevel)
lgr, err := config.Build()
if err != nil {
log.Fatalf("unable to init logger: %v", err)
}
defer func() {
_ = lgr.Sync()
}()
zap.ReplaceGlobals(lgr)
urlCaldav, err := url.Parse(caldavUrl)
if err != nil {
zap.S().Panicf("invalid caldav url '%v': %v", caldavUrl, err)
}
urlCaldav.User = url.UserPassword(user, pwd)
cdav, err := calendar.NewCaldav(urlCaldav.String(), caldavPath)
if err != nil {
zap.S().Fatal("unable to init caldav instance")
}
cal = calendar.New(location,
calendar.WithCaldav(cdav),
calendar.WithCaldavPath(caldavPath),
calendar.WithCaldavSummaryPattern(caldavSummaryPattern),
)
addr := fmt.Sprintf("%s:%d", host, port)
log.Printf("start server on %s", addr)
zap.S().Infof("start server on %s", addr)
h := promhttp.InstrumentHandlerDuration(
calHistogram,
@ -117,9 +170,28 @@ func main() {
Check: func(ctx context.Context) error {
return nil
},
},
))
}),
health.WithChecks(health.Config{
Name: "caldav",
Timeout: 5 * time.Second,
SkipOnErr: false,
Check: func(ctx context.Context) error {
_, err := cal.IsHolidaysFromCaldav(time.Now())
if err != nil {
zap.S().Warnf("unable to check caldav connection: %v", err)
}
return err
},
}),
)
http.Handle("/status", healthz.Handler())
log.Fatal(http.ListenAndServe(addr, nil))
signChan := make(chan os.Signal, 1)
go func() {
zap.S().Fatal(http.ListenAndServe(addr, nil))
}()
signal.Notify(signChan, syscall.SIGTERM)
<-signChan
zap.S().Info("exit on sigterm")
}

5
go.mod
View File

@ -3,8 +3,11 @@ module domogeek
go 1.18
require (
github.com/avast/retry-go v2.7.0+incompatible
github.com/dolanor/caldav-go v0.2.1
github.com/hellofresh/health-go/v4 v4.5.0
github.com/prometheus/client_golang v1.12.1
go.uber.org/zap v1.21.0
)
require (
@ -17,6 +20,8 @@ require (
github.com/prometheus/procfs v0.7.3 // indirect
go.opentelemetry.io/otel v1.0.0 // indirect
go.opentelemetry.io/otel/trace v1.0.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect
google.golang.org/protobuf v1.26.0 // indirect
)

29
go.sum
View File

@ -39,6 +39,10 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/avast/retry-go v2.7.0+incompatible h1:XaGnzl7gESAideSjr+I8Hki/JBi+Yb9baHlMRPeSC84=
github.com/avast/retry-go v2.7.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -62,6 +66,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dolanor/caldav-go v0.2.1 h1:wbARF+WKIryMOApYdX6CnFKY25Kz3ChPerfFc/R3Txk=
github.com/dolanor/caldav-go v0.2.1/go.mod h1:0A9uEq2TN7U1eNh11hHAZ0FA7jyuFSh5hXeaoFPC0fc=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -226,9 +232,11 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -263,6 +271,7 @@ github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+t
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -325,6 +334,7 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.mongodb.org/mongo-driver v1.7.2/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -340,13 +350,21 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
@ -391,6 +409,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -424,6 +443,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -443,6 +463,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -489,7 +510,9 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
@ -558,6 +581,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -645,6 +669,7 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -656,9 +681,11 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

203
pkg/calendar/calendar.go Normal file
View File

@ -0,0 +1,203 @@
package calendar
import (
"fmt"
"github.com/avast/retry-go"
"github.com/dolanor/caldav-go/caldav"
"github.com/dolanor/caldav-go/caldav/entities"
"github.com/dolanor/caldav-go/icalendar/components"
"go.uber.org/zap"
"math"
"net/http"
"strings"
"time"
)
type Caldav interface {
QueryEvents(path string, query *entities.CalendarQuery) (events []*components.Event, oerr error)
}
type Calendar struct {
Location *time.Location
cdav Caldav
caldavPath string
caldavSummaryPattern string
}
func NewCaldav(caldavUrl, caldavPath string) (Caldav, error) {
// create a reference to your CalDAV-compliant server
server, _ := caldav.NewServer(caldavUrl)
// create a CalDAV client to speak to the server
var client = caldav.NewClient(server, http.DefaultClient)
err := retry.Do(
func() error {
// start executing requests!
err := client.ValidateServer(caldavPath)
if err != nil {
return fmt.Errorf("bad caldav configuration, unable to validate connexion: %w", err)
}
return nil
},
retry.OnRetry(
func(n uint, err error) {
zap.S().Errorf("unable to validate caldav connection on retry %d: %v", n, err)
},
),
retry.Attempts(0), // Infinite attempts
retry.DelayType(retry.BackOffDelay),
)
if err != nil {
return nil, fmt.Errorf("unable to validate caldav connection: %w", err)
}
return client, nil
}
type Option func(calendar *Calendar)
func WithCaldav(cdav Caldav) Option {
return func(calendar *Calendar) {
calendar.cdav = cdav
}
}
func WithCaldavSummaryPattern(caldavSummaryPattern string) Option {
return func(calendar *Calendar) {
calendar.caldavSummaryPattern = caldavSummaryPattern
}
}
func WithCaldavPath(caldavPath string) Option {
return func(calendar *Calendar) {
calendar.caldavPath = caldavPath
}
}
func New(location *time.Location, opts ...Option) *Calendar {
c := &Calendar{
location,
nil,
"",
"",
}
for _, opt := range opts {
opt(c)
}
return c
}
func (cal *Calendar) GetEasterDay(year int) time.Time {
g := float64(year % 19.0)
c := math.Floor(float64(year) / 100.0)
c4 := math.Floor(c / 4.0)
h := float64(int(19.0*g+c-c4-math.Floor((8.0*c+13)/25)+15) % 30.0)
k := math.Floor(h / 28.0)
i := (k*math.Floor(29./(h+1.))*math.Floor((21.-g)/11.)-1.)*k + h
// jour de Pâques (0=dimanche, 1=lundi....)
dayWeek := int(math.Floor(float64(year)/4.)+float64(year)+i+2+c4-c) % 7
// Jour de Pâques en jours enpartant de 1 = 1er mars
presJour := int(28 + int(i) - dayWeek)
// mois (0 = janvier, ... 2 = mars, 3 = avril)
month := 2
if presJour > 31 {
month = 3
}
// Mois dans l'année
month += 1
// jour du mois
day := presJour - 31
if month == 2 {
day = presJour
}
return time.Date(year, 3, 31, 0, 0, 0, 0, cal.Location).AddDate(0, 0, day)
}
func (cal *Calendar) GetHolidays(year int) *[]time.Time {
// Calcul du jour de pâques
paques := cal.GetEasterDay(year)
joursFeries := []time.Time{
// Jour de l'an
time.Date(year, time.January, 1, 0, 0, 0, 0, cal.Location),
// Easter
paques.AddDate(0, 0, 1),
// 1 mai
time.Date(year, time.May, 1, 0, 0, 0, 0, cal.Location),
// 8 mai
time.Date(year, time.May, 8, 0, 0, 0, 0, cal.Location),
// Ascension
paques.AddDate(0, 0, 39),
// 14 juillet
time.Date(year, time.July, 14, 0, 0, 0, 0, cal.Location),
// 15 aout
time.Date(year, time.August, 15, 0, 0, 0, 0, cal.Location),
// Toussaint
time.Date(year, time.November, 1, 0, 0, 0, 0, cal.Location),
// 11 novembre
time.Date(year, time.November, 11, 0, 0, 0, 0, cal.Location),
// noël
time.Date(year, time.December, 25, 0, 0, 0, 0, cal.Location),
}
return &joursFeries
}
func (cal *Calendar) GetHolidaysSet(year int) map[time.Time]bool {
holidays := cal.GetHolidays(year)
result := make(map[time.Time]bool, len(*holidays))
for _, h := range *holidays {
result[h] = true
}
return result
}
func (cal *Calendar) IsHoliday(date time.Time) bool {
h := cal.GetHolidaysSet(date.Year())
d := date.In(cal.Location)
day := time.Date(d.Year(), d.Month(), d.Day(), 0, 0, 0, 0, cal.Location)
caldavHolidays, err := cal.IsHolidaysFromCaldav(day)
if err != nil {
zap.S().Errorf("unable to check holidays from caldav: %v", err)
}
return h[day] || caldavHolidays
}
func (cal *Calendar) IsWorkingDay(date time.Time) bool {
return !cal.IsHoliday(date) && date.Weekday() >= time.Monday && date.Weekday() <= time.Friday
}
func (cal *Calendar) IsWorkingDayToday() bool {
return cal.IsWorkingDay(time.Now())
}
func (cal *Calendar) IsWeekDay(day time.Time) bool {
return day.Weekday() >= time.Monday && day.Weekday() <= time.Friday
}
func (cal *Calendar) IsHolidaysFromCaldav(day time.Time) (bool, error) {
if cal.cdav == nil {
return false, nil
}
query, err := entities.NewEventRangeQuery(day.UTC(), day.UTC().Add(23*time.Hour+59*time.Minute))
if err != nil {
return false, fmt.Errorf("unable to build events range query: %v", err)
}
events, err := cal.cdav.QueryEvents(cal.caldavPath, query)
if err != nil {
return false, fmt.Errorf("unable list events from caldav: %v", err)
}
for _, evt := range events {
if strings.Contains(evt.Summary, cal.caldavSummaryPattern) {
return true, nil
}
}
return false, nil
}

View File

@ -1,6 +1,9 @@
package calendar
import (
"github.com/dolanor/caldav-go/caldav/entities"
"github.com/dolanor/caldav-go/icalendar/components"
"github.com/dolanor/caldav-go/icalendar/values"
"testing"
"time"
)
@ -18,7 +21,7 @@ func TestCalendar_GetEasterDay(t *testing.T) {
time.Date(2021, time.April, 4, 0, 0, 0, 0, loc),
}
c := Calendar{loc}
c := New(loc)
for _, d := range easterDays {
easter := c.GetEasterDay(d.Year())
@ -37,7 +40,7 @@ func TestCalendar_GetHolidays(t *testing.T) {
expectedHolidays := map[time.Time]bool{
time.Date(2020, time.January, 1, 0, 0, 0, 0, loc): true,
time.Date(2020, time.April, 12, 0, 0, 0, 0, loc): true,
time.Date(2020, time.April, 13, 0, 0, 0, 0, loc): true,
time.Date(2020, time.May, 1, 0, 0, 0, 0, loc): true,
time.Date(2020, time.May, 8, 0, 0, 0, 0, loc): true,
time.Date(2020, time.May, 21, 0, 0, 0, 0, loc): true,
@ -48,7 +51,7 @@ func TestCalendar_GetHolidays(t *testing.T) {
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc): true,
}
c := Calendar{loc}
c := New(loc)
holidays := c.GetHolidays(2020)
if len(*holidays) != len(expectedHolidays) {
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
@ -69,7 +72,7 @@ func TestCalendar_GetHolidaysSet(t *testing.T) {
expectedHolidays := []time.Time{
time.Date(2020, time.January, 1, 0, 0, 0, 0, loc),
time.Date(2020, time.April, 12, 0, 0, 0, 0, loc),
time.Date(2020, time.April, 13, 0, 0, 0, 0, loc),
time.Date(2020, time.May, 1, 0, 0, 0, 0, loc),
time.Date(2020, time.May, 8, 0, 0, 0, 0, loc),
time.Date(2020, time.May, 21, 0, 0, 0, 0, loc),
@ -80,13 +83,13 @@ func TestCalendar_GetHolidaysSet(t *testing.T) {
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc),
}
c := Calendar{loc}
c := New(loc)
holidays := c.GetHolidaysSet(2020)
if len(*holidays) != len(expectedHolidays) {
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
if len(holidays) != len(expectedHolidays) {
t.Errorf("bad number of holidays, %d but %d are expected", len(holidays), len(expectedHolidays))
}
for _, h := range expectedHolidays {
if !(*holidays)[h] {
if !(holidays)[h] {
t.Errorf("%v is not a holiday", h)
}
}
@ -101,7 +104,7 @@ func TestCalendar_IsHolidays(t *testing.T) {
expectedHolidays := []time.Time{
time.Date(2020, time.January, 1, 0, 0, 0, 0, loc),
time.Date(2020, time.April, 12, 0, 0, 0, 0, loc),
time.Date(2020, time.April, 13, 0, 0, 0, 0, loc),
time.Date(2020, time.May, 1, 0, 0, 0, 0, loc),
time.Date(2020, time.May, 8, 0, 0, 0, 0, loc),
time.Date(2020, time.May, 21, 0, 0, 0, 0, loc),
@ -112,10 +115,10 @@ func TestCalendar_IsHolidays(t *testing.T) {
time.Date(2020, time.December, 25, 0, 0, 0, 0, loc),
}
c := Calendar{loc}
c := New(loc)
holidays := c.GetHolidaysSet(2020)
if len(*holidays) != len(expectedHolidays) {
t.Errorf("bad number of holidays, %d but %d are expected", len(*holidays), len(expectedHolidays))
if len(holidays) != len(expectedHolidays) {
t.Errorf("bad number of holidays, %d but %d are expected", len(holidays), len(expectedHolidays))
}
for _, h := range expectedHolidays {
if !c.IsHoliday(h) {
@ -133,7 +136,7 @@ func TestCalendar_IsWorkingDay(t *testing.T) {
t.Errorf("unable to load time location: %v", err)
t.Fail()
}
c := Calendar{loc}
c := New(loc)
if c.IsWorkingDay(time.Date(2019, time.January, 01, 0, 0, 0, 0, loc)) {
t.Error("1st january is not a working day")
@ -164,3 +167,114 @@ func TestCalendar_IsWorkingDay(t *testing.T) {
t.Error("Sunday should not be a working day")
}
}
type MockCaldav struct {
events []*components.Event
}
func (m *MockCaldav) QueryEvents(_ string, _ *entities.CalendarQuery) ([]*components.Event, error) {
return m.events, nil
}
func TestCalendar_IsHolidaysFromCaldav(t *testing.T) {
loc, err := time.LoadLocation("Europe/Paris")
if err != nil {
t.Errorf("unable to load time location: %v", err)
t.Fail()
}
type fields struct {
Location *time.Location
cdav *MockCaldav
caldavPath string
caldavSummaryPattern string
}
type args struct {
day time.Time
}
tests := []struct {
name string
fields fields
args args
want bool
wantErr bool
}{
{
name: "Holidays in events",
fields: fields{
Location: loc,
cdav: &MockCaldav{
events: []*components.Event{
{
UID: "1",
DateStart: values.NewDateTime(time.Date(2022, time.April, 16, 0, 0, 0, 0, loc)),
DateEnd: values.NewDateTime(time.Date(2022, time.April, 17, 0, 0, 0, 0, loc)),
Summary: "Holidays",
},
},
},
caldavPath: "my_calendar/",
caldavSummaryPattern: "Holidays",
},
args: args{
day: time.Date(2022, time.April, 16, 0, 0, 0, 0, loc),
},
want: true,
wantErr: false,
},
{
name: "Not Holidays in events",
fields: fields{
Location: loc,
cdav: &MockCaldav{
events: []*components.Event{
{
UID: "1",
DateStart: values.NewDateTime(time.Date(2022, time.April, 16, 0, 0, 0, 0, loc)),
DateEnd: values.NewDateTime(time.Date(2022, time.April, 17, 0, 0, 0, 0, loc)),
Summary: "Another event",
},
},
},
caldavPath: "my_calendar/",
caldavSummaryPattern: "Holidays",
},
args: args{
day: time.Date(2022, time.April, 16, 0, 0, 0, 0, loc),
},
want: false,
wantErr: false,
},
{
name: "No events",
fields: fields{
Location: loc,
cdav: &MockCaldav{},
caldavPath: "my_calendar/",
caldavSummaryPattern: "Holidays",
},
args: args{
day: time.Date(2022, time.April, 15, 0, 0, 0, 0, loc),
},
want: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cal := New(
loc,
WithCaldav(tt.fields.cdav),
WithCaldavPath(tt.fields.caldavPath),
WithCaldavSummaryPattern(tt.fields.caldavSummaryPattern),
)
got, err := cal.IsHolidaysFromCaldav(tt.args.day)
if (err != nil) != tt.wantErr {
t.Errorf("IsHolidaysFromCaldav() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("IsHolidaysFromCaldav() got = %v, want %v", got, tt.want)
}
})
}
}

21
vendor/github.com/avast/retry-go/.gitignore generated vendored Normal file
View File

@ -0,0 +1,21 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# dep
vendor/
Gopkg.lock
# cover
coverage.txt

37
vendor/github.com/avast/retry-go/.godocdown.tmpl generated vendored Normal file
View File

@ -0,0 +1,37 @@
# {{ .Name }}
[![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go)
[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go)
[![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master)
[![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](https://sourcegraph.com/github.com/avast/retry-go?badge)
{{ .EmitSynopsis }}
{{ .EmitUsage }}
## Contributing
Contributions are very much welcome.
### Makefile
Makefile provides several handy rules, like README.md `generator` , `setup` for prepare build/dev environment, `test`, `cover`, etc...
Try `make help` for more information.
### Before pull request
please try:
* run tests (`make test`)
* run linter (`make lint`)
* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`)
### README
README.md are generate from template [.godocdown.tmpl](.godocdown.tmpl) and code documentation via [godocdown](https://github.com/robertkrimen/godocdown).
Never edit README.md direct, because your change will be lost.

21
vendor/github.com/avast/retry-go/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,21 @@
language: go
go:
- 1.7
- 1.8
- 1.9
- "1.10"
- 1.11
- 1.12
- 1.13
- 1.14
- 1.15
install:
- make setup
script:
- make ci
after_success:
- bash <(curl -s https://codecov.io/bash)

3
vendor/github.com/avast/retry-go/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,3 @@
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"

21
vendor/github.com/avast/retry-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Avast
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.

65
vendor/github.com/avast/retry-go/Makefile generated vendored Normal file
View File

@ -0,0 +1,65 @@
SOURCE_FILES?=$$(go list ./... | grep -v /vendor/)
TEST_PATTERN?=.
TEST_OPTIONS?=
DEP?=$$(which dep)
VERSION?=$$(cat VERSION)
LINTER?=$$(which golangci-lint)
LINTER_VERSION=1.15.0
ifeq ($(OS),Windows_NT)
DEP_VERS=dep-windows-amd64
LINTER_FILE=golangci-lint-$(LINTER_VERSION)-windows-amd64.zip
LINTER_UNPACK= >| app.zip; unzip -j app.zip -d $$GOPATH/bin; rm app.zip
else ifeq ($(OS), Darwin)
LINTER_FILE=golangci-lint-$(LINTER_VERSION)-darwin-amd64.tar.gz
LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint"
else
DEP_VERS=dep-linux-amd64
LINTER_FILE=golangci-lint-$(LINTER_VERSION)-linux-amd64.tar.gz
LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint"
endif
setup:
go get -u github.com/pierrre/gotestcover
go get -u golang.org/x/tools/cmd/cover
go get -u github.com/robertkrimen/godocdown/godocdown
@if [ "$(LINTER)" = "" ]; then\
curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\
chmod +x $$GOPATH/bin/golangci-lint;\
fi
@if [ "$(DEP)" = "" ]; then\
curl -L https://github.com/golang/dep/releases/download/v0.3.1/$(DEP_VERS) >| $$GOPATH/bin/dep;\
chmod +x $$GOPATH/bin/dep;\
fi
dep ensure
generate: ## Generate README.md
godocdown >| README.md
test: generate test_and_cover_report lint
test_and_cover_report:
gotestcover $(TEST_OPTIONS) -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
cover: test ## Run all the tests and opens the coverage report
go tool cover -html=coverage.txt
fmt: ## gofmt and goimports all go files
find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done
lint: ## Run all the linters
golangci-lint run
ci: test_and_cover_report ## Run all the tests but no linters - use https://golangci.com integration instead
build:
go build
release: ## Release new version
git tag | grep -q $(VERSION) && echo This version was released! Increase VERSION! || git tag $(VERSION) && git push origin $(VERSION) && git tag v$(VERSION) && git push origin v$(VERSION)
# Absolutely awesome: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.DEFAULT_GOAL := build

351
vendor/github.com/avast/retry-go/README.md generated vendored Normal file
View File

@ -0,0 +1,351 @@
# retry
[![Release](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Travis](https://img.shields.io/travis/avast/retry-go.svg?style=flat-square)](https://travis-ci.org/avast/retry-go)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/fieg9gon3qlq0a9a?svg=true)](https://ci.appveyor.com/project/JaSei/retry-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go)
[![GoDoc](https://godoc.org/github.com/avast/retry-go?status.svg&style=flat-square)](http://godoc.org/github.com/avast/retry-go)
[![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=master)](https://codecov.io/github/avast/retry-go?branch=master)
[![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](https://sourcegraph.com/github.com/avast/retry-go?badge)
Simple library for retry mechanism
slightly inspired by
[Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry)
### SYNOPSIS
http get with retry:
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
fmt.Println(body)
[next examples](https://github.com/avast/retry-go/tree/master/examples)
### SEE ALSO
* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly
complicated interface.
* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for
http calls with retries and backoff
* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the
exponential backoff algorithm from Google's HTTP Client Library for Java. Really
complicated interface.
* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good,
slightly similar as this package, don't have 'simple' `Retry` method
* [matryer/try](https://github.com/matryer/try) - very popular package,
nonintuitive interface (for me)
### BREAKING CHANGES
1.0.2 -> 2.0.0
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units`
anymore)
* function `retry.Units` are removed
* [more about this breaking change](https://github.com/avast/retry-go/issues/7)
0.3.0 -> 1.0.0
* `retry.Retry` function are changed to `retry.Do` function
* `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are
now implement via functions produces Options (aka `retry.OnRetry`)
## Usage
```go
var (
DefaultAttempts = uint(10)
DefaultDelay = 100 * time.Millisecond
DefaultMaxJitter = 100 * time.Millisecond
DefaultOnRetry = func(n uint, err error) {}
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
DefaultLastErrorOnly = false
DefaultContext = context.Background()
)
```
#### func BackOffDelay
```go
func BackOffDelay(n uint, config *Config) time.Duration
```
BackOffDelay is a DelayType which increases delay between consecutive retries
#### func Do
```go
func Do(retryableFunc RetryableFunc, opts ...Option) error
```
#### func FixedDelay
```go
func FixedDelay(_ uint, config *Config) time.Duration
```
FixedDelay is a DelayType which keeps delay the same through all iterations
#### func IsRecoverable
```go
func IsRecoverable(err error) bool
```
IsRecoverable checks if error is an instance of `unrecoverableError`
#### func RandomDelay
```go
func RandomDelay(_ uint, config *Config) time.Duration
```
RandomDelay is a DelayType which picks a random delay up to config.maxJitter
#### func Unrecoverable
```go
func Unrecoverable(err error) error
```
Unrecoverable wraps an error in `unrecoverableError` struct
#### type Config
```go
type Config struct {
}
```
#### type DelayTypeFunc
```go
type DelayTypeFunc func(n uint, config *Config) time.Duration
```
#### func CombineDelay
```go
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc
```
CombineDelay is a DelayType the combines all of the specified delays into a new
DelayTypeFunc
#### type Error
```go
type Error []error
```
Error type represents list of errors in retry
#### func (Error) Error
```go
func (e Error) Error() string
```
Error method return string representation of Error It is an implementation of
error interface
#### func (Error) WrappedErrors
```go
func (e Error) WrappedErrors() []error
```
WrappedErrors returns the list of errors that this Error is wrapping. It is an
implementation of the `errwrap.Wrapper` interface in package
[errwrap](https://github.com/hashicorp/errwrap) so that `retry.Error` can be
used with that library.
#### type OnRetryFunc
```go
type OnRetryFunc func(n uint, err error)
```
Function signature of OnRetry function n = count of attempts
#### type Option
```go
type Option func(*Config)
```
Option represents an option for retry.
#### func Attempts
```go
func Attempts(attempts uint) Option
```
Attempts set count of retry default is 10
#### func Context
```go
func Context(ctx context.Context) Option
```
Context allow to set context of retry default are Background context
example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope)
ctx, cancel := context.WithCancel(context.Background())
cancel()
retry.Do(
func() error {
...
},
retry.Context(ctx),
)
#### func Delay
```go
func Delay(delay time.Duration) Option
```
Delay set delay between retry default is 100ms
#### func DelayType
```go
func DelayType(delayType DelayTypeFunc) Option
```
DelayType set type of the delay between retries default is BackOff
#### func LastErrorOnly
```go
func LastErrorOnly(lastErrorOnly bool) Option
```
return the direct last error that came from the retried function default is
false (return wrapped errors with everything)
#### func MaxDelay
```go
func MaxDelay(maxDelay time.Duration) Option
```
MaxDelay set maximum delay between retry does not apply by default
#### func MaxJitter
```go
func MaxJitter(maxJitter time.Duration) Option
```
MaxJitter sets the maximum random Jitter between retries for RandomDelay
#### func OnRetry
```go
func OnRetry(onRetry OnRetryFunc) Option
```
OnRetry function callback are called each retry
log each retry example:
retry.Do(
func() error {
return errors.New("some error")
},
retry.OnRetry(func(n uint, err error) {
log.Printf("#%d: %s\n", n, err)
}),
)
#### func RetryIf
```go
func RetryIf(retryIf RetryIfFunc) Option
```
RetryIf controls whether a retry should be attempted after an error (assuming
there are any retry attempts remaining)
skip retry if special error example:
retry.Do(
func() error {
return errors.New("special error")
},
retry.RetryIf(func(err error) bool {
if err.Error() == "special error" {
return false
}
return true
})
)
By default RetryIf stops execution if the error is wrapped using
`retry.Unrecoverable`, so above example may also be shortened to:
retry.Do(
func() error {
return retry.Unrecoverable(errors.New("special error"))
}
)
#### type RetryIfFunc
```go
type RetryIfFunc func(error) bool
```
Function signature of retry if function
#### type RetryableFunc
```go
type RetryableFunc func() error
```
Function signature of retryable function
## Contributing
Contributions are very much welcome.
### Makefile
Makefile provides several handy rules, like README.md `generator` , `setup` for prepare build/dev environment, `test`, `cover`, etc...
Try `make help` for more information.
### Before pull request
please try:
* run tests (`make test`)
* run linter (`make lint`)
* if your IDE don't automaticaly do `go fmt`, run `go fmt` (`make fmt`)
### README
README.md are generate from template [.godocdown.tmpl](.godocdown.tmpl) and code documentation via [godocdown](https://github.com/robertkrimen/godocdown).
Never edit README.md direct, because your change will be lost.

1
vendor/github.com/avast/retry-go/VERSION generated vendored Normal file
View File

@ -0,0 +1 @@
2.7.0

19
vendor/github.com/avast/retry-go/appveyor.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
version: "{build}"
clone_folder: c:\Users\appveyor\go\src\github.com\avast\retry-go
#os: Windows Server 2012 R2
platform: x64
install:
- copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe
- set GOPATH=C:\Users\appveyor\go
- set PATH=%PATH%;c:\MinGW\bin
- set PATH=%PATH%;%GOPATH%\bin;c:\go\bin
- set GOBIN=%GOPATH%\bin
- go version
- go env
- make setup
build_script:
- make ci

193
vendor/github.com/avast/retry-go/options.go generated vendored Normal file
View File

@ -0,0 +1,193 @@
package retry
import (
"context"
"math"
"math/rand"
"time"
)
// Function signature of retry if function
type RetryIfFunc func(error) bool
// Function signature of OnRetry function
// n = count of attempts
type OnRetryFunc func(n uint, err error)
type DelayTypeFunc func(n uint, config *Config) time.Duration
type Config struct {
attempts uint
delay time.Duration
maxDelay time.Duration
maxJitter time.Duration
onRetry OnRetryFunc
retryIf RetryIfFunc
delayType DelayTypeFunc
lastErrorOnly bool
context context.Context
maxBackOffN uint
}
// Option represents an option for retry.
type Option func(*Config)
// return the direct last error that came from the retried function
// default is false (return wrapped errors with everything)
func LastErrorOnly(lastErrorOnly bool) Option {
return func(c *Config) {
c.lastErrorOnly = lastErrorOnly
}
}
// Attempts set count of retry
// default is 10
func Attempts(attempts uint) Option {
return func(c *Config) {
c.attempts = attempts
}
}
// Delay set delay between retry
// default is 100ms
func Delay(delay time.Duration) Option {
return func(c *Config) {
c.delay = delay
}
}
// MaxDelay set maximum delay between retry
// does not apply by default
func MaxDelay(maxDelay time.Duration) Option {
return func(c *Config) {
c.maxDelay = maxDelay
}
}
// MaxJitter sets the maximum random Jitter between retries for RandomDelay
func MaxJitter(maxJitter time.Duration) Option {
return func(c *Config) {
c.maxJitter = maxJitter
}
}
// DelayType set type of the delay between retries
// default is BackOff
func DelayType(delayType DelayTypeFunc) Option {
return func(c *Config) {
c.delayType = delayType
}
}
// BackOffDelay is a DelayType which increases delay between consecutive retries
func BackOffDelay(n uint, config *Config) time.Duration {
// 1 << 63 would overflow signed int64 (time.Duration), thus 62.
const max uint = 62
if config.maxBackOffN == 0 {
if config.delay <= 0 {
config.delay = 1
}
config.maxBackOffN = max - uint(math.Floor(math.Log2(float64(config.delay))))
}
if n > config.maxBackOffN {
n = config.maxBackOffN
}
return config.delay << n
}
// FixedDelay is a DelayType which keeps delay the same through all iterations
func FixedDelay(_ uint, config *Config) time.Duration {
return config.delay
}
// RandomDelay is a DelayType which picks a random delay up to config.maxJitter
func RandomDelay(_ uint, config *Config) time.Duration {
return time.Duration(rand.Int63n(int64(config.maxJitter)))
}
// CombineDelay is a DelayType the combines all of the specified delays into a new DelayTypeFunc
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
const maxInt64 = uint64(math.MaxInt64)
return func(n uint, config *Config) time.Duration {
var total uint64
for _, delay := range delays {
total += uint64(delay(n, config))
if total > maxInt64 {
total = maxInt64
}
}
return time.Duration(total)
}
}
// OnRetry function callback are called each retry
//
// log each retry example:
//
// retry.Do(
// func() error {
// return errors.New("some error")
// },
// retry.OnRetry(func(n uint, err error) {
// log.Printf("#%d: %s\n", n, err)
// }),
// )
func OnRetry(onRetry OnRetryFunc) Option {
return func(c *Config) {
c.onRetry = onRetry
}
}
// RetryIf controls whether a retry should be attempted after an error
// (assuming there are any retry attempts remaining)
//
// skip retry if special error example:
//
// retry.Do(
// func() error {
// return errors.New("special error")
// },
// retry.RetryIf(func(err error) bool {
// if err.Error() == "special error" {
// return false
// }
// return true
// })
// )
//
// By default RetryIf stops execution if the error is wrapped using `retry.Unrecoverable`,
// so above example may also be shortened to:
//
// retry.Do(
// func() error {
// return retry.Unrecoverable(errors.New("special error"))
// }
// )
func RetryIf(retryIf RetryIfFunc) Option {
return func(c *Config) {
c.retryIf = retryIf
}
}
// Context allow to set context of retry
// default are Background context
//
// example of immediately cancellation (maybe it isn't the best example, but it describes behavior enough; I hope)
// ctx, cancel := context.WithCancel(context.Background())
// cancel()
//
// retry.Do(
// func() error {
// ...
// },
// retry.Context(ctx),
// )
func Context(ctx context.Context) Option {
return func(c *Config) {
c.context = ctx
}
}

219
vendor/github.com/avast/retry-go/retry.go generated vendored Normal file
View File

@ -0,0 +1,219 @@
/*
Simple library for retry mechanism
slightly inspired by [Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry)
SYNOPSIS
http get with retry:
url := "http://example.com"
var body []byte
err := retry.Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
fmt.Println(body)
[next examples](https://github.com/avast/retry-go/tree/master/examples)
SEE ALSO
* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - slightly complicated interface.
* [sethgrid/pester](https://github.com/sethgrid/pester) - only http retry for http calls with retries and backoff
* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Go port of the exponential backoff algorithm from Google's HTTP Client Library for Java. Really complicated interface.
* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - looks good, slightly similar as this package, don't have 'simple' `Retry` method
* [matryer/try](https://github.com/matryer/try) - very popular package, nonintuitive interface (for me)
BREAKING CHANGES
1.0.2 -> 2.0.0
* argument of `retry.Delay` is final delay (no multiplication by `retry.Units` anymore)
* function `retry.Units` are removed
* [more about this breaking change](https://github.com/avast/retry-go/issues/7)
0.3.0 -> 1.0.0
* `retry.Retry` function are changed to `retry.Do` function
* `retry.RetryCustom` (OnRetry) and `retry.RetryCustomWithOpts` functions are now implement via functions produces Options (aka `retry.OnRetry`)
*/
package retry
import (
"context"
"fmt"
"strings"
"time"
)
// Function signature of retryable function
type RetryableFunc func() error
var (
DefaultAttempts = uint(10)
DefaultDelay = 100 * time.Millisecond
DefaultMaxJitter = 100 * time.Millisecond
DefaultOnRetry = func(n uint, err error) {}
DefaultRetryIf = IsRecoverable
DefaultDelayType = CombineDelay(BackOffDelay, RandomDelay)
DefaultLastErrorOnly = false
DefaultContext = context.Background()
)
func Do(retryableFunc RetryableFunc, opts ...Option) error {
var n uint
//default
config := &Config{
attempts: DefaultAttempts,
delay: DefaultDelay,
maxJitter: DefaultMaxJitter,
onRetry: DefaultOnRetry,
retryIf: DefaultRetryIf,
delayType: DefaultDelayType,
lastErrorOnly: DefaultLastErrorOnly,
context: DefaultContext,
}
//apply opts
for _, opt := range opts {
opt(config)
}
if err := config.context.Err(); err != nil {
return err
}
var errorLog Error
if !config.lastErrorOnly {
errorLog = make(Error, config.attempts)
} else {
errorLog = make(Error, 1)
}
lastErrIndex := n
for n < config.attempts {
err := retryableFunc()
if err != nil {
errorLog[lastErrIndex] = unpackUnrecoverable(err)
if !config.retryIf(err) {
break
}
config.onRetry(n, err)
// if this is last attempt - don't wait
if n == config.attempts-1 {
break
}
delayTime := config.delayType(n, config)
if config.maxDelay > 0 && delayTime > config.maxDelay {
delayTime = config.maxDelay
}
select {
case <-time.After(delayTime):
case <-config.context.Done():
return config.context.Err()
}
} else {
return nil
}
n++
if !config.lastErrorOnly {
lastErrIndex = n
}
}
if config.lastErrorOnly {
return errorLog[lastErrIndex]
}
return errorLog
}
// Error type represents list of errors in retry
type Error []error
// Error method return string representation of Error
// It is an implementation of error interface
func (e Error) Error() string {
logWithNumber := make([]string, lenWithoutNil(e))
for i, l := range e {
if l != nil {
logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error())
}
}
return fmt.Sprintf("All attempts fail:\n%s", strings.Join(logWithNumber, "\n"))
}
func lenWithoutNil(e Error) (count int) {
for _, v := range e {
if v != nil {
count++
}
}
return
}
// WrappedErrors returns the list of errors that this Error is wrapping.
// It is an implementation of the `errwrap.Wrapper` interface
// in package [errwrap](https://github.com/hashicorp/errwrap) so that
// `retry.Error` can be used with that library.
func (e Error) WrappedErrors() []error {
return e
}
type unrecoverableError struct {
error
}
// Unrecoverable wraps an error in `unrecoverableError` struct
func Unrecoverable(err error) error {
return unrecoverableError{err}
}
// IsRecoverable checks if error is an instance of `unrecoverableError`
func IsRecoverable(err error) bool {
_, isUnrecoverable := err.(unrecoverableError)
return !isUnrecoverable
}
func unpackUnrecoverable(err error) error {
if unrecoverable, isUnrecoverable := err.(unrecoverableError); isUnrecoverable {
return unrecoverable.error
}
return err
}

22
vendor/github.com/dolanor/caldav-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015
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.

191
vendor/github.com/dolanor/caldav-go/caldav/client.go generated vendored Normal file
View File

@ -0,0 +1,191 @@
package caldav
import (
"fmt"
"log"
"net/http"
"strings"
cent "github.com/dolanor/caldav-go/caldav/entities"
"github.com/dolanor/caldav-go/icalendar/components"
"github.com/dolanor/caldav-go/utils"
"github.com/dolanor/caldav-go/webdav"
"github.com/dolanor/caldav-go/webdav/entities"
)
var _ = log.Print
// a client for making WebDAV requests
type Client webdav.Client
// downcasts the client to the WebDAV interface
func (c *Client) WebDAV() *webdav.Client {
return (*webdav.Client)(c)
}
// returns the embedded CalDAV server reference
func (c *Client) Server() *Server {
return (*Server)(c.WebDAV().Server())
}
// fetches a list of CalDAV features supported by the server
// returns an error if the server does not support DAV
func (c *Client) Features(path string) ([]string, error) {
var cfeatures []string
if features, err := c.WebDAV().Features(path); err != nil {
return cfeatures, utils.NewError(c.Features, "unable to detect features", c, err)
} else {
for _, feature := range features {
if strings.HasPrefix(feature, "calendar-") {
cfeatures = append(cfeatures, feature)
}
}
return cfeatures, nil
}
}
// fetches a list of CalDAV features and checks if a certain one is supported by the server
// returns an error if the server does not support DAV
func (c *Client) SupportsFeature(name string, path string) (bool, error) {
if features, err := c.Features(path); err != nil {
return false, utils.NewError(c.SupportsFeature, "feature detection failed", c, err)
} else {
var test = fmt.Sprintf("calendar-%s", name)
for _, feature := range features {
if feature == test {
return true, nil
}
}
return false, nil
}
}
// fetches a list of CalDAV features and checks if a certain one is supported by the server
// returns an error if the server does not support DAV
func (c *Client) ValidateServer(path string) error {
if found, err := c.SupportsFeature("access", path); err != nil {
return utils.NewError(c.SupportsFeature, "feature detection failed", c, err)
} else if !found {
return utils.NewError(c.SupportsFeature, "calendar access feature missing", c, nil)
} else {
return nil
}
}
// creates a new calendar collection on a given path
func (c *Client) MakeCalendar(path string) error {
if req, err := c.Server().NewRequest("MKCALENDAR", path); err != nil {
return utils.NewError(c.MakeCalendar, "unable to create request", c, err)
} else if resp, err := c.Do(req); err != nil {
return utils.NewError(c.MakeCalendar, "unable to execute request", c, err)
} else if resp.StatusCode != http.StatusCreated {
err := new(entities.Error)
resp.Decode(err)
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
return utils.NewError(c.MakeCalendar, msg, c, err)
} else {
return nil
}
}
// creates or updates one or more events on the remote CalDAV server
func (c *Client) PutEvents(path string, events ...*components.Event) error {
if len(events) <= 0 {
return utils.NewError(c.PutEvents, "no calendar events provided", c, nil)
} else if cal := components.NewCalendar(events...); events[0] == nil {
return utils.NewError(c.PutEvents, "icalendar event must not be nil", c, nil)
} else if err := c.PutCalendars(path, cal); err != nil {
return utils.NewError(c.PutEvents, "unable to put calendar", c, err)
}
return nil
}
// creates or updates one or more calendars on the remote CalDAV server
func (c *Client) PutCalendars(path string, calendars ...*components.Calendar) error {
if req, err := c.Server().NewRequest("PUT", path, calendars); err != nil {
return utils.NewError(c.PutCalendars, "unable to encode request", c, err)
} else if resp, err := c.Do(req); err != nil {
return utils.NewError(c.PutCalendars, "unable to execute request", c, err)
} else if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
err := new(entities.Error)
resp.WebDAV().Decode(err)
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
return utils.NewError(c.PutCalendars, msg, c, err)
}
return nil
}
// attempts to fetch an event on the remote CalDAV server
func (c *Client) GetEvents(path string) ([]*components.Event, error) {
cal := new(components.Calendar)
if req, err := c.Server().NewRequest("GET", path); err != nil {
return nil, utils.NewError(c.GetEvents, "unable to create request", c, err)
} else if resp, err := c.Do(req); err != nil {
return nil, utils.NewError(c.GetEvents, "unable to execute request", c, err)
} else if resp.StatusCode != http.StatusOK {
err := new(entities.Error)
resp.WebDAV().Decode(err)
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
return nil, utils.NewError(c.GetEvents, msg, c, err)
} else if err := resp.Decode(cal); err != nil {
return nil, utils.NewError(c.GetEvents, "unable to decode response", c, err)
} else {
return cal.Events, nil
}
}
// attempts to fetch an event on the remote CalDAV server
func (c *Client) QueryEvents(path string, query *cent.CalendarQuery) (events []*components.Event, oerr error) {
ms := new(cent.Multistatus)
if req, err := c.Server().WebDAV().NewRequest("REPORT", path, query); err != nil {
oerr = utils.NewError(c.QueryEvents, "unable to create request", c, err)
} else if req.Http().Native().Header.Set("Depth", string(webdav.Depth1)); false {
} else if resp, err := c.WebDAV().Do(req); err != nil {
oerr = utils.NewError(c.QueryEvents, "unable to execute request", c, err)
} else if resp.StatusCode == http.StatusNotFound {
return // no events if not found
} else if resp.StatusCode != webdav.StatusMulti {
err := new(entities.Error)
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
resp.Decode(err)
oerr = utils.NewError(c.QueryEvents, msg, c, err)
} else if err := resp.Decode(ms); err != nil {
msg := "unable to decode response"
oerr = utils.NewError(c.QueryEvents, msg, c, err)
} else {
for i, r := range ms.Responses {
for j, p := range r.PropStats {
if p.Prop == nil || p.Prop.CalendarData == nil {
continue
} else if cal, err := p.Prop.CalendarData.CalendarComponent(); err != nil {
msg := fmt.Sprintf("unable to decode property %d of response %d", j, i)
oerr = utils.NewError(c.QueryEvents, msg, c, err)
return
} else {
events = append(events, cal.Events...)
}
}
}
}
return
}
// executes a CalDAV request
func (c *Client) Do(req *Request) (*Response, error) {
if resp, err := c.WebDAV().Do((*webdav.Request)(req)); err != nil {
return nil, utils.NewError(c.Do, "unable to execute CalDAV request", c, err)
} else {
return NewResponse(resp), nil
}
}
// creates a new client for communicating with an WebDAV server
func NewClient(server *Server, native *http.Client) *Client {
return (*Client)(webdav.NewClient((*webdav.Server)(server), native))
}
// creates a new client for communicating with a WebDAV server
// uses the default HTTP client from net/http
func NewDefaultClient(server *Server) *Client {
return NewClient(server, http.DefaultClient)
}

View File

@ -0,0 +1,51 @@
package entities
import (
"encoding/xml"
"github.com/dolanor/caldav-go/caldav/values"
"github.com/dolanor/caldav-go/icalendar"
"github.com/dolanor/caldav-go/icalendar/components"
"github.com/dolanor/caldav-go/utils"
"strings"
)
// a CalDAV calendar data object
type CalendarData struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-data"`
Component *Component `xml:",omitempty"`
RecurrenceSetLimit *RecurrenceSetLimit `xml:",omitempty"`
ExpandRecurrenceSet *ExpandRecurrenceSet `xml:",omitempty"`
Content string `xml:",chardata"`
}
func (c *CalendarData) CalendarComponent() (*components.Calendar, error) {
cal := new(components.Calendar)
if content := strings.TrimSpace(c.Content); content == "" {
return nil, utils.NewError(c.CalendarComponent, "no calendar data to decode", c, nil)
} else if err := icalendar.Unmarshal(content, cal); err != nil {
return nil, utils.NewError(c.CalendarComponent, "decoding calendar data failed", c, err)
} else {
return cal, nil
}
}
// an iCalendar specifier for returned calendar data
type Component struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav comp"`
Properties []*PropertyName `xml:",omitempty"`
Components []*Component `xml:",omitempty"`
}
// used to restrict recurring event data to a particular time range
type RecurrenceSetLimit struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav limit-recurrence-set"`
StartTime *values.DateTime `xml:"start,attr"`
EndTime *values.DateTime `xml:"end,attr"`
}
// used to expand recurring events into individual calendar event data
type ExpandRecurrenceSet struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav expand"`
StartTime *values.DateTime `xml:"start,attr"`
EndTime *values.DateTime `xml:"end,attr"`
}

View File

@ -0,0 +1,59 @@
package entities
import (
"encoding/xml"
"github.com/dolanor/caldav-go/caldav/values"
"github.com/dolanor/caldav-go/utils"
"github.com/dolanor/caldav-go/webdav/entities"
"time"
)
// a CalDAV calendar query object
type CalendarQuery struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar-query"`
Prop *Prop `xml:",omitempty"`
AllProp *entities.AllProp `xml:",omitempty"`
Filter *Filter `xml:",omitempty"`
}
// creates a new CalDAV query for iCalendar events from a particular time range
func NewEventRangeQuery(start, end time.Time) (*CalendarQuery, error) {
var err error
var dtstart, dtend *values.DateTime
if dtstart, err = values.NewDateTime("start", start); err != nil {
return nil, utils.NewError(NewEventRangeQuery, "unable to encode start time", start, err)
} else if dtend, err = values.NewDateTime("end", end); err != nil {
return nil, utils.NewError(NewEventRangeQuery, "unable to encode end time", end, err)
}
// construct the query object
query := new(CalendarQuery)
// request all calendar data
query.Prop = new(Prop)
query.Prop.CalendarData = new(CalendarData)
// expand recurring events
query.Prop.CalendarData.ExpandRecurrenceSet = new(ExpandRecurrenceSet)
query.Prop.CalendarData.ExpandRecurrenceSet.StartTime = dtstart
query.Prop.CalendarData.ExpandRecurrenceSet.EndTime = dtend
// filter down calendar data to only iCalendar data
query.Filter = new(Filter)
query.Filter.ComponentFilter = new(ComponentFilter)
query.Filter.ComponentFilter.Name = values.CalendarComponentName
// filter down iCalendar data to only events
query.Filter.ComponentFilter.ComponentFilter = new(ComponentFilter)
query.Filter.ComponentFilter.ComponentFilter.Name = values.EventComponentName
// filter down the events to only those that fall within the time range
query.Filter.ComponentFilter.ComponentFilter.TimeRange = new(TimeRange)
query.Filter.ComponentFilter.ComponentFilter.TimeRange.StartTime = dtstart
query.Filter.ComponentFilter.ComponentFilter.TimeRange.EndTime = dtend
// return the event query
return query, nil
}

View File

@ -0,0 +1,62 @@
package entities
import (
"encoding/xml"
"github.com/dolanor/caldav-go/caldav/values"
"github.com/dolanor/caldav-go/icalendar/properties"
)
// a CalDAV query filter entity
type Filter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav filter"`
ComponentFilter *ComponentFilter `xml:",omitempty"`
}
// used to filter down calendar components, such as VCALENDAR > VEVENT
type ComponentFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav comp-filter"`
Name values.ComponentName `xml:"name,attr"`
ComponentFilter *ComponentFilter `xml:",omitempty"`
TimeRange *TimeRange `xml:",omitempty"`
PropertyFilter *PropertyFilter `xml:",omitempty"`
ParameterFilter *ParameterFilter `xml:",omitempty"`
}
// used to restrict component filters to a particular time range
type TimeRange struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav time-range"`
StartTime *values.DateTime `xml:"start,attr"`
EndTime *values.DateTime `xml:"end,attr"`
}
// used to restrict component filters to a property value
type PropertyFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav prop-filter"`
Name properties.PropertyName `xml:"name,attr"`
TextMatch *TextMatch `xml:",omitempty"`
ParameterFilter *ParameterFilter `xml:",omitempty"`
}
// used to restrict component filters to a parameter value
type ParameterFilter struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav param-filter"`
Name properties.ParameterName `xml:"name,attr"`
TextMatch *TextMatch `xml:",omitempty"`
}
// used to match properties by text value
type TextMatch struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav text-match"`
Collation values.TextCollation `xml:"collation,attr,omitempty"`
NegateCondition values.HumanBoolean `xml:"attr,negate-condition,omitempty"`
Content string `xml:",innerxml"`
}
// creates a new CalDAV property value matcher
func NewPropertyMatcher(name properties.PropertyName, content string) *PropertyFilter {
pf := new(PropertyFilter)
pf.Name = name
pf.TextMatch = new(TextMatch)
pf.TextMatch.Content = content
return pf
}

View File

@ -0,0 +1,23 @@
package entities
import "encoding/xml"
// metadata about a property
type PropStat struct {
XMLName xml.Name `xml:"propstat"`
Status string `xml:"status"`
Prop *Prop `xml:",omitempty"`
}
// a multistatus response entity
type Response struct {
XMLName xml.Name `xml:"response"`
Href string `xml:"href"`
PropStats []*PropStat `xml:"propstat,omitempty"`
}
// a request to find properties on an an entity or collection
type Multistatus struct {
XMLName xml.Name `xml:"DAV: multistatus"`
Responses []*Response `xml:"response,omitempty"`
}

View File

@ -0,0 +1,23 @@
package entities
import (
"encoding/xml"
"github.com/dolanor/caldav-go/webdav/entities"
)
// a CalDAV Property resource
type Prop struct {
XMLName xml.Name `xml:"DAV: prop"`
GetContentType string `xml:"getcontenttype,omitempty"`
DisplayName string `xml:"displayname,omitempty"`
CalendarData *CalendarData `xml:",omitempty"`
ResourceType *entities.ResourceType `xml:",omitempty"`
CTag string `xml:"http://calendarserver.org/ns/ getctag,omitempty"`
ETag string `xml:"http://calendarserver.org/ns/ getetag,omitempty"`
}
// used to restrict properties returned in calendar data
type PropertyName struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav prop"`
Name string `xml:"name,attr"`
}

56
vendor/github.com/dolanor/caldav-go/caldav/request.go generated vendored Normal file
View File

@ -0,0 +1,56 @@
package caldav
import (
"bytes"
"github.com/dolanor/caldav-go/http"
"github.com/dolanor/caldav-go/icalendar"
"github.com/dolanor/caldav-go/utils"
"github.com/dolanor/caldav-go/webdav"
"io"
"io/ioutil"
"log"
"strings"
)
var _ = log.Print
// an CalDAV request object
type Request webdav.Request
// downcasts the request to the WebDAV interface
func (r *Request) WebDAV() *webdav.Request {
return (*webdav.Request)(r)
}
// creates a new CalDAV request object
func NewRequest(method string, urlstr string, icaldata ...interface{}) (*Request, error) {
if buffer, err := icalToReadCloser(icaldata...); err != nil {
return nil, utils.NewError(NewRequest, "unable to encode icalendar data", icaldata, err)
} else if r, err := http.NewRequest(method, urlstr, buffer); err != nil {
return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err)
} else {
if buffer != nil {
// set the content type to XML if we have a body
r.Native().Header.Set("Content-Type", "text/calendar; charset=UTF-8")
}
return (*Request)(r), nil
}
}
func icalToReadCloser(icaldata ...interface{}) (io.ReadCloser, error) {
var buffer []string
for _, icaldatum := range icaldata {
if encoded, err := icalendar.Marshal(icaldatum); err != nil {
return nil, utils.NewError(icalToReadCloser, "unable to encode as icalendar data", icaldatum, err)
} else {
// log.Printf("OUT: %+v", encoded)
buffer = append(buffer, encoded)
}
}
if len(buffer) > 0 {
var encoded = strings.Join(buffer, "\n")
return ioutil.NopCloser(bytes.NewBuffer([]byte(encoded))), nil
} else {
return nil, nil
}
}

40
vendor/github.com/dolanor/caldav-go/caldav/response.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package caldav
import (
"github.com/dolanor/caldav-go/icalendar"
"github.com/dolanor/caldav-go/utils"
"github.com/dolanor/caldav-go/webdav"
"io/ioutil"
"log"
)
var _ = log.Print
// a WebDAV response object
type Response webdav.Response
// downcasts the response to the WebDAV interface
func (r *Response) WebDAV() *webdav.Response {
return (*webdav.Response)(r)
}
// decodes a CalDAV iCalendar response into the provided interface
func (r *Response) Decode(into interface{}) error {
if body := r.Body; body == nil {
return nil
} else if encoded, err := ioutil.ReadAll(body); err != nil {
return utils.NewError(r.Decode, "unable to read response body", r, err)
} else {
// log.Printf("IN: %+v", string(encoded))
if err := icalendar.Unmarshal(string(encoded), into); err != nil {
return utils.NewError(r.Decode, "unable to decode response body", r, err)
} else {
return nil
}
}
}
// creates a new WebDAV response object
func NewResponse(response *webdav.Response) *Response {
return (*Response)(response)
}

30
vendor/github.com/dolanor/caldav-go/caldav/server.go generated vendored Normal file
View File

@ -0,0 +1,30 @@
package caldav
import (
"github.com/dolanor/caldav-go/utils"
"github.com/dolanor/caldav-go/webdav"
)
// a server that accepts CalDAV requests
type Server webdav.Server
// NewServer creates a reference to a CalDAV server.
// host is the url to access the server, stopping at the port:
// https://user:password@host:port/
func NewServer(host string) (*Server, error) {
if s, err := webdav.NewServer(host); err != nil {
return nil, utils.NewError(NewServer, "unable to create WebDAV server", host, err)
} else {
return (*Server)(s), nil
}
}
// downcasts the server to the WebDAV interface
func (s *Server) WebDAV() *webdav.Server {
return (*webdav.Server)(s)
}
// creates a new CalDAV request object
func (s *Server) NewRequest(method string, path string, icaldata ...interface{}) (*Request, error) {
return NewRequest(method, s.WebDAV().Http().AbsUrlStr(path), icaldata...)
}

View File

@ -0,0 +1,8 @@
package values
type ComponentName string
const (
CalendarComponentName ComponentName = "VCALENDAR"
EventComponentName = "VEVENT"
)

View File

@ -0,0 +1,31 @@
package values
import (
"encoding/xml"
"errors"
"github.com/dolanor/caldav-go/icalendar/values"
"time"
)
// a representation of a date and time for iCalendar
type DateTime struct {
name string
t time.Time
}
// creates a new caldav datetime representation, must be in UTC
func NewDateTime(name string, t time.Time) (*DateTime, error) {
if t.Location() != time.UTC {
return nil, errors.New("CalDAV datetime must be in UTC")
} else {
return &DateTime{name: name, t: t.Truncate(time.Second)}, nil
}
}
// encodes the datetime value for the iCalendar specification
func (d *DateTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
layout := values.UTCDateTimeFormatString
value := d.t.Format(layout)
attr := xml.Attr{Name: name, Value: value}
return attr, nil
}

View File

@ -0,0 +1,8 @@
package values
type HumanBoolean string
const (
YesHumanBoolean HumanBoolean = "yes"
NoHumanBoolean = "no"
)

View File

@ -0,0 +1,8 @@
package values
type TextCollation string
const (
OctetTextCollation TextCollation = "i;octet"
ASCIICaseMapCollation = "i;ascii-casemap"
)

53
vendor/github.com/dolanor/caldav-go/http/client.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
package http
import (
"github.com/dolanor/caldav-go/utils"
"net/http"
)
// a client for making HTTP requests
type Client struct {
native *http.Client
server *Server
requestHeaders map[string]string
}
func (c *Client) SetHeader(key string, value string) {
if c.requestHeaders == nil {
c.requestHeaders = map[string]string{}
}
c.requestHeaders[key] = value
}
// downcasts to the native HTTP interface
func (c *Client) Native() *http.Client {
return c.native
}
// returns the embedded HTTP server reference
func (c *Client) Server() *Server {
return c.server
}
// executes an HTTP request
func (c *Client) Do(req *Request) (*Response, error) {
for key, value := range c.requestHeaders {
req.Header.Add(key, value)
}
if resp, err := c.Native().Do((*http.Request)(req)); err != nil {
return nil, utils.NewError(c.Do, "unable to execute HTTP request", c, err)
} else {
return NewResponse(resp), nil
}
}
// creates a new client for communicating with an HTTP server
func NewClient(server *Server, native *http.Client) *Client {
return &Client{server: server, native: native}
}
// creates a new client for communicating with a server
// uses the default HTTP client from net/http
func NewDefaultClient(server *Server) *Client {
return NewClient(server, http.DefaultClient)
}

39
vendor/github.com/dolanor/caldav-go/http/request.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package http
import (
"github.com/dolanor/caldav-go/utils"
"io"
"net/http"
)
// an HTTP request object
type Request http.Request
// downcasts the request to the native HTTP interface
func (r *Request) Native() *http.Request {
return (*http.Request)(r)
}
// creates a new HTTP request object
func NewRequest(method string, urlstr string, body ...io.ReadCloser) (*Request, error) {
var err error
var r = new(http.Request)
if len(body) > 0 && body[0] != nil {
r, err = http.NewRequest(method, urlstr, body[0])
} else {
r, err = http.NewRequest(method, urlstr, nil)
}
if err != nil {
return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err)
} else if auth := r.URL.User; auth != nil {
pass, _ := auth.Password()
r.SetBasicAuth(auth.Username(), pass)
r.URL.User = nil
}
return (*Request)(r), nil
}

18
vendor/github.com/dolanor/caldav-go/http/response.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package http
import (
"net/http"
)
// an HTTP response object
type Response http.Response
// downcasts the response to the native HTTP interface
func (r *Response) Native() *http.Response {
return (*http.Response)(r)
}
// creates a new HTTP response object
func NewResponse(response *http.Response) *Response {
return (*Response)(response)
}

48
vendor/github.com/dolanor/caldav-go/http/server.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package http
import (
"github.com/dolanor/caldav-go/utils"
"io"
"log"
"net/url"
spath "path"
"strings"
)
var _ = log.Print
// a server that accepts HTTP requests
type Server struct {
baseUrl *url.URL
}
// creates a reference to an http server
func NewServer(baseUrlStr string) (*Server, error) {
var err error
var s = new(Server)
if s.baseUrl, err = url.Parse(baseUrlStr); err != nil {
return nil, utils.NewError(NewServer, "unable to parse server base url", baseUrlStr, err)
} else {
return s, nil
}
}
// converts a path name to an absolute URL
func (s *Server) UserInfo() *url.Userinfo {
return s.baseUrl.User
}
// converts a path name to an absolute URL
func (s *Server) AbsUrlStr(path string) string {
uri := *s.baseUrl
uri.Path = spath.Join(uri.Path, path)
if strings.HasSuffix(path, "/") {
uri.Path = uri.Path + "/"
}
return uri.String()
}
// creates a new HTTP request object
func (s *Server) NewRequest(method string, path string, body ...io.ReadCloser) (*Request, error) {
return NewRequest(method, s.AbsUrlStr(path), body...)
}

View File

@ -0,0 +1,87 @@
package components
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/values"
"github.com/dolanor/caldav-go/utils"
"time"
)
type Calendar struct {
// specifies the identifier corresponding to the highest version number or the minimum and maximum
// range of the iCalendar specification that is required in order to interpret the iCalendar object.
Version string `ical:",2.0"`
// specifies the identifier for the product that created the iCalendar object
ProductId string `ical:"prodid,-//dolanor/caldav-go//NONSGML v1.0.0//EN"`
// specifies the text value that uniquely identifies the "VTIMEZONE" calendar component.
TimeZoneId string `ical:"tzid,omitempty"`
// defines the iCalendar object method associated with the calendar object.
Method values.Method `ical:",omitempty"`
// defines the calendar scale used for the calendar information specified in the iCalendar object.
CalScale values.CalScale `ical:",omitempty"`
// defines the different timezones used by the various components nested within
TimeZones []*TimeZone `ical:",omitempty"`
// unique events to be stored together in the icalendar file
Events []*Event `ical:",omitempty"`
}
func (c *Calendar) UseTimeZone(location *time.Location) *TimeZone {
tz := NewDynamicTimeZone(location)
c.TimeZones = append(c.TimeZones, tz)
c.TimeZoneId = tz.Id
return tz
}
func (c *Calendar) UsingTimeZone() bool {
return len(c.TimeZoneId) > 0
}
func (c *Calendar) UsingGlobalTimeZone() bool {
return c.UsingTimeZone() && c.TimeZoneId[0] == '/'
}
func (c *Calendar) ValidateICalValue() error {
for i, e := range c.Events {
if e == nil {
continue // skip nil events
}
if err := e.ValidateICalValue(); err != nil {
msg := fmt.Sprintf("event %d failed validation", i)
return utils.NewError(c.ValidateICalValue, msg, c, err)
}
if e.DateStart == nil && c.Method == "" {
msg := fmt.Sprintf("no value for method and no start date defined on event %d", i)
return utils.NewError(c.ValidateICalValue, msg, c, nil)
}
}
if c.UsingTimeZone() && !c.UsingGlobalTimeZone() {
for i, t := range c.TimeZones {
if t == nil || t.Id != c.TimeZoneId {
msg := fmt.Sprintf("timezone ID does not match timezone %d", i)
return utils.NewError(c.ValidateICalValue, msg, c, nil)
}
}
}
return nil
}
func NewCalendar(events ...*Event) *Calendar {
cal := new(Calendar)
cal.Events = events
return cal
}

View File

@ -0,0 +1,172 @@
package components
import (
"github.com/dolanor/caldav-go/icalendar/values"
"github.com/dolanor/caldav-go/utils"
"time"
)
type Event struct {
// defines the persistent, globally unique identifier for the calendar component.
UID string `ical:",required"`
// indicates the date/time that the instance of the iCalendar object was created.
DateStamp *values.DateTime `ical:"dtstamp,required"`
// specifies when the calendar component begins.
DateStart *values.DateTime `ical:"dtstart,required"`
// specifies the date and time that a calendar component ends.
DateEnd *values.DateTime `ical:"dtend,omitempty"`
// specifies a positive duration of time.
Duration *values.Duration `ical:",omitempty"`
// defines the access classification for a calendar component.
AccessClassification values.EventAccessClassification `ical:"class,omitempty"`
// specifies the date and time that the calendar information was created by the calendar user agent in the
// calendar store.
// Note: This is analogous to the creation date and time for a file in the file system.
Created *values.DateTime `ical:",omitempty"`
// provides a more complete description of the calendar component, than that provided by the Summary property.
Description string `ical:",omitempty"`
// specifies information related to the global position for the activity specified by a calendar component.
Geo *values.Geo `ical:",omitempty"`
// specifies the date and time that the information associated with the calendar component was last revised in the
// calendar store.
// Note: This is analogous to the modification date and time for a file in the file system.
LastModified *values.DateTime `ical:"last-modified,omitempty"`
// defines the intended venue for the activity defined by a calendar component.
Location *values.Location `ical:",omitempty"`
// defines the organizer for a calendar component.
Organizer *values.OrganizerContact `ical:",omitempty"`
// defines the relative priority for a calendar component.
Priority int `ical:",omitempty"`
// defines the revision sequence number of the calendar component within a sequence of revisions.
Sequence int `ical:",omitempty"`
// efines the overall status or confirmation for the calendar component.
Status values.EventStatus `ical:",omitempty"`
// defines a short summary or subject for the calendar component.
Summary string `ical:",omitempty"`
// defines whether an event is transparent or not to busy time searches.
values.TimeTransparency `ical:"transp,omitempty"`
// defines a Uniform Resource Locator (URL) associated with the iCalendar object.
Url *values.Url `ical:",omitempty"`
// used in conjunction with the "UID" and "SEQUENCE" property to identify a specific instance of a recurring
// event calendar component. The property value is the effective value of the DateStart property of the
// recurrence instance.
RecurrenceId *values.DateTime `ical:"recurrence_id,omitempty"`
// defines a rule or repeating pattern for recurring events, to-dos, or time zone definitions.
RecurrenceRules []*values.RecurrenceRule `ical:",omitempty"`
// property provides the capability to associate a document object with a calendar component.
Attachment *values.Url `ical:"attach,omitempty"`
// defines an "Attendee" within a calendar component.
Attendees []*values.AttendeeContact `ical:",omitempty"`
// defines the categories for a calendar component.
Categories *values.CSV `ical:",omitempty"`
// specifies non-processing information intended to provide a comment to the calendar user.
Comments []values.Comment `ical:",omitempty"`
// used to represent contact information or alternately a reference to contact information associated with the calendar component.
ContactInfo *values.CSV `ical:"contact,omitempty"`
// defines the list of date/time exceptions for a recurring calendar component.
*values.ExceptionDateTimes `ical:",omitempty"`
// defines the list of date/times for a recurrence set.
*values.RecurrenceDateTimes `ical:",omitempty"`
// used to represent a relationship or reference between one calendar component and another.
RelatedTo *values.Url `ical:"related-to,omitempty"`
// defines the equipment or resources anticipated for an activity specified by a calendar entity.
Resources *values.CSV `ical:",omitempty"`
}
// validates the event internals
func (e *Event) ValidateICalValue() error {
if e.UID == "" {
return utils.NewError(e.ValidateICalValue, "the UID value must be set", e, nil)
}
if e.DateStart == nil {
return utils.NewError(e.ValidateICalValue, "event start date must be set", e, nil)
}
if e.DateEnd == nil && e.Duration == nil {
return utils.NewError(e.ValidateICalValue, "event end date or duration must be set", e, nil)
}
if e.DateEnd != nil && e.Duration != nil {
return utils.NewError(e.ValidateICalValue, "event end date and duration are mutually exclusive fields", e, nil)
}
return nil
}
// adds one or more recurrence rule to the event
func (e *Event) AddRecurrenceRules(r ...*values.RecurrenceRule) {
e.RecurrenceRules = append(e.RecurrenceRules, r...)
}
// adds one or more recurrence rule exception to the event
func (e *Event) AddRecurrenceExceptions(d ...*values.DateTime) {
if e.ExceptionDateTimes == nil {
e.ExceptionDateTimes = new(values.ExceptionDateTimes)
}
*e.ExceptionDateTimes = append(*e.ExceptionDateTimes, d...)
}
// checks to see if the event is a recurrence
func (e *Event) IsRecurrence() bool {
return e.RecurrenceId != nil
}
// checks to see if the event is a recurrence override
func (e *Event) IsOverride() bool {
return e.IsRecurrence() && !e.RecurrenceId.Equals(e.DateStart)
}
// creates a new iCalendar event with no end time
func NewEvent(uid string, start time.Time) *Event {
e := new(Event)
e.UID = uid
e.DateStamp = values.NewDateTime(time.Now().UTC())
e.DateStart = values.NewDateTime(start)
return e
}
// creates a new iCalendar event that lasts a certain duration
func NewEventWithDuration(uid string, start time.Time, duration time.Duration) *Event {
e := NewEvent(uid, start)
e.Duration = values.NewDuration(duration)
return e
}
// creates a new iCalendar event that has an explicit start and end time
func NewEventWithEnd(uid string, start time.Time, end time.Time) *Event {
e := NewEvent(uid, start)
e.DateEnd = values.NewDateTime(end)
return e
}

View File

@ -0,0 +1,40 @@
package components
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/values"
"net/url"
"time"
)
type TimeZone struct {
// defines the persistent, globally unique identifier for the calendar component.
Id string `ical:"tzid,required"`
// the location name, as defined by the standards body
ExtLocationName string `ical:"x-lic-location,omitempty"`
// defines a Uniform Resource Locator (URL) associated with the iCalendar object.
Url *values.Url `ical:"tzurl,omitempty"`
// specifies the date and time that the information associated with the calendar component was last revised in the
// calendar store.
// Note: This is analogous to the modification date and time for a file in the file system.
LastModified *values.DateTime `ical:"last-modified,omitempty"`
// TODO need to figure out how to handle standard and daylight savings time
}
func NewDynamicTimeZone(location *time.Location) *TimeZone {
t := new(TimeZone)
t.Id = location.String()
t.ExtLocationName = location.String()
t.Url = values.NewUrl(url.URL{
Scheme: "http",
Host: "tzurl.org",
Path: fmt.Sprintf("/zoneinfo/%s", t.Id),
})
return t
}

View File

@ -0,0 +1,196 @@
package icalendar
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"strings"
)
const (
Newline = "\r\n"
)
var _ = log.Print
type encoder func(reflect.Value) (string, error)
func tagAndJoinValue(v reflect.Value, in []string) (string, error) {
if tag, err := extractTagFromValue(v); err != nil {
return "", utils.NewError(tagAndJoinValue, "unable to extract tag from value", v, err)
} else {
var out []string
out = append(out, properties.MarshalProperty(properties.NewProperty("begin", tag)))
out = append(out, in...)
out = append(out, properties.MarshalProperty(properties.NewProperty("end", tag)))
return strings.Join(out, Newline), nil
}
}
func marshalCollection(v reflect.Value) (string, error) {
var out []string
for i, n := 0, v.Len(); i < n; i++ {
vi := v.Index(i).Interface()
if encoded, err := Marshal(vi); err != nil {
msg := fmt.Sprintf("unable to encode interface at index %d", i)
return "", utils.NewError(marshalCollection, msg, vi, err)
} else if encoded != "" {
out = append(out, encoded)
}
}
return strings.Join(out, Newline), nil
}
func marshalStruct(v reflect.Value) (string, error) {
var out []string
// iterate over all fields
vtype := v.Type()
n := vtype.NumField()
for i := 0; i < n; i++ {
// keep a reference to the field value and definition
fv := v.Field(i)
fs := vtype.Field(i)
// use the field definition to extract out property defaults
p := properties.PropertyFromStructField(fs)
if p == nil {
continue // skip explicitly ignored fields and private members
}
fi := fv.Interface()
// some fields are not properties, but actually nested objects.
// detect those early using the property and object encoder...
if _, ok := fi.(properties.CanEncodeValue); !ok && !isInvalidOrEmptyValue(fv) {
if encoded, err := encode(fv, objectEncoder); err != nil {
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
} else if encoded != "" {
// encoding worked! no need to process as a property
out = append(out, encoded)
continue
}
}
// now check to see if the field value overrides the defaults...
if !isInvalidOrEmptyValue(fv) {
// first, check the field value interface for overrides...
if overrides, err := properties.PropertyFromInterface(fi); err != nil {
msg := fmt.Sprintf("field %s failed validation", fs.Name)
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
} else if p.Merge(overrides); p.Value == "" {
// then, if we couldn't find an override from the interface,
// try the simple string encoder...
if p.Value, err = stringEncoder(fv); err != nil {
msg := fmt.Sprintf("unable to encode field %s", fs.Name)
return "", utils.NewError(marshalStruct, msg, v.Interface(), err)
}
}
}
// make sure we have a value by this point
if !p.HasNameAndValue() {
if p.OmitEmpty {
continue
} else if p.DefaultValue != "" {
p.Value = p.DefaultValue
} else if p.Required {
msg := fmt.Sprintf("missing value for required field %s", fs.Name)
return "", utils.NewError(Marshal, msg, v.Interface(), nil)
}
}
// encode in the property
out = append(out, properties.MarshalProperty(p))
}
// wrap the fields in the enclosing struct tags
return tagAndJoinValue(v, out)
}
func objectEncoder(v reflect.Value) (string, error) {
// decompose the value into its interface parts
v = dereferencePointerValue(v)
// encode the value based off of its type
switch v.Kind() {
case reflect.Slice:
fallthrough
case reflect.Array:
return marshalCollection(v)
case reflect.Struct:
return marshalStruct(v)
}
return "", nil
}
func stringEncoder(v reflect.Value) (string, error) {
return fmt.Sprintf("%v", v.Interface()), nil
}
func propertyEncoder(v reflect.Value) (string, error) {
vi := v.Interface()
if p, err := properties.PropertyFromInterface(vi); err != nil {
// return early if interface fails its own validation
return "", err
} else if p.HasNameAndValue() {
// if an interface encodes its own name and value, it's a property
return properties.MarshalProperty(p), nil
}
return "", nil
}
func encode(v reflect.Value, encoders ...encoder) (string, error) {
for _, encode := range encoders {
if encoded, err := encode(v); err != nil {
return "", err
} else if encoded != "" {
return encoded, nil
}
}
return "", nil
}
// converts an iCalendar component into its string representation
func Marshal(target interface{}) (string, error) {
// don't do anything with invalid interfaces
v := reflect.ValueOf(target)
if isInvalidOrEmptyValue(v) {
return "", utils.NewError(Marshal, "unable to marshal empty or invalid values", target, nil)
}
if encoded, err := encode(v, propertyEncoder, objectEncoder, stringEncoder); err != nil {
return "", err
} else if encoded == "" {
return "", utils.NewError(Marshal, "unable to encode interface, all methods exhausted", v.Interface(), nil)
} else {
return encoded, nil
}
}

View File

@ -0,0 +1,29 @@
package properties
type CanValidateValue interface {
ValidateICalValue() error
}
type CanDecodeValue interface {
DecodeICalValue(string) error
}
type CanDecodeParams interface {
DecodeICalParams(Params) error
}
type CanEncodeTag interface {
EncodeICalTag() (string, error)
}
type CanEncodeValue interface {
EncodeICalValue() (string, error)
}
type CanEncodeName interface {
EncodeICalName() (PropertyName, error)
}
type CanEncodeParams interface {
EncodeICalParams() (Params, error)
}

View File

@ -0,0 +1,31 @@
package properties
import "strings"
type PropertyName string
const (
UIDPropertyName PropertyName = "UID"
CommentPropertyName = "COMMENT"
OrganizerPropertyName = "ORGANIZER"
AttendeePropertyName = "ATTENDEE"
ExceptionDateTimesPropertyName = "EXDATE"
RecurrenceDateTimesPropertyName = "RDATE"
RecurrenceRulePropertyName = "RRULE"
LocationPropertyName = "LOCATION"
)
type ParameterName string
const (
CanonicalNameParameterName ParameterName = "CN"
TimeZoneIdPropertyName = "TZID"
ValuePropertyName = "VALUE"
AlternateRepresentationName = "ALTREP"
)
type Params map[ParameterName]string
func (p PropertyName) Equals(test string) bool {
return strings.EqualFold(string(p), test)
}

View File

@ -0,0 +1,177 @@
package properties
import (
"fmt"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"strings"
)
var _ = log.Print
var propNameSanitizer = strings.NewReplacer(
"_", "-",
":", "\\:",
)
var propValueSanitizer = strings.NewReplacer(
"\"", "'",
"\\", "\\\\",
"\n", "\\n",
)
var propNameDesanitizer = strings.NewReplacer(
"-", "_",
"\\:", ":",
)
var propValueDesanitizer = strings.NewReplacer(
"'", "\"",
"\\\\", "\\",
"\\n", "\n",
)
type Property struct {
Name PropertyName
Value, DefaultValue string
Params Params
OmitEmpty, Required bool
}
func (p *Property) HasNameAndValue() bool {
return p.Name != "" && p.Value != ""
}
func (p *Property) Merge(override *Property) {
if override.Name != "" {
p.Name = override.Name
}
if override.Value != "" {
p.Value = override.Value
}
if override.Params != nil {
p.Params = override.Params
}
}
func PropertyFromStructField(fs reflect.StructField) (p *Property) {
ftag := fs.Tag.Get("ical")
if fs.PkgPath != "" || ftag == "-" {
return
}
p = new(Property)
// parse the field tag
if ftag != "" {
tags := strings.Split(ftag, ",")
p.Name = PropertyName(tags[0])
if len(tags) > 1 {
if tags[1] == "omitempty" {
p.OmitEmpty = true
} else if tags[1] == "required" {
p.Required = true
} else {
p.DefaultValue = tags[1]
}
}
}
// make sure we have a name
if p.Name == "" {
p.Name = PropertyName(fs.Name)
}
p.Name = PropertyName(strings.ToUpper(string(p.Name)))
return
}
func MarshalProperty(p *Property) string {
name := strings.ToUpper(propNameSanitizer.Replace(string(p.Name)))
value := propValueSanitizer.Replace(p.Value)
keys := []string{name}
for name, value := range p.Params {
name = ParameterName(strings.ToUpper(propNameSanitizer.Replace(string(name))))
value = propValueSanitizer.Replace(value)
if strings.ContainsAny(value, " :") {
keys = append(keys, fmt.Sprintf("%s=\"%s\"", name, value))
} else {
keys = append(keys, fmt.Sprintf("%s=%s", name, value))
}
}
name = strings.Join(keys, ";")
return fmt.Sprintf("%s:%s", name, value)
}
func PropertyFromInterface(target interface{}) (p *Property, err error) {
var ierr error
if va, ok := target.(CanValidateValue); ok {
if ierr = va.ValidateICalValue(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed validation", target, ierr)
return
}
}
p = new(Property)
if enc, ok := target.(CanEncodeName); ok {
if p.Name, ierr = enc.EncodeICalName(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed name encoding", target, ierr)
return
}
}
if enc, ok := target.(CanEncodeParams); ok {
if p.Params, ierr = enc.EncodeICalParams(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed params encoding", target, ierr)
return
}
}
if enc, ok := target.(CanEncodeValue); ok {
if p.Value, ierr = enc.EncodeICalValue(); ierr != nil {
err = utils.NewError(PropertyFromInterface, "interface failed value encoding", target, ierr)
return
}
}
return
}
func UnmarshalProperty(line string) *Property {
nvp := strings.SplitN(line, ":", 2)
prop := new(Property)
if len(nvp) > 1 {
prop.Value = strings.TrimSpace(nvp[1])
}
npp := strings.Split(nvp[0], ";")
if len(npp) > 1 {
prop.Params = make(map[ParameterName]string, 0)
for i := 1; i < len(npp); i++ {
var key, value string
kvp := strings.Split(npp[i], "=")
key = strings.TrimSpace(kvp[0])
key = propNameDesanitizer.Replace(key)
if len(kvp) > 1 {
value = strings.TrimSpace(kvp[1])
value = propValueDesanitizer.Replace(value)
value = strings.Trim(value, "\"")
}
prop.Params[ParameterName(key)] = value
}
}
prop.Name = PropertyName(strings.TrimSpace(npp[0]))
prop.Name = PropertyName(propNameDesanitizer.Replace(string(prop.Name)))
prop.Value = propValueDesanitizer.Replace(prop.Value)
return prop
}
func NewProperty(name, value string) *Property {
return &Property{Name: PropertyName(name), Value: value}
}

View File

@ -0,0 +1,78 @@
package icalendar
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"strings"
)
var _ = log.Print
func isInvalidOrEmptyValue(v reflect.Value) bool {
if !v.IsValid() {
return true
}
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
func newValue(in reflect.Value) (out reflect.Value, isArrayElement bool) {
typ := in.Type()
kind := typ.Kind()
for {
if kind == reflect.Array || kind == reflect.Slice {
isArrayElement = true
} else if kind != reflect.Ptr {
break
}
typ = typ.Elem()
kind = typ.Kind()
}
out = reflect.New(typ)
return
}
func dereferencePointerValue(v reflect.Value) reflect.Value {
for (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.Elem().IsValid() {
v = v.Elem()
}
return v
}
func extractTagFromValue(v reflect.Value) (string, error) {
vdref := dereferencePointerValue(v)
vtemp, _ := newValue(vdref)
if encoder, ok := vtemp.Interface().(properties.CanEncodeTag); ok {
if tag, err := encoder.EncodeICalTag(); err != nil {
return "", utils.NewError(extractTagFromValue, "unable to extract tag from interface", v.Interface(), err)
} else {
return strings.ToUpper(tag), nil
}
} else {
typ := vtemp.Elem().Type()
return strings.ToUpper(fmt.Sprintf("v%s", typ.Name())), nil
}
}

View File

@ -0,0 +1,365 @@
package icalendar
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"reflect"
"regexp"
"strconv"
"strings"
)
var _ = log.Print
var splitter = regexp.MustCompile("\r?\n")
type token struct {
name string
components map[string][]*token
properties map[properties.PropertyName][]*properties.Property
}
func tokenize(encoded string) (*token, error) {
if encoded = strings.TrimSpace(encoded); encoded == "" {
return nil, utils.NewError(tokenize, "no content to tokenize", encoded, nil)
}
return tokenizeSlice(splitter.Split(encoded, -1))
}
func tokenizeSlice(slice []string, name ...string) (*token, error) {
tok := new(token)
size := len(slice)
if len(name) > 0 {
tok.name = name[0]
} else if size <= 0 {
return nil, utils.NewError(tokenizeSlice, "token has no content", slice, nil)
}
tok.properties = make(map[properties.PropertyName][]*properties.Property, 0)
tok.components = make(map[string][]*token, 0)
for i := 0; i < size; i++ {
// Handle iCalendar's space-indented line break format
// See: https://www.ietf.org/rfc/rfc2445.txt section 4.1
// "a long line can be split between any two characters by inserting a CRLF immediately followed by a single
// linear white space character"
line := slice[i]
for ; i < size-1 && strings.HasPrefix(slice[i+1], " "); i++ {
next := slice[i+1]
line += next[1:len(next)]
}
prop := properties.UnmarshalProperty(line)
if prop.Name.Equals("begin") {
for j := i; j < size; j++ {
end := strings.Replace(line, "BEGIN", "END", 1)
if slice[j] == end {
if component, err := tokenizeSlice(slice[i+1:j], prop.Value); err != nil {
msg := fmt.Sprintf("unable to tokenize %s component", prop.Value)
return nil, utils.NewError(tokenizeSlice, msg, slice, err)
} else {
existing, _ := tok.components[prop.Value]
tok.components[prop.Value] = append(existing, component)
i = j
break
}
}
}
} else if existing, ok := tok.properties[prop.Name]; ok {
tok.properties[prop.Name] = []*properties.Property{prop}
} else {
tok.properties[prop.Name] = append(existing, prop)
}
}
return tok, nil
}
func hydrateInterface(v reflect.Value, prop *properties.Property) (bool, error) {
// unable to decode into empty values
if isInvalidOrEmptyValue(v) {
return false, nil
}
var i = v.Interface()
var hasValue = false
// decode a value if possible
if decoder, ok := i.(properties.CanDecodeValue); ok {
if err := decoder.DecodeICalValue(prop.Value); err != nil {
return false, utils.NewError(hydrateInterface, "error decoding property value", v, err)
} else {
hasValue = true
}
}
// decode any params, if supported
if len(prop.Params) > 0 {
if decoder, ok := i.(properties.CanDecodeParams); ok {
if err := decoder.DecodeICalParams(prop.Params); err != nil {
return false, utils.NewError(hydrateInterface, "error decoding property parameters", v, err)
}
}
}
// finish with any validation
if validator, ok := i.(properties.CanValidateValue); ok {
if err := validator.ValidateICalValue(); err != nil {
return false, utils.NewError(hydrateInterface, "error validating property value", v, err)
}
}
return hasValue, nil
}
func hydrateLiteral(v reflect.Value, prop *properties.Property) (reflect.Value, error) {
literal := dereferencePointerValue(v)
switch literal.Kind() {
case reflect.Bool:
if i, err := strconv.ParseBool(prop.Value); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode bool "+prop.Value, literal.Interface(), err)
} else {
literal.SetBool(i)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if i, err := strconv.ParseInt(prop.Value, 10, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode int "+prop.Value, literal.Interface(), err)
} else {
literal.SetInt(i)
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
if i, err := strconv.ParseUint(prop.Value, 10, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode uint "+prop.Value, literal.Interface(), err)
} else {
literal.SetUint(i)
}
case reflect.Float32, reflect.Float64:
if i, err := strconv.ParseFloat(prop.Value, 64); err != nil {
return literal, utils.NewError(hydrateLiteral, "unable to decode float "+prop.Value, literal.Interface(), err)
} else {
literal.SetFloat(i)
}
case reflect.String:
literal.SetString(prop.Value)
default:
return literal, utils.NewError(hydrateLiteral, "unable to decode value as literal "+prop.Value, literal.Interface(), nil)
}
return literal, nil
}
func hydrateProperty(v reflect.Value, prop *properties.Property) error {
// check to see if the interface handles it's own hydration
if handled, err := hydrateInterface(v, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate interface", v, err)
} else if handled {
return nil // exit early if handled by the interface
}
// if we got here, we need to create a new instance to
// set into the property.
var vnew, varr = newValue(v)
var vlit bool
// check to see if the new value handles it's own hydration
if handled, err := hydrateInterface(vnew, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new interface value", vnew, err)
} else if vlit = !handled; vlit {
// if not, treat it as a literal
if vnewlit, err := hydrateLiteral(vnew, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new literal value", vnew, err)
} else if _, err := hydrateInterface(vnewlit, prop); err != nil {
return utils.NewError(hydrateProperty, "unable to hydrate new literal interface value", vnewlit, err)
}
}
// now we can set the value
vnewval := dereferencePointerValue(vnew)
voldval := dereferencePointerValue(v)
// make sure we can set the new value into the provided pointer
if varr {
// for arrays, append the new value into the array structure
if !voldval.CanSet() {
return utils.NewError(hydrateProperty, "unable to set array value", v, nil)
} else {
voldval.Set(reflect.Append(voldval, vnewval))
}
} else if vlit {
// for literals, set the dereferenced value
if !voldval.CanSet() {
return utils.NewError(hydrateProperty, "unable to set literal value", v, nil)
} else {
voldval.Set(vnewval)
}
} else if !v.CanSet() {
return utils.NewError(hydrateProperty, "unable to set pointer value", v, nil)
} else {
// everything else should be a pointer, set it directly
v.Set(vnew)
}
return nil
}
func hydrateNestedComponent(v reflect.Value, component *token) error {
// create a new object to hold the property value
var vnew, varr = newValue(v)
if err := hydrateComponent(vnew, component); err != nil {
return utils.NewError(hydrateNestedComponent, "unable to decode component", component, err)
}
if varr {
// for arrays, append the new value into the array structure
voldval := dereferencePointerValue(v)
if !voldval.CanSet() {
return utils.NewError(hydrateNestedComponent, "unable to set array value", v, nil)
} else {
voldval.Set(reflect.Append(voldval, vnew))
}
} else if !v.CanSet() {
return utils.NewError(hydrateNestedComponent, "unable to set pointer value", v, nil)
} else {
// everything else should be a pointer, set it directly
v.Set(vnew)
}
return nil
}
func hydrateProperties(v reflect.Value, component *token) error {
vdref := dereferencePointerValue(v)
vtype := vdref.Type()
vkind := vdref.Kind()
if vkind != reflect.Struct {
return utils.NewError(hydrateProperties, "unable to hydrate properties of non-struct", v, nil)
}
n := vtype.NumField()
for i := 0; i < n; i++ {
prop := properties.PropertyFromStructField(vtype.Field(i))
if prop == nil {
continue // skip if field is ignored
}
vfield := vdref.Field(i)
// first try to hydrate property values
if properties, ok := component.properties[prop.Name]; ok {
for _, prop := range properties {
if err := hydrateProperty(vfield, prop); err != nil {
msg := fmt.Sprintf("unable to hydrate property %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
}
}
}
// then try to hydrate components
vtemp, _ := newValue(vfield)
if tag, err := extractTagFromValue(vtemp); err != nil {
msg := fmt.Sprintf("unable to extract tag from property %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
} else if components, ok := component.components[tag]; ok {
for _, comp := range components {
if err := hydrateNestedComponent(vfield, comp); err != nil {
msg := fmt.Sprintf("unable to hydrate component %s", prop.Name)
return utils.NewError(hydrateProperties, msg, v, err)
}
}
}
}
return nil
}
func hydrateComponent(v reflect.Value, component *token) error {
if tag, err := extractTagFromValue(v); err != nil {
return utils.NewError(hydrateComponent, "error extracting tag from value", component, err)
} else if tag != component.name {
msg := fmt.Sprintf("expected %s and found %s", tag, component.name)
return utils.NewError(hydrateComponent, msg, component, nil)
} else if err := hydrateProperties(v, component); err != nil {
return utils.NewError(hydrateComponent, "unable to hydrate properties", component, err)
}
return nil
}
func hydrateComponents(v reflect.Value, components []*token) error {
vdref := dereferencePointerValue(v)
for i, component := range components {
velem := reflect.New(vdref.Type().Elem())
if err := hydrateComponent(velem, component); err != nil {
msg := fmt.Sprintf("unable to hydrate component %d", i)
return utils.NewError(hydrateComponent, msg, component, err)
} else {
v.Set(reflect.Append(vdref, velem))
}
}
return nil
}
func hydrateValue(v reflect.Value, component *token) error {
if !v.IsValid() || v.Kind() != reflect.Ptr {
return utils.NewError(hydrateValue, "unmarshal target must be a valid pointer", v, nil)
}
// handle any encodable properties
if encoder, isprop := v.Interface().(properties.CanEncodeName); isprop {
if name, err := encoder.EncodeICalName(); err != nil {
return utils.NewError(hydrateValue, "unable to lookup property name", v, err)
} else if properties, found := component.properties[name]; !found || len(properties) == 0 {
return utils.NewError(hydrateValue, "no matching propery values found for "+string(name), v, nil)
} else if len(properties) > 1 {
return utils.NewError(hydrateValue, "more than one property value matches single property interface", v, nil)
} else {
return hydrateProperty(v, properties[0])
}
}
// handle components
vkind := dereferencePointerValue(v).Kind()
if tag, err := extractTagFromValue(v); err != nil {
return utils.NewError(hydrateValue, "unable to extract component tag", v, err)
} else if components, found := component.components[tag]; !found || len(components) == 0 {
msg := fmt.Sprintf("unable to find matching component for %s", tag)
return utils.NewError(hydrateValue, msg, v, nil)
} else if vkind == reflect.Array || vkind == reflect.Slice {
return hydrateComponents(v, components)
} else if len(components) > 1 {
return utils.NewError(hydrateValue, "non-array interface provided but more than one component found!", v, nil)
} else {
return hydrateComponent(v, components[0])
}
}
// decodes encoded icalendar data into a native interface
func Unmarshal(encoded string, into interface{}) error {
if component, err := tokenize(encoded); err != nil {
return utils.NewError(Unmarshal, "unable to tokenize encoded data", encoded, err)
} else {
return hydrateValue(reflect.ValueOf(into), component)
}
}

View File

@ -0,0 +1,7 @@
package values
type CalScale string
const (
GregorianCalScale CalScale = "GREGORIAN"
)

View File

@ -0,0 +1,33 @@
package values
import (
"github.com/dolanor/caldav-go/icalendar/properties"
)
// specifies non-processing information intended to provide a comment to the calendar user.
type Comment string
// encodes the comment value for the iCalendar specification
func (c Comment) EncodeICalValue() (string, error) {
return string(c), nil
}
// decodes the comment value from the iCalendar specification
func (c Comment) DecodeICalValue(value string) error {
c = Comment(value)
return nil
}
// encodes the comment value for the iCalendar specification
func (c Comment) EncodeICalName() (properties.PropertyName, error) {
return properties.CommentPropertyName, nil
}
// creates a list of comments from strings
func NewComments(comments ...string) []Comment {
var _comments []Comment
for _, comment := range comments {
_comments = append(_comments, Comment(comment))
}
return _comments
}

View File

@ -0,0 +1,139 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"net/mail"
"strings"
)
var _ = log.Print
// Specifies the organizer of a group scheduled calendar entity. The property is specified within the "VFREEBUSY"
// calendar component to specify the calendar user requesting the free or busy time. When publishing a "VFREEBUSY"
// calendar component, the property is used to specify the calendar that the published busy time came from.
//
// The property has the property parameters CN, for specifying the common or display name associated with the
// "Organizer", DIR, for specifying a pointer to the directory information associated with the "Organizer",
// SENT-BY, for specifying another calendar user that is acting on behalf of the "Organizer". The non-standard
// parameters may also be specified on this property. If the LANGUAGE property parameter is specified, the identified
// language applies to the CN parameter value.
type Contact struct {
Entry mail.Address
}
type AttendeeContact Contact
type OrganizerContact Contact
// creates a new icalendar attendee representation
func NewAttendeeContact(name, email string) *AttendeeContact {
return &AttendeeContact{Entry: mail.Address{Name: name, Address: email}}
}
// creates a new icalendar organizer representation
func NewOrganizerContact(name, email string) *OrganizerContact {
return &OrganizerContact{Entry: mail.Address{Name: name, Address: email}}
}
// validates the contact value for the iCalendar specification
func (c *Contact) ValidateICalValue() error {
email := c.Entry.String()
if _, err := mail.ParseAddress(email); err != nil {
msg := fmt.Sprintf("unable to validate address %s", email)
return utils.NewError(c.ValidateICalValue, msg, c, err)
} else {
return nil
}
}
// encodes the contact value for the iCalendar specification
func (c *Contact) EncodeICalValue() (string, error) {
return fmt.Sprintf("MAILTO:%s", c.Entry.Address), nil
}
// encodes the contact params for the iCalendar specification
func (c *Contact) EncodeICalParams() (params properties.Params, err error) {
if c.Entry.Name != "" {
params = properties.Params{properties.CanonicalNameParameterName: c.Entry.Name}
}
return
}
// decodes the contact value from the iCalendar specification
func (c *Contact) DecodeICalValue(value string) error {
parts := strings.SplitN(value, ":", 2)
if len(parts) > 1 {
c.Entry.Address = parts[1]
}
return nil
}
// decodes the contact params from the iCalendar specification
func (c *Contact) DecodeICalParams(params properties.Params) error {
if name, found := params[properties.CanonicalNameParameterName]; found {
c.Entry.Name = name
}
return nil
}
// validates the contact value for the iCalendar specification
func (c *OrganizerContact) ValidateICalValue() error {
return (*Contact)(c).ValidateICalValue()
}
// encodes the contact value for the iCalendar specification
func (c *OrganizerContact) EncodeICalValue() (string, error) {
return (*Contact)(c).EncodeICalValue()
}
// encodes the contact params for the iCalendar specification
func (c *OrganizerContact) EncodeICalParams() (params properties.Params, err error) {
return (*Contact)(c).EncodeICalParams()
}
// decodes the contact value from the iCalendar specification
func (c *OrganizerContact) DecodeICalValue(value string) error {
return (*Contact)(c).DecodeICalValue(value)
}
// decodes the contact params from the iCalendar specification
func (c *OrganizerContact) DecodeICalParams(params properties.Params) error {
return (*Contact)(c).DecodeICalParams(params)
}
// encodes the contact property name for the iCalendar specification
func (o *OrganizerContact) EncodeICalName() (properties.PropertyName, error) {
return properties.OrganizerPropertyName, nil
}
// validates the contact value for the iCalendar specification
func (c *AttendeeContact) ValidateICalValue() error {
return (*Contact)(c).ValidateICalValue()
}
// encodes the contact value for the iCalendar specification
func (c *AttendeeContact) EncodeICalValue() (string, error) {
return (*Contact)(c).EncodeICalValue()
}
// encodes the contact params for the iCalendar specification
func (c *AttendeeContact) EncodeICalParams() (params properties.Params, err error) {
return (*Contact)(c).EncodeICalParams()
}
// decodes the contact value from the iCalendar specification
func (c *AttendeeContact) DecodeICalValue(value string) error {
return (*Contact)(c).DecodeICalValue(value)
}
// decodes the contact params from the iCalendar specification
func (c *AttendeeContact) DecodeICalParams(params properties.Params) error {
return (*Contact)(c).DecodeICalParams(params)
}
// encodes the contact property name for the iCalendar specification
func (o *AttendeeContact) EncodeICalName() (properties.PropertyName, error) {
return properties.AttendeePropertyName, nil
}

View File

@ -0,0 +1,24 @@
package values
import (
"log"
"strings"
)
var _ = log.Print
type CSV []string
func (csv *CSV) EncodeICalValue() (string, error) {
return strings.Join(*csv, ","), nil
}
func (csv *CSV) DecodeICalValue(value string) error {
value = strings.TrimSpace(value)
*csv = CSV(strings.Split(value, ","))
return nil
}
func NewCSV(items ...string) *CSV {
return (*CSV)(&items)
}

View File

@ -0,0 +1,265 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"strings"
"time"
)
var _ = log.Print
const DateFormatString = "20060102"
const DateTimeFormatString = "20060102T150405"
const UTCDateTimeFormatString = "20060102T150405Z"
// a representation of a date and time for iCalendar
type DateTime struct {
t time.Time
}
type DateTimes []*DateTime
// The exception dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence set
// is generated by gathering all of the start date-times generated by any of the specified "RRULE" and "RDATE"
// properties, and then excluding any start date and times which fall within the union of start date and times
// generated by any specified "EXRULE" and "EXDATE" properties. This implies that start date and times within exclusion
// related properties (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
// one recurrence is considered. Duplicate instances are ignored.
//
// The "EXDATE" property can be used to exclude the value specified in "DTSTART". However, in such cases the original
// "DTSTART" date MUST still be maintained by the calendaring and scheduling system because the original "DTSTART"
// value has inherent usage dependencies by other properties such as the "RECURRENCE-ID".
type ExceptionDateTimes DateTimes
// The recurrence dates, if specified, are used in computing the recurrence set. The recurrence set is the complete set
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence set
// is generated by gathering all of the start date-times generated by any of the specified "RRULE" and "RDATE"
// properties, and then excluding any start date and times which fall within the union of start date and times
// generated by any specified "EXRULE" and "EXDATE" properties. This implies that start date and times within exclusion
// related properties (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
// one recurrence is considered. Duplicate instances are ignored.
type RecurrenceDateTimes DateTimes
// creates a new icalendar datetime representation
func NewDateTime(t time.Time) *DateTime {
return &DateTime{t: t.Truncate(time.Second)}
}
// creates a new icalendar datetime array representation
func NewDateTimes(dates ...*DateTime) DateTimes {
return DateTimes(dates)
}
// creates a new icalendar datetime array representation
func NewExceptionDateTimes(dates ...*DateTime) *ExceptionDateTimes {
datetimes := NewDateTimes(dates...)
return (*ExceptionDateTimes)(&datetimes)
}
// creates a new icalendar datetime array representation
func NewRecurrenceDateTimes(dates ...*DateTime) *RecurrenceDateTimes {
datetimes := NewDateTimes(dates...)
return (*RecurrenceDateTimes)(&datetimes)
}
// checks to see if two datetimes are equal
func (d *DateTime) Equals(test *DateTime) bool {
return d.t.Equal(test.t)
}
// returns the native time for the datetime object
func (d *DateTime) NativeTime() time.Time {
return d.t
}
// encodes the datetime value for the iCalendar specification
func (d *DateTime) EncodeICalValue() (string, error) {
val := d.t.Format(DateTimeFormatString)
loc := d.t.Location()
if loc == time.UTC {
val = fmt.Sprintf("%sZ", val)
}
return val, nil
}
// decodes the datetime value from the iCalendar specification
func (d *DateTime) DecodeICalValue(value string) error {
layout := DateTimeFormatString
if strings.HasSuffix(value, "Z") {
layout = UTCDateTimeFormatString
} else if len(value) == 8 {
layout = DateFormatString
}
var err error
d.t, err = time.ParseInLocation(layout, value, time.UTC)
if err != nil {
return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err)
} else {
return nil
}
}
// encodes the datetime params for the iCalendar specification
func (d *DateTime) EncodeICalParams() (params properties.Params, err error) {
loc := d.t.Location()
if loc != time.UTC {
params = properties.Params{properties.TimeZoneIdPropertyName: loc.String()}
}
return
}
// decodes the datetime params from the iCalendar specification
func (d *DateTime) DecodeICalParams(params properties.Params) error {
layout := DateTimeFormatString
value := d.t.Format(layout)
if name, found := params[properties.TimeZoneIdPropertyName]; !found {
return nil
} else if loc, err := time.LoadLocation(name); err != nil {
return utils.NewError(d.DecodeICalValue, "unable to parse timezone", d, err)
} else if t, err := time.ParseInLocation(layout, value, loc); err != nil {
return utils.NewError(d.DecodeICalValue, "unable to parse datetime value", d, err)
} else {
d.t = t
return nil
}
}
// validates the datetime value against the iCalendar specification
func (d *DateTime) ValidateICalValue() error {
loc := d.t.Location()
if loc == time.Local {
msg := "DateTime location may not Local, please use UTC or explicit Location"
return utils.NewError(d.ValidateICalValue, msg, d, nil)
}
if loc.String() == "" {
msg := "DateTime location must have a valid name"
return utils.NewError(d.ValidateICalValue, msg, d, nil)
}
return nil
}
// encodes the datetime value for the iCalendar specification
func (d *DateTime) String() string {
if s, err := d.EncodeICalValue(); err != nil {
panic(err)
} else {
return s
}
}
// encodes a list of datetime values for the iCalendar specification
func (ds *DateTimes) EncodeICalValue() (string, error) {
var csv CSV
for i, d := range *ds {
if s, err := d.EncodeICalValue(); err != nil {
msg := fmt.Sprintf("unable to encode datetime at index %d", i)
return "", utils.NewError(ds.EncodeICalValue, msg, ds, err)
} else {
csv = append(csv, s)
}
}
return csv.EncodeICalValue()
}
// encodes a list of datetime params for the iCalendar specification
func (ds *DateTimes) EncodeICalParams() (params properties.Params, err error) {
if len(*ds) > 0 {
params, err = (*ds)[0].EncodeICalParams()
}
return
}
// decodes a list of datetime params from the iCalendar specification
func (ds *DateTimes) DecodeICalParams(params properties.Params) error {
for i, d := range *ds {
if err := d.DecodeICalParams(params); err != nil {
msg := fmt.Sprintf("unable to decode datetime params for index %d", i)
return utils.NewError(ds.DecodeICalValue, msg, ds, err)
}
}
return nil
}
// encodes a list of datetime values for the iCalendar specification
func (ds *DateTimes) DecodeICalValue(value string) error {
csv := new(CSV)
if err := csv.DecodeICalValue(value); err != nil {
return utils.NewError(ds.DecodeICalValue, "unable to decode datetime list as CSV", ds, err)
}
for i, value := range *csv {
d := new(DateTime)
if err := d.DecodeICalValue(value); err != nil {
msg := fmt.Sprintf("unable to decode datetime at index %d", i)
return utils.NewError(ds.DecodeICalValue, msg, ds, err)
} else {
*ds = append(*ds, d)
}
}
return nil
}
// encodes exception date times property name for icalendar
func (e *ExceptionDateTimes) EncodeICalName() (properties.PropertyName, error) {
return properties.ExceptionDateTimesPropertyName, nil
}
// encodes recurrence date times property name for icalendar
func (r *RecurrenceDateTimes) EncodeICalName() (properties.PropertyName, error) {
return properties.RecurrenceDateTimesPropertyName, nil
}
// encodes exception date times property value for icalendar
func (e *ExceptionDateTimes) EncodeICalValue() (string, error) {
return (*DateTimes)(e).EncodeICalValue()
}
// encodes recurrence date times property value for icalendar
func (r *RecurrenceDateTimes) EncodeICalValue() (string, error) {
return (*DateTimes)(r).EncodeICalValue()
}
// decodes exception date times property value for icalendar
func (e *ExceptionDateTimes) DecodeICalValue(value string) error {
return (*DateTimes)(e).DecodeICalValue(value)
}
// decodes recurrence date times property value for icalendar
func (r *RecurrenceDateTimes) DecodeICalValue(value string) error {
return (*DateTimes)(r).DecodeICalValue(value)
}
// encodes exception date times property params for icalendar
func (e *ExceptionDateTimes) EncodeICalParams() (params properties.Params, err error) {
return (*DateTimes)(e).EncodeICalParams()
}
// encodes recurrence date times property params for icalendar
func (r *RecurrenceDateTimes) EncodeICalParams() (params properties.Params, err error) {
return (*DateTimes)(r).EncodeICalParams()
}
// encodes exception date times property params for icalendar
func (e *ExceptionDateTimes) DecodeICalParams(params properties.Params) error {
return (*DateTimes)(e).DecodeICalParams(params)
}
// encodes recurrence date times property params for icalendar
func (r *RecurrenceDateTimes) DecodeICalParams(params properties.Params) error {
return (*DateTimes)(r).DecodeICalParams(params)
}

View File

@ -0,0 +1,132 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/utils"
"log"
"math"
"regexp"
"strconv"
"strings"
"time"
)
var _ = log.Print
// a representation of duration for iCalendar
type Duration struct {
d time.Duration
}
// breaks apart the duration into its component time parts
func (d *Duration) Decompose() (weeks, days, hours, minutes, seconds int64) {
// chip away at this
rem := time.Duration(math.Abs(float64(d.d)))
div := time.Hour * 24 * 7
weeks = int64(rem / div)
rem = rem % div
div = div / 7
days = int64(rem / div)
rem = rem % div
div = div / 24
hours = int64(rem / div)
rem = rem % div
div = div / 60
minutes = int64(rem / div)
rem = rem % div
div = div / 60
seconds = int64(rem / div)
return
}
// returns the native golang duration
func (d *Duration) NativeDuration() time.Duration {
return d.d
}
// returns true if the duration is negative
func (d *Duration) IsPast() bool {
return d.d < 0
}
// encodes the duration of time into iCalendar format
func (d *Duration) EncodeICalValue() (string, error) {
var parts []string
weeks, days, hours, minutes, seconds := d.Decompose()
if d.IsPast() {
parts = append(parts, "-")
}
parts = append(parts, "P")
if weeks > 0 {
parts = append(parts, fmt.Sprintf("%dW", weeks))
}
if days > 0 {
parts = append(parts, fmt.Sprintf("%dD", days))
}
if hours > 0 || minutes > 0 || seconds > 0 {
parts = append(parts, "T")
if hours > 0 {
parts = append(parts, fmt.Sprintf("%dH", hours))
}
if minutes > 0 {
parts = append(parts, fmt.Sprintf("%dM", minutes))
}
if seconds > 0 {
parts = append(parts, fmt.Sprintf("%dS", seconds))
}
}
return strings.Join(parts, ""), nil
}
var durationRegEx = regexp.MustCompile("(\\d+)(\\w)")
// decodes the duration of time from iCalendar format
func (d *Duration) DecodeICalValue(value string) error {
var seconds int64
var isPast = strings.HasPrefix(value, "-P")
var matches = durationRegEx.FindAllStringSubmatch(value, -1)
for _, match := range matches {
var multiplier int64
ivalue, err := strconv.ParseInt(match[1], 10, 64)
if err != nil {
return utils.NewError(d.DecodeICalValue, "unable to decode duration value "+match[1], d, nil)
}
switch match[2] {
case "S":
multiplier = 1
case "M":
multiplier = 60
case "H":
multiplier = 60 * 60
case "D":
multiplier = 60 * 60 * 24
case "W":
multiplier = 60 * 60 * 24 * 7
default:
return utils.NewError(d.DecodeICalValue, "unable to decode duration segment "+match[2], d, nil)
}
seconds = seconds + multiplier*ivalue
}
d.d = time.Duration(seconds) * time.Second
if isPast {
d.d = -d.d
}
return nil
}
func (d *Duration) String() string {
if s, err := d.EncodeICalValue(); err != nil {
panic(err)
} else {
return s
}
}
// creates a new iCalendar duration representation
func NewDuration(d time.Duration) *Duration {
return &Duration{d: d}
}

View File

@ -0,0 +1,17 @@
package values
// An access classification is only one component of the general security system within a calendar application.
// It provides a method of capturing the scope of the access the calendar owner intends for information within an
// individual calendar entry. The access classification of an individual iCalendar component is useful when measured
// along with the other security components of a calendar system (e.g., calendar user authentication, authorization,
// access rights, access role, etc.). Hence, the semantics of the individual access classifications cannot be completely
// defined by this memo alone. Additionally, due to the "blind" nature of most exchange processes using this memo, these
// access classifications cannot serve as an enforcement statement for a system receiving an iCalendar object. Rather,
// they provide a method for capturing the intention of the calendar owner for the access to the calendar component.
type EventAccessClassification string
const (
PublicEventAccessClassification EventAccessClassification = "PUBLIC"
PrivateEventAccessClassification = "PRIVATE"
ConfidentialEventAccessClassification = "CONFIDENTIAL"
)

View File

@ -0,0 +1,13 @@
package values
// In a group scheduled calendar component, the property is used by the "Organizer" to provide a confirmation of the
// event to the "Attendees".
// For example in an Event calendar component, the "Organizer" can indicate that a meeting is tentative, confirmed or
// cancelled.
type EventStatus string
const (
TentativeEventStatus EventStatus = "TENTATIVE" // Indicates event is tentative.
ConfirmedEventStatus = "CONFIRMED" // Indicates event is definite.
CancelledEventStatus = "CANCELLED" // Indicates event is cancelled.
)

View File

@ -0,0 +1,69 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/utils"
"log"
"strconv"
"strings"
)
var _ = log.Print
// a representation of a geographical point for iCalendar
type Geo struct {
coords []float64
}
// creates a new icalendar geo representation
func NewGeo(lat, lng float64) *Geo {
return &Geo{coords: []float64{lat, lng}}
}
// returns the latitude encoded into the geo point
func (g *Geo) Lat() float64 {
return g.coords[0]
}
// returns the longitude encoded into the geo point
func (g *Geo) Lng() float64 {
return g.coords[1]
}
// validates the geo value against the iCalendar specification
func (g *Geo) ValidateICalValue() error {
if len(g.coords) != 2 {
return utils.NewError(g.ValidateICalValue, "geo value must have length of 2", g, nil)
}
if g.Lat() < -90 || g.Lat() > 90 {
return utils.NewError(g.ValidateICalValue, "geo latitude must be between -90 and 90 degrees", g, nil)
}
if g.Lng() < -180 || g.Lng() > 180 {
return utils.NewError(g.ValidateICalValue, "geo longitude must be between -180 and 180 degrees", g, nil)
}
return nil
}
// encodes the geo value for the iCalendar specification
func (g *Geo) EncodeICalValue() (string, error) {
return fmt.Sprintf("%f %f", g.Lat(), g.Lng()), nil
}
// decodes the geo value from the iCalendar specification
func (g *Geo) DecodeICalValue(value string) error {
if latlng := strings.Split(value, " "); len(latlng) < 2 {
return utils.NewError(g.DecodeICalValue, "geo value must have both a latitude and longitude component", g, nil)
} else if lat, err := strconv.ParseFloat(latlng[0], 64); err != nil {
return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err)
} else if lng, err := strconv.ParseFloat(latlng[1], 64); err != nil {
return utils.NewError(g.DecodeICalValue, "unable to decode latitude component", g, err)
} else {
*g = Geo{coords: []float64{lat, lng}}
return nil
}
}

View File

@ -0,0 +1,78 @@
package values
import (
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"net/url"
)
var _ = log.Print
// Specific venues such as conference or meeting rooms may be explicitly specified using this property. An alternate
// representation may be specified that is a URI that points to directory information with more structured specification
// of the location. For example, the alternate representation may specify either an LDAP URI pointing to an LDAP server
// entry or a CID URI pointing to a MIME body part containing a vCard [RFC 2426] for the location.
type Location struct {
value string
altrep *url.URL
}
// creates a new icalendar location representation
func NewLocation(value string, altrep ...*url.URL) *Location {
loc := &Location{value: value}
if len(altrep) > 0 {
loc.altrep = altrep[0]
}
return loc
}
// returns an alternate representation for the location
// if one exists
func (l *Location) AltRep() *url.URL {
return l.altrep
}
// encodes the location for the iCalendar specification
func (l *Location) EncodeICalValue() (string, error) {
return l.value, nil
}
// decodes the location from the iCalendar specification
func (l *Location) DecodeICalValue(value string) error {
l.value = value
return nil
}
// encodes the location params for the iCalendar specification
func (l *Location) EncodeICalParams() (params properties.Params, err error) {
if l.altrep != nil {
params = properties.Params{properties.AlternateRepresentationName: l.altrep.String()}
}
return
}
// decodes the location params from the iCalendar specification
func (l *Location) DecodeICalParams(params properties.Params) error {
if rep, found := params[properties.AlternateRepresentationName]; !found {
return nil
} else if altrep, err := url.Parse(rep); err != nil {
return utils.NewError(l.DecodeICalValue, "unable to parse alternate representation", l, err)
} else {
l.altrep = altrep
return nil
}
}
// validates the location against the iCalendar specification
func (l *Location) ValidateICalValue() error {
if l.altrep != nil {
if _, err := url.Parse(l.altrep.String()); err != nil {
msg := "location alternate representation must be a valid url"
return utils.NewError(l.ValidateICalValue, msg, l, err)
}
}
return nil
}

View File

@ -0,0 +1,7 @@
package values
type Method string
const (
PublishMethod Method = "PUBLISH"
)

View File

@ -0,0 +1,434 @@
package values
import (
"fmt"
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"log"
"regexp"
"strconv"
"strings"
)
// The recurrence rule, if specified, is used in computing the recurrence set. The recurrence set is the complete set
// of recurrence instances for a calendar component. The recurrence set is generated by considering the initial
// "DTSTART" property along with the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained within the iCalendar
// object. The "DTSTART" property defines the first instance in the recurrence set. Multiple instances of the "RRULE"
// and "EXRULE" properties can also be specified to define more sophisticated recurrence sets. The final recurrence
// set is generated by gathering all of the start date/times generated by any of the specified "RRULE" and "RDATE"
// properties, and excluding any start date/times which fall within the union of start date/times generated by any
// specified "EXRULE" and "EXDATE" properties. This implies that start date/times within exclusion related properties
// (i.e., "EXDATE" and "EXRULE") take precedence over those specified by inclusion properties
// (i.e., "RDATE" and "RRULE"). Where duplicate instances are generated by the "RRULE" and "RDATE" properties, only
// one recurrence is considered. Duplicate instances are ignored.
// The "DTSTART" and "DTEND" property pair or "DTSTART" and "DURATION" property pair, specified within the iCalendar
// object defines the first instance of the recurrence. When used with a recurrence rule, the "DTSTART" and "DTEND"
// properties MUST be specified in local time and the appropriate set of "VTIMEZONE" calendar components MUST be
// included. For detail on the usage of the "VTIMEZONE" calendar component, see the "VTIMEZONE" calendar component
// definition.
// Any duration associated with the iCalendar object applies to all members of the generated recurrence set. Any
// modified duration for specific recurrences MUST be explicitly specified using the "RDATE" property.
type RecurrenceRule struct {
Frequency RecurrenceFrequency
Until *DateTime
Count int
Interval int
BySecond []int
ByMinute []int
ByHour []int
ByDay []RecurrenceWeekday
ByMonthDay []int
ByYearDay []int
ByWeekNumber []int
ByMonth []int
BySetPosition []int
WeekStart RecurrenceWeekday
}
var _ = log.Print
// the frequency an event recurs
type RecurrenceFrequency string
const (
SecondRecurrenceFrequency RecurrenceFrequency = "SECONDLY"
MinuteRecurrenceFrequency = "MINUTELY"
HourRecurrenceFrequency = "HOURLY"
DayRecurrenceFrequency = "DAILY"
WeekRecurrenceFrequency = "WEEKLY"
MonthRecurrenceFrequency = "MONTHLY"
YearRecurrenceFrequency = "YEARLY"
)
// the frequency an event recurs
type RecurrenceWeekday string
const (
MondayRecurrenceWeekday RecurrenceWeekday = "MO"
TuesdayRecurrenceWeekday = "TU"
WednesdayRecurrenceWeekday = "WE"
ThursdayRecurrenceWeekday = "TH"
FridayRecurrenceWeekday = "FR"
SaturdayRecurrenceWeekday = "SA"
SundayRecurrenceWeekday = "SU"
)
// creates a new recurrence rule object for iCalendar
func NewRecurrenceRule(frequency RecurrenceFrequency) *RecurrenceRule {
return &RecurrenceRule{Frequency: frequency}
}
var weekdayRegExp = regexp.MustCompile("MO|TU|WE|TH|FR|SA|SU")
// returns true if weekday is a valid constant
func (r RecurrenceWeekday) IsValidWeekDay() bool {
return weekdayRegExp.MatchString(strings.ToUpper(string(r)))
}
var frequencyRegExp = regexp.MustCompile("SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY")
// returns true if weekday is a valid constant
func (r RecurrenceFrequency) IsValidFrequency() bool {
return frequencyRegExp.MatchString(strings.ToUpper(string(r)))
}
// returns the recurrence rule name for the iCalendar specification
func (r *RecurrenceRule) EncodeICalName() (properties.PropertyName, error) {
return properties.RecurrenceRulePropertyName, nil
}
// encodes the recurrence rule value for the iCalendar specification
func (r *RecurrenceRule) EncodeICalValue() (string, error) {
out := []string{fmt.Sprintf("FREQ=%s", strings.ToUpper(string(r.Frequency)))}
if r.Until != nil {
if encoded, err := r.Until.EncodeICalValue(); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode until date", r, err)
} else {
out = append(out, fmt.Sprintf("UNTIL=%s", encoded))
}
}
if r.Count > 0 {
out = append(out, fmt.Sprintf("COUNT=%d", r.Count))
}
if r.Interval > 0 {
out = append(out, fmt.Sprintf("INTERVAL=%d", r.Interval))
}
if len(r.BySecond) > 0 {
if encoded, err := intsToCSV(r.BySecond); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by second value", r, err)
} else {
out = append(out, fmt.Sprintf("BYSECOND=%s", encoded))
}
}
if len(r.ByMinute) > 0 {
if encoded, err := intsToCSV(r.ByMinute); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by minute value", r, err)
} else {
out = append(out, fmt.Sprintf("BYMINUTE=%s", encoded))
}
}
if len(r.ByHour) > 0 {
if encoded, err := intsToCSV(r.ByHour); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by hour value", r, err)
} else {
out = append(out, fmt.Sprintf("BYHOUR=%s", encoded))
}
}
if len(r.ByDay) > 0 {
if encoded, err := daysToCSV(r.ByDay); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by day value", r, err)
} else {
out = append(out, fmt.Sprintf("BYDAY=%s", encoded))
}
}
if len(r.ByMonthDay) > 0 {
if encoded, err := intsToCSV(r.ByMonthDay); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by month day value", r, err)
} else {
out = append(out, fmt.Sprintf("BYMONTHDAY=%s", encoded))
}
}
if len(r.ByYearDay) > 0 {
if encoded, err := intsToCSV(r.ByYearDay); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by year day value", r, err)
} else {
out = append(out, fmt.Sprintf("BYYEARDAY=%s", encoded))
}
}
if len(r.ByWeekNumber) > 0 {
if encoded, err := intsToCSV(r.ByWeekNumber); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by week number value", r, err)
} else {
out = append(out, fmt.Sprintf("BYWEEKNO=%s", encoded))
}
}
if len(r.ByMonth) > 0 {
if encoded, err := intsToCSV(r.ByMonth); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by month value", r, err)
} else {
out = append(out, fmt.Sprintf("BYMONTH=%s", encoded))
}
}
if len(r.BySetPosition) > 0 {
if encoded, err := intsToCSV(r.BySetPosition); err != nil {
return "", utils.NewError(r.EncodeICalValue, "unable to encode by set position value", r, err)
} else {
out = append(out, fmt.Sprintf("BYSETPOS=%s", encoded))
}
}
if r.WeekStart != "" {
out = append(out, fmt.Sprintf("WKST=%s", r.WeekStart))
}
return strings.Join(out, ";"), nil
}
var rruleParamRegExp = regexp.MustCompile("(\\w+)\\s*=\\s*([^;]+)")
// decodes the recurrence rule value from the iCalendar specification
func (r *RecurrenceRule) DecodeICalValue(value string) error {
matches := rruleParamRegExp.FindAllStringSubmatch(value, -1)
if len(matches) <= 0 {
return utils.NewError(r.DecodeICalValue, "no recurrence rules found", r, nil)
}
for _, match := range matches {
if err := r.decodeICalValue(match[1], match[2]); err != nil {
msg := fmt.Sprintf("unable to decode %s value", match[1])
return utils.NewError(r.DecodeICalValue, msg, r, err)
}
}
return nil
}
func (r *RecurrenceRule) decodeICalValue(name string, value string) error {
switch name {
case "FREQ":
r.Frequency = RecurrenceFrequency(value)
case "UNTIL":
until := new(DateTime)
if err := until.DecodeICalValue(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid until value "+value, r, err)
} else {
r.Until = until
}
case "COUNT":
if count, err := strconv.ParseInt(value, 10, 64); err != nil {
return utils.NewError(r.decodeICalValue, "invalid count value "+value, r, err)
} else {
r.Count = int(count)
}
case "INTERVAL":
if interval, err := strconv.ParseInt(value, 10, 64); err != nil {
return utils.NewError(r.decodeICalValue, "invalid interval value "+value, r, err)
} else {
r.Interval = int(interval)
}
case "BYSECOND":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by second value "+value, r, err)
} else {
r.BySecond = ints
}
case "BYMINUTE":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by minute value "+value, r, err)
} else {
r.ByMinute = ints
}
case "BYHOUR":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by hour value "+value, r, err)
} else {
r.ByHour = ints
}
case "BYDAY":
if days, err := csvToDays(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by day value "+value, r, err)
} else {
r.ByDay = days
}
case "BYMONTHDAY":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by month day value "+value, r, err)
} else {
r.ByMonthDay = ints
}
case "BYYEARDAY":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by year day value "+value, r, err)
} else {
r.ByYearDay = ints
}
case "BYWEEKNO":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "invalid by week number value "+value, r, err)
} else {
r.ByWeekNumber = ints
}
case "BYMONTH":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "unable to encode by month value "+value, r, err)
} else {
r.ByMonth = ints
}
case "BYSETPOS":
if ints, err := csvToInts(value); err != nil {
return utils.NewError(r.decodeICalValue, "unable to encode by set position value "+value, r, err)
} else {
r.BySetPosition = ints
}
case "WKST":
r.WeekStart = RecurrenceWeekday(value)
}
return nil
}
// validates the recurrence rule value against the iCalendar specification
func (r *RecurrenceRule) ValidateICalValue() error {
if !r.Frequency.IsValidFrequency() {
return utils.NewError(r.ValidateICalValue, "a frequency is required in all recurrence rules", r, nil)
} else if r.Until != nil && r.Count > 0 {
return utils.NewError(r.ValidateICalValue, "until and count values are mutually exclusive", r, nil)
} else if found, fine := intsInRange(r.BySecond, 59); !fine {
msg := fmt.Sprintf("by second value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByMinute, 59); !fine {
msg := fmt.Sprintf("by minute value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByHour, 23); !fine {
msg := fmt.Sprintf("by hour value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if err := daysInRange(r.ByDay); err != nil {
return utils.NewError(r.ValidateICalValue, "by day value not in range", r, err)
} else if found, fine := intsInRange(r.ByMonthDay, 31); !fine {
msg := fmt.Sprintf("by month day value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByYearDay, 366); !fine {
msg := fmt.Sprintf("by year day value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.ByMonth, 12); !fine {
msg := fmt.Sprintf("by month value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if found, fine := intsInRange(r.BySetPosition, 366); !fine {
msg := fmt.Sprintf("by month value of %d is out of bounds", found)
return utils.NewError(r.ValidateICalValue, msg, r, nil)
} else if err := dayInRange(r.WeekStart); r.WeekStart != "" && err != nil {
return utils.NewError(r.ValidateICalValue, "week start value not in range", r, err)
} else {
return nil
}
}
func intsToCSV(ints []int) (string, error) {
csv := new(CSV)
for _, i := range ints {
*csv = append(*csv, fmt.Sprintf("%d", i))
}
return csv.EncodeICalValue()
}
func csvToInts(value string) (ints []int, err error) {
csv := new(CSV)
if ierr := csv.DecodeICalValue(value); err != nil {
err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr)
return
}
for _, v := range *csv {
if i, ierr := strconv.ParseInt(v, 10, 64); err != nil {
err = utils.NewError(csvToInts, "unable to parse int value "+v, value, ierr)
return
} else {
ints = append(ints, int(i))
}
}
return
}
func intsInRange(ints []int, max int) (int, bool) {
for _, i := range ints {
if i < -max || i > max {
return i, false
}
}
return 0, true
}
func daysInRange(days []RecurrenceWeekday) error {
for _, day := range days {
if err := dayInRange(day); err != nil {
msg := fmt.Sprintf("day value %s is not in range", day)
return utils.NewError(dayInRange, msg, days, err)
}
}
return nil
}
var dayRegExp = regexp.MustCompile("(\\d{1,2})?(\\w{2})")
func dayInRange(day RecurrenceWeekday) error {
var ordinal, weekday string
if matches := dayRegExp.FindAllStringSubmatch(string(day), -1); len(matches) <= 0 {
msg := fmt.Sprintf("weekday value %s is not in valid format", day)
return utils.NewError(dayInRange, msg, day, nil)
} else if len(matches[0]) > 2 {
ordinal = matches[0][1]
weekday = matches[0][2]
} else {
weekday = matches[0][1]
}
if !RecurrenceWeekday(weekday).IsValidWeekDay() {
msg := fmt.Sprintf("weekday value %s is not valid", weekday)
return utils.NewError(dayInRange, msg, day, nil)
} else if i, err := strconv.ParseInt(ordinal, 10, 64); ordinal != "" && err != nil {
msg := fmt.Sprintf("weekday ordinal value %d is not valid", i)
return utils.NewError(dayInRange, msg, day, err)
} else if i < -53 || i > 53 {
msg := fmt.Sprintf("weekday ordinal value %d is not in range", i)
return utils.NewError(dayInRange, msg, day, nil)
} else {
return nil
}
}
func daysToCSV(days []RecurrenceWeekday) (string, error) {
csv := new(CSV)
for _, day := range days {
*csv = append(*csv, strings.ToUpper(string(day)))
}
return csv.EncodeICalValue()
}
func csvToDays(value string) (days []RecurrenceWeekday, err error) {
csv := new(CSV)
if ierr := csv.DecodeICalValue(value); err != nil {
err = utils.NewError(csvToInts, "unable to decode CSV value", value, ierr)
return
}
for _, v := range *csv {
days = append(days, RecurrenceWeekday(v))
}
return
}

View File

@ -0,0 +1,12 @@
package values
// Time Transparency is the characteristic of an event that determines whether it appears to consume time on a calendar.
// Events that consume actual time for the individual or resource associated with the calendar SHOULD be recorded as
// OPAQUE, allowing them to be detected by free-busy time searches. Other events, which do not take up the individual's
// (or resource's) time SHOULD be recorded as TRANSPARENT, making them invisible to free-busy time searches.
type TimeTransparency string
const (
OpaqueTimeTransparency TimeTransparency = "OPAQUE" // Blocks or opaque on busy time searches. DEFAULT
TransparentTimeTransparency = "TRANSPARENT" // Transparent on busy time searches.
)

View File

@ -0,0 +1,49 @@
package values
import (
"github.com/dolanor/caldav-go/icalendar/properties"
"github.com/dolanor/caldav-go/utils"
"net/url"
)
// a representation of duration for iCalendar
type Url struct {
u url.URL
}
// encodes the URL into iCalendar format
func (u *Url) EncodeICalValue() (string, error) {
return u.u.String(), nil
}
// encodes the url params for the iCalendar specification
func (u *Url) EncodeICalParams() (params properties.Params, err error) {
params = properties.Params{
properties.ValuePropertyName: "URI",
}
return
}
// decodes the URL from iCalendar format
func (u *Url) DecodeICalValue(value string) error {
if parsed, err := url.Parse(value); err != nil {
return utils.NewError(u.ValidateICalValue, "unable to parse url", u, err)
} else {
u.u = *parsed
return nil
}
}
// validates the URL for iCalendar format
func (u *Url) ValidateICalValue() error {
if _, err := url.Parse(u.u.String()); err != nil {
return utils.NewError(u.ValidateICalValue, "invalid URL object", u, err)
} else {
return nil
}
}
// creates a new iCalendar duration representation
func NewUrl(u url.URL) *Url {
return &Url{u: u}
}

37
vendor/github.com/dolanor/caldav-go/utils/error.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
package utils
import (
"fmt"
"reflect"
"runtime"
)
type Error struct {
method interface{}
message string
context interface{}
cause error
}
func NewError(method interface{}, message string, context interface{}, cause error) *Error {
e := new(Error)
e.method = method
e.message = message
e.context = context
e.cause = cause
return e
}
func (e *Error) Error() string {
pc := reflect.ValueOf(e.method).Pointer()
fn := runtime.FuncForPC(pc).Name()
msg := fmt.Sprintf("error: %s\nfunc: %s", e.message, fn)
if e.context != nil {
tname := reflect.ValueOf(e.context).Type()
msg = fmt.Sprintf("%s\ncontext: %s", msg, tname.String())
}
if e.cause != nil {
msg = fmt.Sprintf("%s\ncause: %s", msg, e.cause.Error())
}
return msg
}

119
vendor/github.com/dolanor/caldav-go/webdav/client.go generated vendored Normal file
View File

@ -0,0 +1,119 @@
package webdav
import (
"fmt"
"github.com/dolanor/caldav-go/http"
"github.com/dolanor/caldav-go/utils"
"github.com/dolanor/caldav-go/webdav/entities"
nhttp "net/http"
)
const (
StatusMulti = 207
)
// a client for making WebDAV requests
type Client http.Client
// downcasts the client to the local HTTP interface
func (c *Client) Http() *http.Client {
return (*http.Client)(c)
}
// returns the embedded WebDav server reference
func (c *Client) Server() *Server {
return (*Server)(c.Http().Server())
}
// executes a WebDAV request
func (c *Client) Do(req *Request) (*Response, error) {
if resp, err := c.Http().Do((*http.Request)(req)); err != nil {
return nil, utils.NewError(c.Do, "unable to execute WebDAV request", c, err)
} else {
return NewResponse(resp), nil
}
}
// checks if a resource exists given a particular path
func (c *Client) Exists(path string) (bool, error) {
if req, err := c.Server().NewRequest("HEAD", path); err != nil {
return false, utils.NewError(c.Exists, "unable to create request", c, err)
} else if resp, err := c.Do(req); err != nil {
return false, utils.NewError(c.Exists, "unable to execute request", c, err)
} else {
return resp.StatusCode != nhttp.StatusNotFound, nil
}
}
// deletes a resource if it exists on a particular path
func (c *Client) Delete(path string) error {
if req, err := c.Server().NewRequest("DELETE", path); err != nil {
return utils.NewError(c.Delete, "unable to create request", c, err)
} else if resp, err := c.Do(req); err != nil {
return utils.NewError(c.Delete, "unable to execute request", c, err)
} else if resp.StatusCode != nhttp.StatusNoContent && resp.StatusCode != nhttp.StatusNotFound {
err := new(entities.Error)
resp.Decode(err)
msg := fmt.Sprintf("unexpected server response %s", resp.Status)
return utils.NewError(c.Delete, msg, c, err)
} else {
return nil
}
}
// fetches a list of WebDAV features supported by the server
// returns an error if the server does not support DAV
func (c *Client) Features(path string) ([]string, error) {
if req, err := c.Server().NewRequest("OPTIONS", path); err != nil {
return []string{}, utils.NewError(c.Features, "unable to create request", c, err)
} else if resp, err := c.Do(req); err != nil {
return []string{}, utils.NewError(c.Features, "unable to execute request", c, err)
} else {
return resp.Features(), nil
}
}
// returns an error if the server does not support WebDAV
func (c *Client) ValidateServer(path string) error {
if features, err := c.Features(path); err != nil {
return utils.NewError(c.ValidateServer, "feature detection failed", c, err)
} else if len(features) <= 0 {
return utils.NewError(c.ValidateServer, "no DAV headers found", c, err)
} else {
return nil
}
}
// executes a PROPFIND request against the WebDAV server
// returns a multistatus XML entity
func (c *Client) Propfind(path string, depth Depth, pf *entities.Propfind) (*entities.Multistatus, error) {
ms := new(entities.Multistatus)
if req, err := c.Server().NewRequest("PROPFIND", path, pf); err != nil {
return nil, utils.NewError(c.Propfind, "unable to create request", c, err)
} else if req.Http().Native().Header.Set("Depth", string(depth)); depth == "" {
return nil, utils.NewError(c.Propfind, "search depth must be defined", c, nil)
} else if resp, err := c.Do(req); err != nil {
return nil, utils.NewError(c.Propfind, "unable to execute request", c, err)
} else if resp.StatusCode != StatusMulti {
msg := fmt.Sprintf("unexpected status: %s", resp.Status)
return nil, utils.NewError(c.Propfind, msg, c, nil)
} else if err := resp.Decode(ms); err != nil {
return nil, utils.NewError(c.Propfind, "unable to decode response", c, err)
}
return ms, nil
}
// creates a new client for communicating with an WebDAV server
func NewClient(server *Server, native *nhttp.Client) *Client {
return (*Client)(http.NewClient((*http.Server)(server), native))
}
// creates a new client for communicating with a WebDAV server
// uses the default HTTP client from net/http
func NewDefaultClient(server *Server) *Client {
return NewClient(server, nhttp.DefaultClient)
}

9
vendor/github.com/dolanor/caldav-go/webdav/depth.go generated vendored Normal file
View File

@ -0,0 +1,9 @@
package webdav
type Depth string
const (
Depth0 Depth = "0"
Depth1 = "1"
DepthInfinity = "infinity"
)

View File

@ -0,0 +1,18 @@
package entities
import "encoding/xml"
// a WebDAV error
type Error struct {
XMLName xml.Name `xml:"DAV: error"`
Description string `xml:"error-description,omitempty"`
Message string `xml:"message,omitempty"`
}
func (e *Error) Error() string {
if e.Description != "" {
return e.Description
} else {
return e.Message
}
}

View File

@ -0,0 +1,23 @@
package entities
import "encoding/xml"
// metadata about a property
type PropStat struct {
XMLName xml.Name `xml:"propstat"`
Status string `xml:"status"`
Prop *Prop `xml:",omitempty"`
}
// a multistatus response entity
type Response struct {
XMLName xml.Name `xml:"response"`
Href string `xml:"href"`
PropStats []*PropStat `xml:"propstat,omitempty"`
}
// a request to find properties on an an entity or collection
type Multistatus struct {
XMLName xml.Name `xml:"DAV: multistatus"`
Responses []*Response `xml:"response,omitempty"`
}

View File

@ -0,0 +1,32 @@
package entities
import (
"encoding/xml"
)
// a property of a resource
type Prop struct {
XMLName xml.Name `xml:"DAV: prop"`
GetContentType string `xml:"getcontenttype,omitempty"`
DisplayName string `xml:"displayname,omitempty"`
ResourceType *ResourceType `xml:",omitempty"`
CTag string `xml:"http://calendarserver.org/ns/ getctag,omitempty"`
ETag string `xml:"http://calendarserver.org/ns/ getetag,omitempty"`
}
// the type of a resource
type ResourceType struct {
XMLName xml.Name `xml:"resourcetype"`
Collection *ResourceTypeCollection `xml:",omitempty"`
Calendar *ResourceTypeCalendar `xml:",omitempty"`
}
// A calendar resource type
type ResourceTypeCalendar struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:caldav calendar"`
}
// A collection resource type
type ResourceTypeCollection struct {
XMLName xml.Name `xml:"collection"`
}

View File

@ -0,0 +1,20 @@
package entities
import "encoding/xml"
// a request to find properties on an an entity or collection
type Propfind struct {
XMLName xml.Name `xml:"DAV: propfind"`
AllProp *AllProp `xml:",omitempty"`
Props []*Prop `xml:"prop,omitempty"`
}
// a propfind property representing all properties
type AllProp struct {
XMLName xml.Name `xml:"allprop"`
}
// a convenience method for searching all properties
func NewAllPropsFind() *Propfind {
return &Propfind{AllProp: new(AllProp)}
}

56
vendor/github.com/dolanor/caldav-go/webdav/request.go generated vendored Normal file
View File

@ -0,0 +1,56 @@
package webdav
import (
"bytes"
"encoding/xml"
"github.com/dolanor/caldav-go/http"
"github.com/dolanor/caldav-go/utils"
"io"
"io/ioutil"
"log"
"strings"
)
var _ = log.Print
// an WebDAV request object
type Request http.Request
// downcasts the request to the local HTTP interface
func (r *Request) Http() *http.Request {
return (*http.Request)(r)
}
// creates a new WebDAV request object
func NewRequest(method string, urlstr string, xmldata ...interface{}) (*Request, error) {
if buffer, length, err := xmlToReadCloser(xmldata); err != nil {
return nil, utils.NewError(NewRequest, "unable to encode xml data", xmldata, err)
} else if r, err := http.NewRequest(method, urlstr, buffer); err != nil {
return nil, utils.NewError(NewRequest, "unable to create request", urlstr, err)
} else {
if buffer != nil {
// set the content type to XML if we have a body
r.Native().Header.Set("Content-Type", "text/xml; charset=UTF-8")
r.ContentLength = int64(length)
}
return (*Request)(r), nil
}
}
func xmlToReadCloser(xmldata ...interface{}) (io.ReadCloser, int, error) {
var buffer []string
for _, xmldatum := range xmldata {
if encoded, err := xml.Marshal(xmldatum); err != nil {
return nil, 0, utils.NewError(xmlToReadCloser, "unable to encode as xml", xmldatum, err)
} else {
buffer = append(buffer, string(encoded))
}
}
if len(buffer) > 0 {
var encoded = strings.Join(buffer, "\n")
// log.Printf("[WebDAV Request]\n%+v\n", encoded)
return ioutil.NopCloser(bytes.NewBuffer([]byte(encoded))), len(encoded), nil
} else {
return nil, 0, nil
}
}

54
vendor/github.com/dolanor/caldav-go/webdav/response.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
package webdav
import (
"encoding/xml"
"github.com/dolanor/caldav-go/http"
"github.com/dolanor/caldav-go/utils"
"io/ioutil"
"log"
"strings"
)
var _ = log.Print
var _ = ioutil.ReadAll
// a WebDAV response object
type Response http.Response
// downcasts the response to the local HTTP interface
func (r *Response) Http() *http.Response {
return (*http.Response)(r)
}
// returns a list of WebDAV features found in the response
func (r *Response) Features() (features []string) {
if dav := r.Header.Get("DAV"); dav != "" {
features = strings.Split(dav, ", ")
}
return
}
// decodes a WebDAV XML response into the provided interface
func (r *Response) Decode(into interface{}) error {
// data, _ := ioutil.ReadAll(r.Body)
// log.Printf("[WebDAV Response]\n%+v\n", string(data))
// if err := xml.Unmarshal(data, into); err != nil {
// return utils.NewError(r.Decode, "unable to decode response body", r, err)
// } else {
// return nil
// }
if body := r.Body; body == nil {
return nil
} else if decoder := xml.NewDecoder(body); decoder == nil {
return nil
} else if err := decoder.Decode(into); err != nil {
return utils.NewError(r.Decode, "unable to decode response body", r, err)
} else {
return nil
}
}
// creates a new WebDAV response object
func NewResponse(response *http.Response) *Response {
return (*Response)(response)
}

28
vendor/github.com/dolanor/caldav-go/webdav/server.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package webdav
import (
"github.com/dolanor/caldav-go/http"
"github.com/dolanor/caldav-go/utils"
)
// a server that accepts WebDAV requests
type Server http.Server
// creates a reference to an WebDAV server
func NewServer(baseUrlStr string) (*Server, error) {
if s, err := http.NewServer(baseUrlStr); err != nil {
return nil, utils.NewError(NewServer, "unable to create WebDAV server", baseUrlStr, err)
} else {
return (*Server)(s), nil
}
}
// downcasts the server to the local HTTP interface
func (s *Server) Http() *http.Server {
return (*http.Server)(s)
}
// creates a new WebDAV request object
func (s *Server) NewRequest(method string, path string, xmldata ...interface{}) (*Request, error) {
return NewRequest(method, s.Http().AbsUrlStr(path), xmldata...)
}

19
vendor/go.uber.org/atomic/.codecov.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
coverage:
range: 80..100
round: down
precision: 2
status:
project: # measuring the overall project coverage
default: # context, you can create multiple ones with custom titles
enabled: yes # must be yes|true to enable this status
target: 100 # specify the target coverage for each commit status
# option: "auto" (must increase from parent commit or pull request base)
# option: "X%" a static target percentage to hit
if_not_found: success # if parent is not found report status as success, error, or failure
if_ci_failed: error # if ci fails report status as success, error, or failure
# Also update COVER_IGNORE_PKGS in the Makefile.
ignore:
- /internal/gen-atomicint/
- /internal/gen-valuewrapper/

12
vendor/go.uber.org/atomic/.gitignore generated vendored Normal file
View File

@ -0,0 +1,12 @@
/bin
.DS_Store
/vendor
cover.html
cover.out
lint.log
# Binaries
*.test
# Profiling output
*.prof

27
vendor/go.uber.org/atomic/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,27 @@
sudo: false
language: go
go_import_path: go.uber.org/atomic
env:
global:
- GO111MODULE=on
matrix:
include:
- go: oldstable
- go: stable
env: LINT=1
cache:
directories:
- vendor
before_install:
- go version
script:
- test -z "$LINT" || make lint
- make cover
after_success:
- bash <(curl -s https://codecov.io/bash)

76
vendor/go.uber.org/atomic/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,76 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.7.0] - 2020-09-14
### Added
- Support JSON serialization and deserialization of primitive atomic types.
- Support Text marshalling and unmarshalling for string atomics.
### Changed
- Disallow incorrect comparison of atomic values in a non-atomic way.
### Removed
- Remove dependency on `golang.org/x/{lint, tools}`.
## [1.6.0] - 2020-02-24
### Changed
- Drop library dependency on `golang.org/x/{lint, tools}`.
## [1.5.1] - 2019-11-19
- Fix bug where `Bool.CAS` and `Bool.Toggle` do work correctly together
causing `CAS` to fail even though the old value matches.
## [1.5.0] - 2019-10-29
### Changed
- With Go modules, only the `go.uber.org/atomic` import path is supported now.
If you need to use the old import path, please add a `replace` directive to
your `go.mod`.
## [1.4.0] - 2019-05-01
### Added
- Add `atomic.Error` type for atomic operations on `error` values.
## [1.3.2] - 2018-05-02
### Added
- Add `atomic.Duration` type for atomic operations on `time.Duration` values.
## [1.3.1] - 2017-11-14
### Fixed
- Revert optimization for `atomic.String.Store("")` which caused data races.
## [1.3.0] - 2017-11-13
### Added
- Add `atomic.Bool.CAS` for compare-and-swap semantics on bools.
### Changed
- Optimize `atomic.String.Store("")` by avoiding an allocation.
## [1.2.0] - 2017-04-12
### Added
- Shadow `atomic.Value` from `sync/atomic`.
## [1.1.0] - 2017-03-10
### Added
- Add atomic `Float64` type.
### Changed
- Support new `go.uber.org/atomic` import path.
## [1.0.0] - 2016-07-18
- Initial release.
[1.7.0]: https://github.com/uber-go/atomic/compare/v1.6.0...v1.7.0
[1.6.0]: https://github.com/uber-go/atomic/compare/v1.5.1...v1.6.0
[1.5.1]: https://github.com/uber-go/atomic/compare/v1.5.0...v1.5.1
[1.5.0]: https://github.com/uber-go/atomic/compare/v1.4.0...v1.5.0
[1.4.0]: https://github.com/uber-go/atomic/compare/v1.3.2...v1.4.0
[1.3.2]: https://github.com/uber-go/atomic/compare/v1.3.1...v1.3.2
[1.3.1]: https://github.com/uber-go/atomic/compare/v1.3.0...v1.3.1
[1.3.0]: https://github.com/uber-go/atomic/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/uber-go/atomic/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/uber-go/atomic/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/uber-go/atomic/releases/tag/v1.0.0

19
vendor/go.uber.org/atomic/LICENSE.txt generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2016 Uber Technologies, Inc.
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.

78
vendor/go.uber.org/atomic/Makefile generated vendored Normal file
View File

@ -0,0 +1,78 @@
# Directory to place `go install`ed binaries into.
export GOBIN ?= $(shell pwd)/bin
GOLINT = $(GOBIN)/golint
GEN_ATOMICINT = $(GOBIN)/gen-atomicint
GEN_ATOMICWRAPPER = $(GOBIN)/gen-atomicwrapper
STATICCHECK = $(GOBIN)/staticcheck
GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
# Also update ignore section in .codecov.yml.
COVER_IGNORE_PKGS = \
go.uber.org/atomic/internal/gen-atomicint \
go.uber.org/atomic/internal/gen-atomicwrapper
.PHONY: build
build:
go build ./...
.PHONY: test
test:
go test -race ./...
.PHONY: gofmt
gofmt:
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" && cat $(FMT_LOG) && false)
$(GOLINT):
cd tools && go install golang.org/x/lint/golint
$(STATICCHECK):
cd tools && go install honnef.co/go/tools/cmd/staticcheck
$(GEN_ATOMICWRAPPER): $(wildcard ./internal/gen-atomicwrapper/*)
go build -o $@ ./internal/gen-atomicwrapper
$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*)
go build -o $@ ./internal/gen-atomicint
.PHONY: golint
golint: $(GOLINT)
$(GOLINT) ./...
.PHONY: staticcheck
staticcheck: $(STATICCHECK)
$(STATICCHECK) ./...
.PHONY: lint
lint: gofmt golint staticcheck generatenodirty
# comma separated list of packages to consider for code coverage.
COVER_PKG = $(shell \
go list -find ./... | \
grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \
paste -sd, -)
.PHONY: cover
cover:
go test -coverprofile=cover.out -coverpkg $(COVER_PKG) -v ./...
go tool cover -html=cover.out -o cover.html
.PHONY: generate
generate: $(GEN_ATOMICINT) $(GEN_ATOMICWRAPPER)
go generate ./...
.PHONY: generatenodirty
generatenodirty:
@[ -z "$$(git status --porcelain)" ] || ( \
echo "Working tree is dirty. Commit your changes first."; \
exit 1 )
@make generate
@status=$$(git status --porcelain); \
[ -z "$$status" ] || ( \
echo "Working tree is dirty after `make generate`:"; \
echo "$$status"; \
echo "Please ensure that the generated code is up-to-date." )

63
vendor/go.uber.org/atomic/README.md generated vendored Normal file
View File

@ -0,0 +1,63 @@
# atomic [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report Card][reportcard-img]][reportcard]
Simple wrappers for primitive types to enforce atomic access.
## Installation
```shell
$ go get -u go.uber.org/atomic@v1
```
### Legacy Import Path
As of v1.5.0, the import path `go.uber.org/atomic` is the only supported way
of using this package. If you are using Go modules, this package will fail to
compile with the legacy import path path `github.com/uber-go/atomic`.
We recommend migrating your code to the new import path but if you're unable
to do so, or if your dependencies are still using the old import path, you
will have to add a `replace` directive to your `go.mod` file downgrading the
legacy import path to an older version.
```
replace github.com/uber-go/atomic => github.com/uber-go/atomic v1.4.0
```
You can do so automatically by running the following command.
```shell
$ go mod edit -replace github.com/uber-go/atomic=github.com/uber-go/atomic@v1.4.0
```
## Usage
The standard library's `sync/atomic` is powerful, but it's easy to forget which
variables must be accessed atomically. `go.uber.org/atomic` preserves all the
functionality of the standard library, but wraps the primitive types to
provide a safer, more convenient API.
```go
var atom atomic.Uint32
atom.Store(42)
atom.Sub(2)
atom.CAS(40, 11)
```
See the [documentation][doc] for a complete API specification.
## Development Status
Stable.
---
Released under the [MIT License](LICENSE.txt).
[doc-img]: https://godoc.org/github.com/uber-go/atomic?status.svg
[doc]: https://godoc.org/go.uber.org/atomic
[ci-img]: https://travis-ci.com/uber-go/atomic.svg?branch=master
[ci]: https://travis-ci.com/uber-go/atomic
[cov-img]: https://codecov.io/gh/uber-go/atomic/branch/master/graph/badge.svg
[cov]: https://codecov.io/gh/uber-go/atomic
[reportcard-img]: https://goreportcard.com/badge/go.uber.org/atomic
[reportcard]: https://goreportcard.com/report/go.uber.org/atomic

81
vendor/go.uber.org/atomic/bool.go generated vendored Normal file
View File

@ -0,0 +1,81 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
)
// Bool is an atomic type-safe wrapper for bool values.
type Bool struct {
_ nocmp // disallow non-atomic comparison
v Uint32
}
var _zeroBool bool
// NewBool creates a new Bool.
func NewBool(v bool) *Bool {
x := &Bool{}
if v != _zeroBool {
x.Store(v)
}
return x
}
// Load atomically loads the wrapped bool.
func (x *Bool) Load() bool {
return truthy(x.v.Load())
}
// Store atomically stores the passed bool.
func (x *Bool) Store(v bool) {
x.v.Store(boolToInt(v))
}
// CAS is an atomic compare-and-swap for bool values.
func (x *Bool) CAS(o, n bool) bool {
return x.v.CAS(boolToInt(o), boolToInt(n))
}
// Swap atomically stores the given bool and returns the old
// value.
func (x *Bool) Swap(o bool) bool {
return truthy(x.v.Swap(boolToInt(o)))
}
// MarshalJSON encodes the wrapped bool into JSON.
func (x *Bool) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Load())
}
// UnmarshalJSON decodes a bool from JSON.
func (x *Bool) UnmarshalJSON(b []byte) error {
var v bool
if err := json.Unmarshal(b, &v); err != nil {
return err
}
x.Store(v)
return nil
}

53
vendor/go.uber.org/atomic/bool_ext.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"strconv"
)
//go:generate bin/gen-atomicwrapper -name=Bool -type=bool -wrapped=Uint32 -pack=boolToInt -unpack=truthy -cas -swap -json -file=bool.go
func truthy(n uint32) bool {
return n == 1
}
func boolToInt(b bool) uint32 {
if b {
return 1
}
return 0
}
// Toggle atomically negates the Boolean and returns the previous value.
func (b *Bool) Toggle() bool {
for {
old := b.Load()
if b.CAS(old, !old) {
return old
}
}
}
// String encodes the wrapped value as a string.
func (b *Bool) String() string {
return strconv.FormatBool(b.Load())
}

23
vendor/go.uber.org/atomic/doc.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
// Package atomic provides simple wrappers around numerics to enforce atomic
// access.
package atomic

82
vendor/go.uber.org/atomic/duration.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"time"
)
// Duration is an atomic type-safe wrapper for time.Duration values.
type Duration struct {
_ nocmp // disallow non-atomic comparison
v Int64
}
var _zeroDuration time.Duration
// NewDuration creates a new Duration.
func NewDuration(v time.Duration) *Duration {
x := &Duration{}
if v != _zeroDuration {
x.Store(v)
}
return x
}
// Load atomically loads the wrapped time.Duration.
func (x *Duration) Load() time.Duration {
return time.Duration(x.v.Load())
}
// Store atomically stores the passed time.Duration.
func (x *Duration) Store(v time.Duration) {
x.v.Store(int64(v))
}
// CAS is an atomic compare-and-swap for time.Duration values.
func (x *Duration) CAS(o, n time.Duration) bool {
return x.v.CAS(int64(o), int64(n))
}
// Swap atomically stores the given time.Duration and returns the old
// value.
func (x *Duration) Swap(o time.Duration) time.Duration {
return time.Duration(x.v.Swap(int64(o)))
}
// MarshalJSON encodes the wrapped time.Duration into JSON.
func (x *Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Load())
}
// UnmarshalJSON decodes a time.Duration from JSON.
func (x *Duration) UnmarshalJSON(b []byte) error {
var v time.Duration
if err := json.Unmarshal(b, &v); err != nil {
return err
}
x.Store(v)
return nil
}

40
vendor/go.uber.org/atomic/duration_ext.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import "time"
//go:generate bin/gen-atomicwrapper -name=Duration -type=time.Duration -wrapped=Int64 -pack=int64 -unpack=time.Duration -cas -swap -json -imports time -file=duration.go
// Add atomically adds to the wrapped time.Duration and returns the new value.
func (d *Duration) Add(n time.Duration) time.Duration {
return time.Duration(d.v.Add(int64(n)))
}
// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
func (d *Duration) Sub(n time.Duration) time.Duration {
return time.Duration(d.v.Sub(int64(n)))
}
// String encodes the wrapped value as a string.
func (d *Duration) String() string {
return d.Load().String()
}

51
vendor/go.uber.org/atomic/error.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
// Error is an atomic type-safe wrapper for error values.
type Error struct {
_ nocmp // disallow non-atomic comparison
v Value
}
var _zeroError error
// NewError creates a new Error.
func NewError(v error) *Error {
x := &Error{}
if v != _zeroError {
x.Store(v)
}
return x
}
// Load atomically loads the wrapped error.
func (x *Error) Load() error {
return unpackError(x.v.Load())
}
// Store atomically stores the passed error.
func (x *Error) Store(v error) {
x.v.Store(packError(v))
}

39
vendor/go.uber.org/atomic/error_ext.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
// atomic.Value panics on nil inputs, or if the underlying type changes.
// Stabilize by always storing a custom struct that we control.
//go:generate bin/gen-atomicwrapper -name=Error -type=error -wrapped=Value -pack=packError -unpack=unpackError -file=error.go
type packedError struct{ Value error }
func packError(v error) interface{} {
return packedError{v}
}
func unpackError(v interface{}) error {
if err, ok := v.(packedError); ok {
return err.Value
}
return nil
}

76
vendor/go.uber.org/atomic/float64.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"math"
)
// Float64 is an atomic type-safe wrapper for float64 values.
type Float64 struct {
_ nocmp // disallow non-atomic comparison
v Uint64
}
var _zeroFloat64 float64
// NewFloat64 creates a new Float64.
func NewFloat64(v float64) *Float64 {
x := &Float64{}
if v != _zeroFloat64 {
x.Store(v)
}
return x
}
// Load atomically loads the wrapped float64.
func (x *Float64) Load() float64 {
return math.Float64frombits(x.v.Load())
}
// Store atomically stores the passed float64.
func (x *Float64) Store(v float64) {
x.v.Store(math.Float64bits(v))
}
// CAS is an atomic compare-and-swap for float64 values.
func (x *Float64) CAS(o, n float64) bool {
return x.v.CAS(math.Float64bits(o), math.Float64bits(n))
}
// MarshalJSON encodes the wrapped float64 into JSON.
func (x *Float64) MarshalJSON() ([]byte, error) {
return json.Marshal(x.Load())
}
// UnmarshalJSON decodes a float64 from JSON.
func (x *Float64) UnmarshalJSON(b []byte) error {
var v float64
if err := json.Unmarshal(b, &v); err != nil {
return err
}
x.Store(v)
return nil
}

47
vendor/go.uber.org/atomic/float64_ext.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import "strconv"
//go:generate bin/gen-atomicwrapper -name=Float64 -type=float64 -wrapped=Uint64 -pack=math.Float64bits -unpack=math.Float64frombits -cas -json -imports math -file=float64.go
// Add atomically adds to the wrapped float64 and returns the new value.
func (f *Float64) Add(s float64) float64 {
for {
old := f.Load()
new := old + s
if f.CAS(old, new) {
return new
}
}
}
// Sub atomically subtracts from the wrapped float64 and returns the new value.
func (f *Float64) Sub(s float64) float64 {
return f.Add(-s)
}
// String encodes the wrapped value as a string.
func (f *Float64) String() string {
// 'g' is the behavior for floats with %v.
return strconv.FormatFloat(f.Load(), 'g', -1, 64)
}

26
vendor/go.uber.org/atomic/gen.go generated vendored Normal file
View File

@ -0,0 +1,26 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go
//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go

102
vendor/go.uber.org/atomic/int32.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"strconv"
"sync/atomic"
)
// Int32 is an atomic wrapper around int32.
type Int32 struct {
_ nocmp // disallow non-atomic comparison
v int32
}
// NewInt32 creates a new Int32.
func NewInt32(i int32) *Int32 {
return &Int32{v: i}
}
// Load atomically loads the wrapped value.
func (i *Int32) Load() int32 {
return atomic.LoadInt32(&i.v)
}
// Add atomically adds to the wrapped int32 and returns the new value.
func (i *Int32) Add(n int32) int32 {
return atomic.AddInt32(&i.v, n)
}
// Sub atomically subtracts from the wrapped int32 and returns the new value.
func (i *Int32) Sub(n int32) int32 {
return atomic.AddInt32(&i.v, -n)
}
// Inc atomically increments the wrapped int32 and returns the new value.
func (i *Int32) Inc() int32 {
return i.Add(1)
}
// Dec atomically decrements the wrapped int32 and returns the new value.
func (i *Int32) Dec() int32 {
return i.Sub(1)
}
// CAS is an atomic compare-and-swap.
func (i *Int32) CAS(old, new int32) bool {
return atomic.CompareAndSwapInt32(&i.v, old, new)
}
// Store atomically stores the passed value.
func (i *Int32) Store(n int32) {
atomic.StoreInt32(&i.v, n)
}
// Swap atomically swaps the wrapped int32 and returns the old value.
func (i *Int32) Swap(n int32) int32 {
return atomic.SwapInt32(&i.v, n)
}
// MarshalJSON encodes the wrapped int32 into JSON.
func (i *Int32) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
// UnmarshalJSON decodes JSON into the wrapped int32.
func (i *Int32) UnmarshalJSON(b []byte) error {
var v int32
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
// String encodes the wrapped value as a string.
func (i *Int32) String() string {
v := i.Load()
return strconv.FormatInt(int64(v), 10)
}

102
vendor/go.uber.org/atomic/int64.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"strconv"
"sync/atomic"
)
// Int64 is an atomic wrapper around int64.
type Int64 struct {
_ nocmp // disallow non-atomic comparison
v int64
}
// NewInt64 creates a new Int64.
func NewInt64(i int64) *Int64 {
return &Int64{v: i}
}
// Load atomically loads the wrapped value.
func (i *Int64) Load() int64 {
return atomic.LoadInt64(&i.v)
}
// Add atomically adds to the wrapped int64 and returns the new value.
func (i *Int64) Add(n int64) int64 {
return atomic.AddInt64(&i.v, n)
}
// Sub atomically subtracts from the wrapped int64 and returns the new value.
func (i *Int64) Sub(n int64) int64 {
return atomic.AddInt64(&i.v, -n)
}
// Inc atomically increments the wrapped int64 and returns the new value.
func (i *Int64) Inc() int64 {
return i.Add(1)
}
// Dec atomically decrements the wrapped int64 and returns the new value.
func (i *Int64) Dec() int64 {
return i.Sub(1)
}
// CAS is an atomic compare-and-swap.
func (i *Int64) CAS(old, new int64) bool {
return atomic.CompareAndSwapInt64(&i.v, old, new)
}
// Store atomically stores the passed value.
func (i *Int64) Store(n int64) {
atomic.StoreInt64(&i.v, n)
}
// Swap atomically swaps the wrapped int64 and returns the old value.
func (i *Int64) Swap(n int64) int64 {
return atomic.SwapInt64(&i.v, n)
}
// MarshalJSON encodes the wrapped int64 into JSON.
func (i *Int64) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
// UnmarshalJSON decodes JSON into the wrapped int64.
func (i *Int64) UnmarshalJSON(b []byte) error {
var v int64
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
// String encodes the wrapped value as a string.
func (i *Int64) String() string {
v := i.Load()
return strconv.FormatInt(int64(v), 10)
}

35
vendor/go.uber.org/atomic/nocmp.go generated vendored Normal file
View File

@ -0,0 +1,35 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
// nocmp is an uncomparable struct. Embed this inside another struct to make
// it uncomparable.
//
// type Foo struct {
// nocmp
// // ...
// }
//
// This DOES NOT:
//
// - Disallow shallow copies of structs
// - Disallow comparison of pointers to uncomparable structs
type nocmp [0]func()

54
vendor/go.uber.org/atomic/string.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
// @generated Code generated by gen-atomicwrapper.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
// String is an atomic type-safe wrapper for string values.
type String struct {
_ nocmp // disallow non-atomic comparison
v Value
}
var _zeroString string
// NewString creates a new String.
func NewString(v string) *String {
x := &String{}
if v != _zeroString {
x.Store(v)
}
return x
}
// Load atomically loads the wrapped string.
func (x *String) Load() string {
if v := x.v.Load(); v != nil {
return v.(string)
}
return _zeroString
}
// Store atomically stores the passed string.
func (x *String) Store(v string) {
x.v.Store(v)
}

43
vendor/go.uber.org/atomic/string_ext.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
//go:generate bin/gen-atomicwrapper -name=String -type=string -wrapped=Value -file=string.go
// String returns the wrapped value.
func (s *String) String() string {
return s.Load()
}
// MarshalText encodes the wrapped string into a textual form.
//
// This makes it encodable as JSON, YAML, XML, and more.
func (s *String) MarshalText() ([]byte, error) {
return []byte(s.Load()), nil
}
// UnmarshalText decodes text and replaces the wrapped string with it.
//
// This makes it decodable from JSON, YAML, XML, and more.
func (s *String) UnmarshalText(b []byte) error {
s.Store(string(b))
return nil
}

102
vendor/go.uber.org/atomic/uint32.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"strconv"
"sync/atomic"
)
// Uint32 is an atomic wrapper around uint32.
type Uint32 struct {
_ nocmp // disallow non-atomic comparison
v uint32
}
// NewUint32 creates a new Uint32.
func NewUint32(i uint32) *Uint32 {
return &Uint32{v: i}
}
// Load atomically loads the wrapped value.
func (i *Uint32) Load() uint32 {
return atomic.LoadUint32(&i.v)
}
// Add atomically adds to the wrapped uint32 and returns the new value.
func (i *Uint32) Add(n uint32) uint32 {
return atomic.AddUint32(&i.v, n)
}
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
func (i *Uint32) Sub(n uint32) uint32 {
return atomic.AddUint32(&i.v, ^(n - 1))
}
// Inc atomically increments the wrapped uint32 and returns the new value.
func (i *Uint32) Inc() uint32 {
return i.Add(1)
}
// Dec atomically decrements the wrapped uint32 and returns the new value.
func (i *Uint32) Dec() uint32 {
return i.Sub(1)
}
// CAS is an atomic compare-and-swap.
func (i *Uint32) CAS(old, new uint32) bool {
return atomic.CompareAndSwapUint32(&i.v, old, new)
}
// Store atomically stores the passed value.
func (i *Uint32) Store(n uint32) {
atomic.StoreUint32(&i.v, n)
}
// Swap atomically swaps the wrapped uint32 and returns the old value.
func (i *Uint32) Swap(n uint32) uint32 {
return atomic.SwapUint32(&i.v, n)
}
// MarshalJSON encodes the wrapped uint32 into JSON.
func (i *Uint32) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
// UnmarshalJSON decodes JSON into the wrapped uint32.
func (i *Uint32) UnmarshalJSON(b []byte) error {
var v uint32
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
// String encodes the wrapped value as a string.
func (i *Uint32) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
}

102
vendor/go.uber.org/atomic/uint64.go generated vendored Normal file
View File

@ -0,0 +1,102 @@
// @generated Code generated by gen-atomicint.
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import (
"encoding/json"
"strconv"
"sync/atomic"
)
// Uint64 is an atomic wrapper around uint64.
type Uint64 struct {
_ nocmp // disallow non-atomic comparison
v uint64
}
// NewUint64 creates a new Uint64.
func NewUint64(i uint64) *Uint64 {
return &Uint64{v: i}
}
// Load atomically loads the wrapped value.
func (i *Uint64) Load() uint64 {
return atomic.LoadUint64(&i.v)
}
// Add atomically adds to the wrapped uint64 and returns the new value.
func (i *Uint64) Add(n uint64) uint64 {
return atomic.AddUint64(&i.v, n)
}
// Sub atomically subtracts from the wrapped uint64 and returns the new value.
func (i *Uint64) Sub(n uint64) uint64 {
return atomic.AddUint64(&i.v, ^(n - 1))
}
// Inc atomically increments the wrapped uint64 and returns the new value.
func (i *Uint64) Inc() uint64 {
return i.Add(1)
}
// Dec atomically decrements the wrapped uint64 and returns the new value.
func (i *Uint64) Dec() uint64 {
return i.Sub(1)
}
// CAS is an atomic compare-and-swap.
func (i *Uint64) CAS(old, new uint64) bool {
return atomic.CompareAndSwapUint64(&i.v, old, new)
}
// Store atomically stores the passed value.
func (i *Uint64) Store(n uint64) {
atomic.StoreUint64(&i.v, n)
}
// Swap atomically swaps the wrapped uint64 and returns the old value.
func (i *Uint64) Swap(n uint64) uint64 {
return atomic.SwapUint64(&i.v, n)
}
// MarshalJSON encodes the wrapped uint64 into JSON.
func (i *Uint64) MarshalJSON() ([]byte, error) {
return json.Marshal(i.Load())
}
// UnmarshalJSON decodes JSON into the wrapped uint64.
func (i *Uint64) UnmarshalJSON(b []byte) error {
var v uint64
if err := json.Unmarshal(b, &v); err != nil {
return err
}
i.Store(v)
return nil
}
// String encodes the wrapped value as a string.
func (i *Uint64) String() string {
v := i.Load()
return strconv.FormatUint(uint64(v), 10)
}

31
vendor/go.uber.org/atomic/value.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// 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.
package atomic
import "sync/atomic"
// Value shadows the type of the same name from sync/atomic
// https://godoc.org/sync/atomic#Value
type Value struct {
atomic.Value
_ nocmp // disallow non-atomic comparison
}

15
vendor/go.uber.org/multierr/.codecov.yml generated vendored Normal file
View File

@ -0,0 +1,15 @@
coverage:
range: 80..100
round: down
precision: 2
status:
project: # measuring the overall project coverage
default: # context, you can create multiple ones with custom titles
enabled: yes # must be yes|true to enable this status
target: 100 # specify the target coverage for each commit status
# option: "auto" (must increase from parent commit or pull request base)
# option: "X%" a static target percentage to hit
if_not_found: success # if parent is not found report status as success, error, or failure
if_ci_failed: error # if ci fails report status as success, error, or failure

4
vendor/go.uber.org/multierr/.gitignore generated vendored Normal file
View File

@ -0,0 +1,4 @@
/vendor
cover.html
cover.out
/bin

23
vendor/go.uber.org/multierr/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,23 @@
sudo: false
language: go
go_import_path: go.uber.org/multierr
env:
global:
- GO111MODULE=on
go:
- oldstable
- stable
before_install:
- go version
script:
- |
set -e
make lint
make cover
after_success:
- bash <(curl -s https://codecov.io/bash)

60
vendor/go.uber.org/multierr/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,60 @@
Releases
========
v1.6.0 (2020-09-14)
===================
- Actually drop library dependency on development-time tooling.
v1.5.0 (2020-02-24)
===================
- Drop library dependency on development-time tooling.
v1.4.0 (2019-11-04)
===================
- Add `AppendInto` function to more ergonomically build errors inside a
loop.
v1.3.0 (2019-10-29)
===================
- Switch to Go modules.
v1.2.0 (2019-09-26)
===================
- Support extracting and matching against wrapped errors with `errors.As`
and `errors.Is`.
v1.1.0 (2017-06-30)
===================
- Added an `Errors(error) []error` function to extract the underlying list of
errors for a multierr error.
v1.0.0 (2017-05-31)
===================
No changes since v0.2.0. This release is committing to making no breaking
changes to the current API in the 1.X series.
v0.2.0 (2017-04-11)
===================
- Repeatedly appending to the same error is now faster due to fewer
allocations.
v0.1.0 (2017-31-03)
===================
- Initial release

19
vendor/go.uber.org/multierr/LICENSE.txt generated vendored Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2017 Uber Technologies, Inc.
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.

42
vendor/go.uber.org/multierr/Makefile generated vendored Normal file
View File

@ -0,0 +1,42 @@
# Directory to put `go install`ed binaries in.
export GOBIN ?= $(shell pwd)/bin
GO_FILES := $(shell \
find . '(' -path '*/.*' -o -path './vendor' ')' -prune \
-o -name '*.go' -print | cut -b3-)
.PHONY: build
build:
go build ./...
.PHONY: test
test:
go test -race ./...
.PHONY: gofmt
gofmt:
$(eval FMT_LOG := $(shell mktemp -t gofmt.XXXXX))
@gofmt -e -s -l $(GO_FILES) > $(FMT_LOG) || true
@[ ! -s "$(FMT_LOG)" ] || (echo "gofmt failed:" | cat - $(FMT_LOG) && false)
.PHONY: golint
golint:
@cd tools && go install golang.org/x/lint/golint
@$(GOBIN)/golint ./...
.PHONY: staticcheck
staticcheck:
@cd tools && go install honnef.co/go/tools/cmd/staticcheck
@$(GOBIN)/staticcheck ./...
.PHONY: lint
lint: gofmt golint staticcheck
.PHONY: cover
cover:
go test -coverprofile=cover.out -coverpkg=./... -v ./...
go tool cover -html=cover.out -o cover.html
update-license:
@cd tools && go install go.uber.org/tools/update-license
@$(GOBIN)/update-license $(GO_FILES)

23
vendor/go.uber.org/multierr/README.md generated vendored Normal file
View File

@ -0,0 +1,23 @@
# multierr [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
`multierr` allows combining one or more Go `error`s together.
## Installation
go get -u go.uber.org/multierr
## Status
Stable: No breaking changes will be made before 2.0.
-------------------------------------------------------------------------------
Released under the [MIT License].
[MIT License]: LICENSE.txt
[doc-img]: https://godoc.org/go.uber.org/multierr?status.svg
[doc]: https://godoc.org/go.uber.org/multierr
[ci-img]: https://travis-ci.com/uber-go/multierr.svg?branch=master
[cov-img]: https://codecov.io/gh/uber-go/multierr/branch/master/graph/badge.svg
[ci]: https://travis-ci.com/uber-go/multierr
[cov]: https://codecov.io/gh/uber-go/multierr

Some files were not shown because too many files have changed in this diff Show More