diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 351d7be..f91a5a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,8 +30,5 @@ jobs: - name: Build Progress Reporter run: go build ./cmd/progress-report - - name: Build Score Updater - run: go build ./cmd/score-update - - name: Run tests run: go test ./... diff --git a/.goreleaser.yml b/.goreleaser.yml index f2fcb57..f515eee 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -33,21 +33,6 @@ builds: - arm64 goarm: [6, 7] -- id: score-update - main: ./cmd/score-update - binary: score-update - env: - - CGO_ENABLED=0 - goos: - - linux - - darwin - - windows - goarch: - - amd64 - - arm - - arm64 - goarm: [6, 7] - archives: - id: habit-service-archive name_template: |- @@ -60,7 +45,6 @@ archives: builds: - habit-service - progress-report - - score-update replacements: 386: i386 amd64: x86_64 diff --git a/Dockerfile b/Dockerfile index 390ac8b..145b566 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,12 +6,10 @@ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -o /bin/habit-service ./cmd/habit-service RUN CGO_ENABLED=0 go build -o /bin/progress-report ./cmd/progress-report -RUN CGO_ENABLED=0 go build -o /bin/score-update ./cmd/score-update FROM scratch COPY --from=build /bin/habit-service /bin/habit-service COPY --from=build /bin/progress-report /bin/progress-report -COPY --from=build /bin/score-update /bin/score-update COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfo COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ WORKDIR /bin diff --git a/README.md b/README.md index b973a71..1fab47e 100644 --- a/README.md +++ b/README.md @@ -26,12 +26,6 @@ A new [Docker image](https://github.com/utkuufuk?tab=packages&repo_name=habit-se --name habit-service \ ghcr.io/utkuufuk/habit-service/image:latest - # score update runner - docker run --rm \ - --env-file \ - ghcr.io/utkuufuk/habit-service/image:latest \ - ./score-update - # progress report runner docker run --rm \ --env-file \ diff --git a/cmd/habit-service/main.go b/cmd/habit-service/main.go index 01cf44b..4b9010e 100644 --- a/cmd/habit-service/main.go +++ b/cmd/habit-service/main.go @@ -7,18 +7,17 @@ import ( "io/ioutil" "net/http" "os" - "regexp" "strings" "github.com/utkuufuk/habit-service/internal/config" - "github.com/utkuufuk/habit-service/internal/habit" "github.com/utkuufuk/habit-service/internal/logger" "github.com/utkuufuk/habit-service/internal/service" + "github.com/utkuufuk/habit-service/internal/sheets" ) var ( cfg config.ServerConfig - client habit.Client + client sheets.Client ) func init() { @@ -29,7 +28,7 @@ func init() { os.Exit(1) } - client, err = habit.GetClient(context.Background(), cfg.GoogleSheets) + client, err = sheets.GetClient(context.Background(), cfg.GoogleSheets) if err != nil { logger.Error("Could not create gsheets client for Habit Service: %v", err) os.Exit(1) @@ -48,10 +47,7 @@ func handleEntrelloRequest(w http.ResponseWriter, req *http.Request) { } if req.Method == http.MethodGet { - action := service.FetchHabitsAsTrelloCardsAction{ - TimezoneLocation: cfg.TimezoneLocation, - } - cards, err := action.Run(req.Context(), client) + cards, err := service.FetchHabitCards(client, cfg.TimezoneLocation) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, fmt.Sprintf("could not fetch new cards: %v", err)) @@ -71,41 +67,17 @@ func handleEntrelloRequest(w http.ResponseWriter, req *http.Request) { return } - var card struct { - Desc string `json:"desc"` - Labels []struct { - Name string `json:"name"` - } `json:"labels"` - } + var card service.TrelloCard + cell := strings.Split(card.Desc, "\n")[0] if err = json.Unmarshal(body, &card); err != nil { logger.Warn("Invalid request body: %v", err) w.WriteHeader(http.StatusBadRequest) return } - cell := strings.Split(card.Desc, "\n")[0] - matched, err := regexp.MatchString(`[a-zA-Z]{3} 202\d![A-Z][1-9][0-9]?$|^100$`, cell) - if err != nil || matched == false { - logger.Error("Invalid cell name '%s' in card description: %v", cell, err) - w.WriteHeader(http.StatusUnprocessableEntity) - return - } - - symbol := "✔" - for _, c := range card.Labels { - if c.Name == "habit-skip" { - symbol = "–" - break - } - if c.Name == "habit-fail" { - symbol = "✘" - break - } - } - - _, err = service.MarkHabitAction{Cell: cell, Symbol: symbol}.Run(req.Context(), client) + err = service.UpdateHabit(client, cfg.TimezoneLocation, cell, card.Labels) if err != nil { - logger.Error("Could not mark habit at cell '%s' as %s: %v", cell, symbol, err) + logger.Error("Could not update habit at cell '%s': %v", cell, err) w.WriteHeader(http.StatusInternalServerError) return } diff --git a/cmd/progress-report/main.go b/cmd/progress-report/main.go index 9d34842..4b46582 100644 --- a/cmd/progress-report/main.go +++ b/cmd/progress-report/main.go @@ -5,9 +5,9 @@ import ( "os" "github.com/utkuufuk/habit-service/internal/config" - "github.com/utkuufuk/habit-service/internal/habit" "github.com/utkuufuk/habit-service/internal/logger" "github.com/utkuufuk/habit-service/internal/service" + "github.com/utkuufuk/habit-service/internal/sheets" ) func main() { @@ -18,21 +18,19 @@ func main() { } ctx := context.Background() - client, err := habit.GetClient(ctx, cfg.GoogleSheets) + client, err := sheets.GetClient(ctx, cfg.GoogleSheets) if err != nil { logger.Error("Could not create gsheets client for Habit Service: %v", err) os.Exit(1) } - action := service.ReportProgressAction{ - TimezoneLocation: cfg.TimezoneLocation, - SkipList: cfg.SkipList, - TelegramChatId: cfg.TelegramChatId, - TelegramToken: cfg.TelegramToken, - } - - _, err = action.Run(ctx, client) - if err != nil { + if err = service.ReportProgress( + client, + cfg.TimezoneLocation, + cfg.SkipList, + cfg.TelegramChatId, + cfg.TelegramToken, + ); err != nil { logger.Error("Could not run Glados command: %v", err) os.Exit(1) } diff --git a/cmd/score-update/main.go b/cmd/score-update/main.go deleted file mode 100644 index 9a04040..0000000 --- a/cmd/score-update/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "context" - "os" - "time" - - "github.com/utkuufuk/habit-service/internal/config" - "github.com/utkuufuk/habit-service/internal/habit" - "github.com/utkuufuk/habit-service/internal/logger" -) - -func main() { - loc, cfg := config.ParseCommonConfig() - client, err := habit.GetClient(context.Background(), cfg) - if err != nil { - logger.Error("Could not create gsheets client for Habit Service: %v", err) - os.Exit(1) - } - - now := time.Now().In(loc) - habits, err := client.FetchHabits(now) - if err != nil { - logger.Error("could not fetch habits: %v", err) - os.Exit(1) - } - - if err = client.UpdateScores(habits, now); err != nil { - logger.Error("could not update habit scores: %v", err) - os.Exit(1) - } -} diff --git a/go.mod b/go.mod index 3e9ac02..faed436 100644 --- a/go.mod +++ b/go.mod @@ -5,30 +5,31 @@ go 1.18 require ( github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible - github.com/google/go-cmp v0.5.4 + github.com/google/go-cmp v0.5.8 github.com/joho/godotenv v1.4.0 - github.com/utkuufuk/entrello v0.1.1 - golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d - golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 - google.golang.org/api v0.40.0 + golang.org/x/exp v0.0.0-20220428152302-39d4317da171 + golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 + golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 + google.golang.org/api v0.77.0 ) require ( - cloud.google.com/go v0.74.0 // indirect - github.com/adlio/trello v1.8.0 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.4.3 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect + cloud.google.com/go/compute v1.6.0 // indirect + github.com/adlio/trello v1.9.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/googleapis/gax-go/v2 v2.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/technoweenie/multipartstreamer v1.0.1 // indirect - go.opencensus.io v0.22.5 // indirect - golang.org/x/exp v0.0.0-20220428152302-39d4317da171 - golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 // indirect - golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect - golang.org/x/text v0.3.6 // indirect + github.com/utkuufuk/entrello v1.1.2 + go.opencensus.io v0.23.0 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d // indirect - google.golang.org/grpc v1.34.0 // indirect - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e // indirect + google.golang.org/grpc v1.46.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index a0b0b05..f023d0f 100644 --- a/go.sum +++ b/go.sum @@ -14,14 +14,30 @@ cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0 h1:kpgPA77kSSbjSs+fWHkPTxQ6J5Z2Qkruo5jfXEkHxNQ= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0 h1:XdQIN5mdPTSBVwSIVDuY5e8ZzVAccsHvD3qTEz4zIps= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -36,22 +52,40 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/adlio/trello v1.8.0 h1:VU/1zwzuRzATsFC8WiK4f8R0HHQPWpf2H658KEchsmA= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/adlio/trello v1.8.0/go.mod h1:l2068AhUuUuQ9Vsb95ECMueHThYyAj4e85lWPmr2/LE= +github.com/adlio/trello v1.9.0 h1:b8R1oic2yksok5McAd+kcfsvTq5sX+Fv8rMTSpBBRq8= +github.com/adlio/trello v1.9.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -60,8 +94,9 @@ github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -69,6 +104,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -82,8 +119,12 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -94,11 +135,17 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -108,11 +155,21 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -134,25 +191,32 @@ 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= -github.com/utkuufuk/entrello v0.1.1 h1:F3D8rqki/O5zOaGm0Tn66oa6LEkp6i5134oReHLiV7c= -github.com/utkuufuk/entrello v0.1.1/go.mod h1:nMQhDsLBvcOmo5iIh2T+UFCYD0Dfs1xudPUxVtUArGc= +github.com/utkuufuk/entrello v1.1.2 h1:R+SeWKlfH/hc05DhBS3ina3v9oJEo3iWoh2dUjuEedA= +github.com/utkuufuk/entrello v1.1.2/go.mod h1:zzBRIqCPdDaWZDFehYn2yAQf48RvNaO0HQz5Z8nbc4A= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 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= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -172,8 +236,8 @@ golang.org/x/exp v0.0.0-20220428152302-39d4317da171 h1:TfdoLivD44QwvssI9Sv1xwa5D golang.org/x/exp v0.0.0-20220428152302-39d4317da171/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs= -golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 h1:LRtI4W37N+KFebI/qV0OFiLUv4GLOWeEW5hn/KEJvxE= +golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -185,6 +249,7 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -194,6 +259,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB 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.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -222,8 +289,19 @@ 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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11 h1:lwlPPsmjDKK0J6eG6xDWd5XPehI0R024zxjDnw3esPA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -232,8 +310,18 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -244,6 +332,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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -274,20 +363,49 @@ 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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/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-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 h1:Js08h5hqB5xyWR789+QqueR6sDE8mk+YvpETZ+F6X9Y= +golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -333,11 +451,18 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -356,8 +481,25 @@ google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSr google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0 h1:uWrpz12dpVPn7cojP82mk02XDgTJLDPc2KbVTxrWb4A= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.77.0 h1:msijLTxwkJ7Jub5tv9KBVCKtHOQwnvnvkX7ErFFCVxY= +google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -389,6 +531,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -399,8 +542,44 @@ google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d h1:HV9Z9qMhQEsdlvxNFELgQ11RkMzO3CMkjEySjCtuLes= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e h1:gMjH4zLGs9m+dGzR7qHCHaXMOwsJHJKKkHtyXhtOrJk= +google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -414,9 +593,24 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -426,16 +620,22 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/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/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= diff --git a/internal/entrello/entrello.go b/internal/entrello/entrello.go deleted file mode 100644 index 8201aaf..0000000 --- a/internal/entrello/entrello.go +++ /dev/null @@ -1,35 +0,0 @@ -package entrello - -import ( - "fmt" - "time" - - "github.com/utkuufuk/entrello/pkg/trello" - "github.com/utkuufuk/habit-service/internal/habit" -) - -const ( - dueHour = 23 -) - -// ToCards returns a slice of trello cards from the given habits which haven't been marked today -func ToCards(habits map[string]habit.Habit, now time.Time) (cards []trello.Card, err error) { - for name, habit := range habits { - if habit.State != "" { - continue - } - - // include the day of month in card title to force overwrite in the beginning of the next day - title := fmt.Sprintf("%v (%d)", name, now.Day()) - - due := time.Date(now.Year(), now.Month(), now.Day(), dueHour, 0, 0, 0, now.Location()) - c, err := trello.NewCard(title, habit.CellName, &due) - if err != nil { - return nil, fmt.Errorf("could not create habit card: %w", err) - } - - cards = append(cards, c) - } - - return cards, nil -} diff --git a/internal/entrello/entrello_test.go b/internal/entrello/entrello_test.go deleted file mode 100644 index 64f8a69..0000000 --- a/internal/entrello/entrello_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package entrello - -import ( - "testing" - "time" - - "github.com/utkuufuk/habit-service/internal/habit" -) - -func TestToCards(t *testing.T) { - cellName := "Jun 2020!C3" - - tt := []struct { - name string - habits map[string]habit.Habit - numCards int - err error - }{ - { - name: "marked habits", - habits: map[string]habit.Habit{ - "a": {CellName: cellName, State: "✔", Score: 0}, - "b": {CellName: cellName, State: "x", Score: 0}, - "c": {CellName: cellName, State: "✘", Score: 0}, - "d": {CellName: cellName, State: "–", Score: 0}, - "e": {CellName: cellName, State: "-", Score: 0}, - }, - numCards: 0, - err: nil, - }, - { - name: "some marked some unmarked habits", - habits: map[string]habit.Habit{ - "a": {CellName: cellName, State: "✔", Score: 0}, - "b": {CellName: cellName, State: "x", Score: 0}, - "c": {CellName: cellName, State: "✘", Score: 0}, - "d": {CellName: cellName, State: "–", Score: 0}, - "e": {CellName: cellName, State: "-", Score: 0}, - "f": {CellName: cellName, State: "", Score: 0}, - "g": {CellName: cellName, State: "", Score: 0}, - }, - numCards: 2, - err: nil, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - cards, err := ToCards(tc.habits, time.Now()) - if same := (err == nil && tc.err == nil) || tc.err != nil && err != nil; !same { - t.Fatalf("want '%v', got '%v'", tc.err, err) - } - - if len(cards) != tc.numCards { - t.Errorf("expected %d cards, got %d", tc.numCards, len(cards)) - } - }) - } -} diff --git a/internal/habit/habit.go b/internal/habit/habit.go index b1bc22a..67b5ca7 100644 --- a/internal/habit/habit.go +++ b/internal/habit/habit.go @@ -1,14 +1,12 @@ package habit import ( - "context" "fmt" "math" - "strings" + "regexp" "time" - "github.com/utkuufuk/habit-service/internal/config" - "google.golang.org/api/sheets/v4" + "github.com/utkuufuk/habit-service/internal/sheets" ) const ( @@ -17,79 +15,70 @@ const ( dataRowIdx = 2 // number of rows before the first data row starts in the spreadsheet dataColumnIdx = 1 // number of columns before the first data column starts in the spreadsheet - symbolDone = "✔" - symbolFailed = "✘" - symbolSkipped = "–" + SymbolDone = "✔" + SymbolFail = "✘" + SymbolSkip = "–" ) -type Client struct { - spreadsheetId string - service *sheets.SpreadsheetsValuesService -} - type Habit struct { - CellName string - State string - Score float64 -} - -type cell struct { - col string - row int -} - -func GetClient(ctx context.Context, cfg config.GoogleSheetsConfig) (client Client, err error) { - service, err := initService(ctx, cfg) - if err != nil { - return client, fmt.Errorf("could not initialize gsheets service: %w", err) - } - return Client{cfg.SpreadsheetId, service.Spreadsheets.Values}, nil + Cell sheets.Range + State string + Score float64 } -// FetchHabits retrieves the state of today's habits from the spreadsheet -func (c Client) FetchHabits(now time.Time) (map[string]Habit, error) { - rangeName, err := getRangeName(now, cell{"A", 1}, cell{"Z", now.Day() + dataRowIdx}) +// FetchAll retrieves the state of habits from the spreadsheet as of the selected date +func FetchAll(client sheets.Client, date time.Time) (map[string]Habit, error) { + rng, err := sheets.GetRange( + getSheetName(date), + sheets.Cell{Col: "A", Row: 1}, + sheets.Cell{Col: "Z", Row: date.Day() + dataRowIdx}, + ) if err != nil { - return nil, fmt.Errorf("could not get range name: %w", err) + return nil, fmt.Errorf("could not get spreadsheet range: %w", err) } - rows, err := c.readCells(rangeName) + rows, err := client.ReadCells(rng) if err != nil { return nil, fmt.Errorf("could not read cells: %w", err) } - return mapHabits(rows, now) + return parseHabitMap(rows, date) } -func (c Client) MarkHabit(cellName string, symbol string) error { - values := make([][]interface{}, 1) - values[0] = make([]interface{}, 1) - values[0][0] = symbol - return c.writeCells(values, cellName) -} +// Mark marks the habit with the given cell as done/failed/skipped +func Mark(client sheets.Client, cell, symbol string) error { + ok, err := regexp.MatchString(`[a-zA-Z]{3} 202\d![A-Z]([1-9]|[1-3][0-9])$`, cell) + if err != nil || ok == false { + return fmt.Errorf("invalid cell name '%s': %w", cell, err) + } + + if symbol != SymbolDone && symbol != SymbolFail && symbol != SymbolSkip { + return fmt.Errorf("invalid symbol '%s' to mark habit", symbol) + } + + if err := client.WriteCell(cell, symbol); err != nil { + return fmt.Errorf("could not write symbol '%s' to cell '%s': %w", symbol, cell, err) + } -func IsValidMarkSymbol(symbol string) bool { - return symbol == symbolDone || symbol == symbolFailed || symbol == symbolSkipped + return nil } -func (c Client) UpdateScores(habits map[string]Habit, now time.Time) error { - // map habit scores into a slice ordered by column +// WriteScores (re)writes the scores of the given habits in the spreadsheet +func WriteScores(client sheets.Client, date time.Time, habits []Habit) error { scores := make([]float64, len(habits)) - var cellNameComponents []string for _, habit := range habits { - cellNameComponents = strings.Split(habit.CellName, "!") - col := []rune(cellNameComponents[1][0:1])[0] - idx := int(col) - int('A') - 1 + idx := habit.Cell.GetStartColumnIndex() scores[idx] = habit.Score } - // get range name to write habit scores in the sheet row := scoreRowIdx + 1 - firstCol := string(rune(int('A') + dataColumnIdx)) - lastCol := string(rune(int('A') + len(habits))) - rangeName, err := getRangeName(now, cell{firstCol, row}, cell{lastCol, row}) + scoreRowRange, err := sheets.GetRange( + getSheetName(date), + sheets.Cell{Col: string(rune(int('A') + dataColumnIdx)), Row: row}, + sheets.Cell{Col: string(rune(int('A') + len(habits))), Row: row}, + ) if err != nil { - return fmt.Errorf("could not get range name: %w", err) + return fmt.Errorf("could not get score row range: %w", err) } values := make([][]interface{}, 1) @@ -97,38 +86,23 @@ func (c Client) UpdateScores(habits map[string]Habit, now time.Time) error { for i, score := range scores { values[0][i] = score } - return c.writeCells(values, rangeName) -} - -// readCells reads a range of cell values with the given range -func (c Client) readCells(rangeName string) ([][]interface{}, error) { - resp, err := c.service.Get(c.spreadsheetId, rangeName).Do() - if err != nil { - return nil, fmt.Errorf("could not read cells: %w", err) - } - return resp.Values, nil + return client.WriteCells(values, scoreRowRange) } -// writeCells writes a 2D array of values into a range of cells -func (c Client) writeCells(values [][]interface{}, rangeName string) error { - _, err := c.service. - Update(c.spreadsheetId, rangeName, &sheets.ValueRange{Values: values}). - ValueInputOption("USER_ENTERED"). - Do() - - if err != nil { - return fmt.Errorf("could not write cells: %w", err) - } - return nil +// getSheetName gets the sheet name corresponding to the given date +func getSheetName(date time.Time) string { + month := date.Month().String()[:3] + year := date.Year() + return fmt.Sprintf("%s %d", month, year) } -// mapHabits creates a map of habits for given a date and a spreadsheet row data -func mapHabits(rows [][]interface{}, date time.Time) (map[string]Habit, error) { +// parseHabitMap parses a map of habits from spreadsheet row data for the given date +func parseHabitMap(rows [][]interface{}, date time.Time) (map[string]Habit, error) { habits := make(map[string]Habit) for col := dataColumnIdx; col < len(rows[0]); col++ { - // evaluate the habit's cell name for today - c := cell{string(rune('A' + col)), date.Day() + dataRowIdx} - cellName, err := getRangeName(date, c, c) + // evaluate the habit's cell name the selected date + c := sheets.Cell{Col: string(rune('A' + col)), Row: date.Day() + dataRowIdx} + cell, err := sheets.GetRange(getSheetName(date), c, c) if err != nil { return nil, err } @@ -154,11 +128,11 @@ func mapHabits(rows [][]interface{}, date time.Time) (map[string]Habit, error) { } val := rows[row][col] - if val == symbolDone { + if val == SymbolDone { nom++ } - if val == symbolSkipped { + if val == SymbolSkip { denom-- } } @@ -167,28 +141,7 @@ func mapHabits(rows [][]interface{}, date time.Time) (map[string]Habit, error) { score = 0 } - habits[name] = Habit{cellName, state, score} + habits[name] = Habit{cell, state, score} } return habits, nil } - -// getRangeName gets the range name given a date and start & end cells -func getRangeName(date time.Time, start, end cell) (string, error) { - if start.col < "A" || start.col > "Z" || start.row <= 0 { - return "", fmt.Errorf("invalid start cell: %s%d", start.col, start.row) - } - - month := date.Month().String()[:3] - year := date.Year() - - // assume single cell if no end date specified - if end.col == "" || end.row == 0 || (end.col == start.col && end.row == start.row) { - return fmt.Sprintf("%s %d!%s%d", month, year, start.col, start.row), nil - } - - if end.col < "A" || end.col > "Z" || end.row <= 0 { - return "", fmt.Errorf("invalid end cell: %s%d", end.col, end.row) - } - - return fmt.Sprintf("%s %d!%s%d:%s%d", month, year, start.col, start.row, end.col, end.row), nil -} diff --git a/internal/habit/habit_test.go b/internal/habit/habit_test.go index fea1a38..f5141d3 100644 --- a/internal/habit/habit_test.go +++ b/internal/habit/habit_test.go @@ -8,7 +8,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestMapHabits(t *testing.T) { +func TestParseHabitMap(t *testing.T) { any := "." tt := []struct { @@ -101,7 +101,7 @@ func TestMapHabits(t *testing.T) { } } - habits, err := mapHabits(data, date) + habits, err := parseHabitMap(data, date) if same := (err == nil && tc.err == nil) || tc.err != nil && err != nil; !same { t.Fatalf("want '%v', got '%v'", tc.err, err) } @@ -112,93 +112,3 @@ func TestMapHabits(t *testing.T) { }) } } - -func TestGetRangeName(t *testing.T) { - tt := []struct { - name string - year int - month int - start cell - end cell - out string - err error - }{ - { - name: "invalid start col", - year: 2020, - month: 1, - start: cell{"", 1}, - end: cell{}, - err: errors.New(""), - }, - { - name: "invalid start row", - year: 2020, - month: 1, - start: cell{"A", 0}, - end: cell{}, - err: errors.New(""), - }, - { - name: "invalid end col", - year: 2020, - month: 1, - start: cell{"A", 1}, - end: cell{"0", 1}, - err: errors.New(""), - }, - { - name: "invalid end row", - year: 2020, - month: 1, - start: cell{"A", 1}, - end: cell{"A", -1}, - err: errors.New(""), - }, - { - name: "implicit single cell", - year: 2020, - month: 1, - start: cell{"A", 1}, - end: cell{}, - out: "Jan 2020!A1", - err: nil, - }, - { - name: "explicit single cell", - year: 2020, - month: 1, - start: cell{"A", 1}, - end: cell{"A", 1}, - out: "Jan 2020!A1", - err: nil, - }, - { - name: "valid range", - year: 2020, - month: 1, - start: cell{"B", 3}, - end: cell{"D", 5}, - out: "Jan 2020!B3:D5", - err: nil, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - date := time.Date(tc.year, time.Month(tc.month), 1, 0, 0, 0, 0, time.UTC) - out, err := getRangeName(date, tc.start, tc.end) - if same := (err == nil && tc.err == nil) || tc.err != nil && err != nil; !same { - t.Fatalf("want '%v', got '%v'", tc.err, err) - } - - if err == nil { - return - } - - if out != tc.out { - t.Fatalf("range name mismatch; want '%s', got '%s'", tc.out, out) - } - }) - } -} diff --git a/internal/service/action.go b/internal/service/action.go deleted file mode 100644 index 09cc21f..0000000 --- a/internal/service/action.go +++ /dev/null @@ -1,11 +0,0 @@ -package service - -import ( - "context" - - "github.com/utkuufuk/habit-service/internal/habit" -) - -type Action interface { - Run(context.Context, habit.Client) (string, error) -} diff --git a/internal/service/entrello.go b/internal/service/entrello.go new file mode 100644 index 0000000..26be460 --- /dev/null +++ b/internal/service/entrello.go @@ -0,0 +1,92 @@ +package service + +import ( + "fmt" + "time" + + "github.com/utkuufuk/entrello/pkg/trello" + "github.com/utkuufuk/habit-service/internal/habit" + "github.com/utkuufuk/habit-service/internal/sheets" + "golang.org/x/exp/maps" +) + +const dueHour = 23 + +type TrelloLabel struct { + Name string `json:"name"` +} + +type TrelloCard struct { + Desc string `json:"desc"` + Labels []TrelloLabel `json:"labels"` +} + +func FetchHabitCards(client sheets.Client, loc *time.Location) ([]trello.Card, error) { + now := time.Now().In(loc) + + habits, err := habit.FetchAll(client, now) + if err != nil { + return nil, fmt.Errorf("could not fetch habits: %w", err) + } + + cards, err := toTrelloCards(habits, now) + if err != nil { + return nil, fmt.Errorf("could not convert habits to Trello cards: %w", err) + } + + return cards, nil +} + +// UpdateHabit marks the habit as done/failed/skipped, +// and updates the scores of all habits in the spreadsheet. +func UpdateHabit(client sheets.Client, loc *time.Location, cell string, labels []TrelloLabel) error { + symbol := habit.SymbolDone + for _, l := range labels { + if l.Name == "habit-skip" { + symbol = habit.SymbolSkip + break + } + if l.Name == "habit-fail" { + symbol = habit.SymbolFail + break + } + } + + if err := habit.Mark(client, cell, symbol); err != nil { + return fmt.Errorf("could not mark habit at cell '%s' with %s: %w", cell, symbol, err) + } + + now := time.Now().In(loc) + habits, err := habit.FetchAll(client, now) + if err != nil { + return fmt.Errorf("could not fetch habits: %w", err) + } + + if err = habit.WriteScores(client, now, maps.Values(habits)); err != nil { + return fmt.Errorf("could not write habit scores: %w", err) + } + + return nil +} + +// toTrelloCards returns a slice of trello cards from the given habits which haven't been marked today +func toTrelloCards(habits map[string]habit.Habit, now time.Time) (cards []trello.Card, err error) { + for name, habit := range habits { + if habit.State != "" { + continue + } + + // include the day of month in card title to force overwrite in the beginning of the next day + title := fmt.Sprintf("%v (%d)", name, now.Day()) + + due := time.Date(now.Year(), now.Month(), now.Day(), dueHour, 0, 0, 0, now.Location()) + c, err := trello.NewCard(title, string(habit.Cell), &due) + if err != nil { + return nil, fmt.Errorf("could not create habit card: %w", err) + } + + cards = append(cards, c) + } + + return cards, nil +} diff --git a/internal/service/entrello_fetch.go b/internal/service/entrello_fetch.go deleted file mode 100644 index e9c8a31..0000000 --- a/internal/service/entrello_fetch.go +++ /dev/null @@ -1,26 +0,0 @@ -package service - -import ( - "context" - "fmt" - "time" - - "github.com/utkuufuk/entrello/pkg/trello" - "github.com/utkuufuk/habit-service/internal/entrello" - "github.com/utkuufuk/habit-service/internal/habit" -) - -type FetchHabitsAsTrelloCardsAction struct { - TimezoneLocation *time.Location -} - -func (a FetchHabitsAsTrelloCardsAction) Run(ctx context.Context, client habit.Client) ([]trello.Card, error) { - now := time.Now().In(a.TimezoneLocation) - - habits, err := client.FetchHabits(now) - if err != nil { - return nil, fmt.Errorf("could not fetch habits: %w", err) - } - - return entrello.ToCards(habits, now) -} diff --git a/internal/service/entrello_test.go b/internal/service/entrello_test.go new file mode 100644 index 0000000..4b1d85c --- /dev/null +++ b/internal/service/entrello_test.go @@ -0,0 +1,60 @@ +package service + +import ( + "testing" + "time" + + "github.com/utkuufuk/habit-service/internal/habit" + "github.com/utkuufuk/habit-service/internal/sheets" +) + +func TestToTrelloCards(t *testing.T) { + testCell := sheets.Range("Jun 2020!C3") + + tt := []struct { + name string + habits map[string]habit.Habit + numCards int + err error + }{ + { + name: "marked habits", + habits: map[string]habit.Habit{ + "a": {Cell: testCell, State: "✔", Score: 0}, + "b": {Cell: testCell, State: "x", Score: 0}, + "c": {Cell: testCell, State: "✘", Score: 0}, + "d": {Cell: testCell, State: "–", Score: 0}, + "e": {Cell: testCell, State: "-", Score: 0}, + }, + numCards: 0, + err: nil, + }, + { + name: "some marked some unmarked habits", + habits: map[string]habit.Habit{ + "a": {Cell: testCell, State: "✔", Score: 0}, + "b": {Cell: testCell, State: "x", Score: 0}, + "c": {Cell: testCell, State: "✘", Score: 0}, + "d": {Cell: testCell, State: "–", Score: 0}, + "e": {Cell: testCell, State: "-", Score: 0}, + "f": {Cell: testCell, State: "", Score: 0}, + "g": {Cell: testCell, State: "", Score: 0}, + }, + numCards: 2, + err: nil, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + cards, err := toTrelloCards(tc.habits, time.Now()) + if same := (err == nil && tc.err == nil) || tc.err != nil && err != nil; !same { + t.Fatalf("want '%v', got '%v'", tc.err, err) + } + + if len(cards) != tc.numCards { + t.Errorf("expected %d cards, got %d", tc.numCards, len(cards)) + } + }) + } +} diff --git a/internal/service/mark.go b/internal/service/mark.go deleted file mode 100644 index 47e2f6f..0000000 --- a/internal/service/mark.go +++ /dev/null @@ -1,31 +0,0 @@ -package service - -import ( - "context" - "fmt" - "regexp" - - "github.com/utkuufuk/habit-service/internal/habit" -) - -type MarkHabitAction struct { - Cell string - Symbol string -} - -func (a MarkHabitAction) Run(ctx context.Context, client habit.Client) (string, error) { - matched, err := regexp.MatchString(`[a-zA-Z]{3}\ 202\d\![A-Z][1-9][0-9]?$|^100$`, a.Cell) - if err != nil || matched == false { - return "", fmt.Errorf("invalid cell '%s' to mark habit: %v", a.Cell, err) - } - - if !habit.IsValidMarkSymbol(a.Symbol) { - return "", fmt.Errorf("invalid habit symbol '%s' to mark habit", a.Symbol) - } - - if err := client.MarkHabit(a.Cell, a.Symbol); err != nil { - return "", fmt.Errorf("could not mark habit on cell '%s' with symbol '%s': %v", a.Cell, a.Symbol, err) - } - - return "", nil -} diff --git a/internal/service/progress_report.go b/internal/service/progress_report.go index 95d0410..fc77fb4 100644 --- a/internal/service/progress_report.go +++ b/internal/service/progress_report.go @@ -1,7 +1,6 @@ package service import ( - "context" "fmt" "io/ioutil" "os" @@ -10,52 +9,52 @@ import ( tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" "github.com/utkuufuk/habit-service/internal/habit" + "github.com/utkuufuk/habit-service/internal/sheets" "github.com/utkuufuk/habit-service/internal/tableimage" "golang.org/x/exp/slices" ) -type ReportProgressAction struct { - TimezoneLocation *time.Location - SkipList []string - TelegramChatId int64 - TelegramToken string -} - -func (a ReportProgressAction) Run(ctx context.Context, client habit.Client) (string, error) { - now := time.Now().In(a.TimezoneLocation) - currentHabits, err := client.FetchHabits(now) +func ReportProgress( + client sheets.Client, + loc *time.Location, + skipList []string, + telegramChatId int64, + telegramToken string, +) error { + now := time.Now().In(loc) + thisMonthHabits, err := habit.FetchAll(client, now) if err != nil { - return "", fmt.Errorf("could not fetch this month's habits: %w\n", err) + return fmt.Errorf("could not fetch this month's habits: %w\n", err) } year, month, _ := now.Date() - lastMonth := time.Date(year, month, 1, 0, 0, 0, 0, a.TimezoneLocation).Add(-time.Nanosecond) - previousHabits, err := client.FetchHabits(lastMonth) + endOfLastMonth := time.Date(year, month, 1, 0, 0, 0, 0, loc).Add(-time.Nanosecond) + lastMonthHabits, err := habit.FetchAll(client, endOfLastMonth) if err != nil { - return "", fmt.Errorf("could not fetch habits from last month: %w\n", err) + return fmt.Errorf("could not fetch habits from last month: %w\n", err) } table := newTable() - for name, habit := range currentHabits { - if slices.Contains(a.SkipList, name) { + for name, habit := range thisMonthHabits { + if slices.Contains(skipList, name) { continue } - table.addRow(name, previousHabits[name].Score*100, habit.Score*100) + table.addRow(name, lastMonthHabits[name].Score*100, habit.Score*100) } path := fmt.Sprintf("./progress-report-%s.png", now.Format("2006-01-02T15:04:05")) table.save(path) - err = a.sendProgressReport(path) + err = sendProgressReport(path, telegramChatId, telegramToken) if err != nil { - return "", fmt.Errorf("could not send progress report: %w\n", err) + return fmt.Errorf("could not send progress report: %w\n", err) } - return "", os.Remove(path) + return os.Remove(path) } -func (a ReportProgressAction) sendProgressReport(path string) error { - bot, err := tgbotapi.NewBotAPI(a.TelegramToken) +func sendProgressReport(path string, telegramChatId int64, telegramToken string) error { + bot, err := tgbotapi.NewBotAPI(telegramToken) if err != nil { return fmt.Errorf("could not initialize Telegram bot client: %w", err) } @@ -65,7 +64,7 @@ func (a ReportProgressAction) sendProgressReport(path string) error { return fmt.Errorf("could not read progress report image '%s': %w", path, err) } - _, err = bot.Send(tgbotapi.NewPhotoUpload(a.TelegramChatId, tgbotapi.FileBytes{ + _, err = bot.Send(tgbotapi.NewPhotoUpload(telegramChatId, tgbotapi.FileBytes{ Name: "picture", Bytes: photoBytes, })) diff --git a/internal/habit/service.go b/internal/sheets/client.go similarity index 66% rename from internal/habit/service.go rename to internal/sheets/client.go index 87ab95f..142db85 100644 --- a/internal/habit/service.go +++ b/internal/sheets/client.go @@ -1,4 +1,4 @@ -package habit +package sheets import ( "context" @@ -17,7 +17,12 @@ const ( tokenUrl = "https://oauth2.googleapis.com/token" ) -func initService(ctx context.Context, cfg config.GoogleSheetsConfig) (service *sheets.Service, err error) { +type Client struct { + spreadsheetId string + service *sheets.SpreadsheetsValuesService +} + +func GetClient(ctx context.Context, cfg config.GoogleSheetsConfig) (client Client, err error) { auth := &oauth2.Config{ ClientID: cfg.GoogleClientId, ClientSecret: cfg.GoogleClientSecret, @@ -35,10 +40,11 @@ func initService(ctx context.Context, cfg config.GoogleSheetsConfig) (service *s RefreshToken: cfg.GoogleRefreshToken, Expiry: time.Now(), } + + service, err := sheets.New(auth.Client(ctx, token)) if err != nil { - return service, fmt.Errorf("could not get credentials for google spreadsheets: %w", err) + return client, fmt.Errorf("could not initialize gsheets service: %w", err) } - client := auth.Client(ctx, token) - return sheets.New(client) + return Client{cfg.SpreadsheetId, service.Spreadsheets.Values}, nil } diff --git a/internal/sheets/sheets.go b/internal/sheets/sheets.go new file mode 100644 index 0000000..80b1738 --- /dev/null +++ b/internal/sheets/sheets.go @@ -0,0 +1,69 @@ +package sheets + +import ( + "fmt" + "strings" + + "google.golang.org/api/sheets/v4" +) + +type Cell struct { + Col string + Row int +} + +type Range string + +func (r Range) GetStartColumnIndex() int { + cellNameComponents := strings.Split(string(r), "!") + col := []rune(cellNameComponents[1][0:1])[0] + return int(col) - int('A') - 1 +} + +// GetRange gets the range name given the sheet name and start & end cells +func GetRange(sheetName string, start, end Cell) (rng Range, err error) { + if start.Col < "A" || start.Col > "Z" || start.Row <= 0 { + return rng, fmt.Errorf("invalid start cell: %s%d", start.Col, start.Row) + } + + // return single cell name if no end cell specified + if end.Col == "" || end.Row == 0 || (end.Col == start.Col && end.Row == start.Row) { + return Range(fmt.Sprintf("%s!%s%d", sheetName, start.Col, start.Row)), nil + } + + if end.Col < "A" || end.Col > "Z" || end.Row <= 0 { + return rng, fmt.Errorf("invalid end cell: %s%d", end.Col, end.Row) + } + + return Range(fmt.Sprintf("%s!%s%d:%s%d", sheetName, start.Col, start.Row, end.Col, end.Row)), nil +} + +// ReadCells reads a range of cell values with the given range +func (c Client) ReadCells(rng Range) ([][]interface{}, error) { + resp, err := c.service.Get(c.spreadsheetId, string(rng)).Do() + if err != nil { + return nil, fmt.Errorf("could not read cells: %w", err) + } + return resp.Values, nil +} + +// WriteCell writes the given value into the cell +func (c Client) WriteCell(cellName string, value string) error { + values := make([][]interface{}, 1) + values[0] = make([]interface{}, 1) + values[0][0] = value + return c.WriteCells(values, Range(cellName)) +} + +// writeCells writes a 2D array of values into a range of cells +func (c Client) WriteCells(values [][]interface{}, rng Range) error { + _, err := c.service. + Update(c.spreadsheetId, string(rng), &sheets.ValueRange{Values: values}). + ValueInputOption("USER_ENTERED"). + Do() + + if err != nil { + return fmt.Errorf("could not write cells: %w", err) + } + return nil +} diff --git a/internal/sheets/sheets_test.go b/internal/sheets/sheets_test.go new file mode 100644 index 0000000..20f3fa3 --- /dev/null +++ b/internal/sheets/sheets_test.go @@ -0,0 +1,89 @@ +package sheets + +import ( + "errors" + "testing" +) + +func TestGetRangeName(t *testing.T) { + testSheetName := "Jan 2020" + + tt := []struct { + name string + sheetName string + start Cell + end Cell + out string + err error + }{ + { + name: "invalid start col", + sheetName: testSheetName, + start: Cell{"", 1}, + end: Cell{}, + err: errors.New(""), + }, + { + name: "invalid start row", + sheetName: testSheetName, + start: Cell{"A", 0}, + end: Cell{}, + err: errors.New(""), + }, + { + name: "invalid end col", + sheetName: testSheetName, + start: Cell{"A", 1}, + end: Cell{"0", 1}, + err: errors.New(""), + }, + { + name: "invalid end row", + sheetName: testSheetName, + start: Cell{"A", 1}, + end: Cell{"A", -1}, + err: errors.New(""), + }, + { + name: "implicit single cell", + sheetName: testSheetName, + start: Cell{"A", 1}, + end: Cell{}, + out: "Jan 2020!A1", + err: nil, + }, + { + name: "explicit single cell", + sheetName: testSheetName, + start: Cell{"A", 1}, + end: Cell{"A", 1}, + out: "Jan 2020!A1", + err: nil, + }, + { + name: "valid range", + sheetName: testSheetName, + start: Cell{"B", 3}, + end: Cell{"D", 5}, + out: "Jan 2020!B3:D5", + err: nil, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + rng, err := GetRange(testSheetName, tc.start, tc.end) + if same := (err == nil && tc.err == nil) || tc.err != nil && err != nil; !same { + t.Fatalf("want '%v', got '%v'", tc.err, err) + } + + if err == nil { + return + } + + if string(rng) != tc.out { + t.Fatalf("range name mismatch; want '%s', got '%v'", tc.out, rng) + } + }) + } +}