init commit

This commit is contained in:
shynd 2024-06-26 03:08:19 +02:00
commit 0ca987ca2e
No known key found for this signature in database
GPG Key ID: 31DC000B2E21D69D
22 changed files with 2974 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
#IDE Specific
.idea
#Generated Files
projects/
configs/
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
/export/
/defaults/
/assets/debug/
/logs/
/data/assets/videos/
*.mp3
assets/

1
README.md Normal file
View File

@ -0,0 +1 @@
# kemoforge

64
cmd/KemoForge/main.go Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"log"
"net/http"
"os"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/theme"
"g.r-io.lu/shynd/kemoforge/data/assets"
"g.r-io.lu/shynd/kemoforge/internal/KFEditor"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFLog"
_ "net/http/pprof"
)
const (
Version = "0.0.1"
Author = "Shynd"
)
var WindowTitle = "KemoForge" + " | " + Version
func main() {
// start the profiler (located at http://localhost:6060/debug/pprof/)
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// create a new application and window with the title based on the version
a := app.NewWithID("com.kemoforge.editor")
// load the embedded icons/editorpng as bytes
// TODO
iconBytes, err := assets.EditorPng.ReadFile("icons/editor.png")
if err != nil {
// if the icon fails to load, log the error and set the icon to the default application icon
log.Printf("failed to load icon: %v", err)
a.SetIcon(theme.FileApplicationIcon())
} else {
// if the icon loads successfully, set the icon to the loaded icon after converting it to a StaticResource
iconResource := fyne.NewStaticResource("editor.png", iconBytes)
a.SetIcon(iconResource)
}
w := a.NewWindow(WindowTitle)
// use common 720p resolution for base window size
w.Resize(fyne.NewSize(1280, 720))
userHome, err := os.UserConfigDir()
if err != nil {
log.Fatal(err)
}
err = KFLog.Setup(w, a.Preferences().StringWithFallback("logDir", userHome+"/KemoForge/Logs"))
if err != nil {
log.Fatal(err)
}
KFEditor.CreateMainContent(w)
log.Printf("showing main window")
w.ShowAndRun()
}

36
go.mod Normal file
View File

@ -0,0 +1,36 @@
module g.r-io.lu/shynd/kemoforge
go 1.20
require fyne.io/fyne/v2 v2.4.5
require (
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fredbi/uri v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb // indirect
github.com/go-text/render v0.1.0 // indirect
github.com/go-text/typesetting v0.1.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/tevino/abool v1.2.0 // indirect
github.com/yuin/goldmark v1.5.5 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
)

663
go.sum Normal file
View File

@ -0,0 +1,663 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
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/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/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/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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
fyne.io/fyne/v2 v2.4.5 h1:W6jpAEmLoBbKyBB+EXqI7GMJ7kLgHQWCa0wZHUV2VfQ=
fyne.io/fyne/v2 v2.4.5/go.mod h1:SlOgbca0y80cRObu/JOhxIJdIgtoW7aCyqUVlTMgs0Y=
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e h1:Hvs+kW2VwCzNToF3FmnIAzmivNgrclwPgoUdVSrjkP8=
fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
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/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fredbi/uri v1.0.0 h1:s4QwUAZ8fz+mbTsukND+4V5f+mJ/wjaTokwstGUAemg=
github.com/fredbi/uri v1.0.0/go.mod h1:1xC40RnIOGCaQzswaOvrzvG/3M3F0hyDVb3aO/1iGy0=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe h1:A/wiwvQ0CAjPkuJytaD+SsXkPU0asQ+guQEIg1BJGX4=
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe/go.mod h1:d4clgH0/GrRwWjRzJJQXxT/h1TyuNSfF/X64zb/3Ggg=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 h1:+31CdF/okdokeFNoy9L/2PccG3JFidQT3ev64/r4pYU=
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504/go.mod h1:gLRWYfYnMA9TONeppRSikMdXlHQ97xVsPojddUv3b/E=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 h1:hnLq+55b7Zh7/2IRzWCpiTcAvjv/P8ERF+N7+xXbZhk=
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2/go.mod h1:eO7W361vmlPOrykIg+Rsh1SZ3tQBaOsfzZhsIOb/Lm0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
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=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb h1:S9I8pIVT5JHKDvmI1vQ0qs5fqxzUfhcZm/YbUC/8k1k=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-text/render v0.1.0 h1:osrmVDZNHuP1RSu3pNG7Z77Sd2xSbcb/xWytAj9kyVs=
github.com/go-text/render v0.1.0/go.mod h1:jqEuNMenrmj6QRnkdpeaP0oKGFLDNhDkVKwGjsWWYU4=
github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw=
github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/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=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
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/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=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
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/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/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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.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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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/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=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/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/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20211219123610-ec9572f70e60/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/goxjs/gl v0.0.0-20210104184919-e3fafc6f8f2a/go.mod h1:dy/f2gjY09hwVfIyATps4G2ai7/hLwLkc5TrPqONuXY=
github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838/go.mod h1:oS8P8gVOT4ywTcjV6wZlOU4GuVFQ8F5328KY3MJ79CY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
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/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
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=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
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/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
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.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
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=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
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/mobile v0.0.0-20211207041440-4e6c2922fdee/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34=
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
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.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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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-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-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
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-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-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-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-20210403161142-5e06dd20ab57/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-20210615035016-665e8c7367d1/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-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
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.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/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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/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=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8-0.20211022200916-316ba0b74098/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
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/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=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
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/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.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
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=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
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=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
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/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-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
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=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
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/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.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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/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=
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 h1:oomkgU6VaQDsV6qZby2uz1Lap0eXmku8+2em3A/l700=
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2/go.mod h1:sUMDUKNB2ZcVjt92UnLy3cdGs+wDAcrPdV3JP6sVgA4=
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=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -0,0 +1,527 @@
package KFEditor
import (
"bytes"
"embed"
"encoding/json"
"errors"
"io/fs"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFConfig"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFError"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFWidget/CalsWidgets"
)
type KFInfo struct {
Name string `json:"Name"`
Path string `json:"Path"`
OpenDate time.Time `json:"Last Opened"`
}
type KFProject struct {
Info KFInfo
Config *KFConfig.KFConfig
}
var (
//go:embed Templates/*
Templates embed.FS
ActiveProject *KFProject
)
func UpdateProjectInfo(info KFInfo) error {
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
kemoForgeDir := homeDir + "/Documents/KemoForge"
projects, err := ReadProjectInfo()
if err != nil {
return err
}
_, err = os.Stat(info.Path)
if os.IsNotExist(err) {
// file does not exist so remove it from the projects.kf file
for i, project := range projects {
if project.Name == info.Name {
projects = append(projects[:i], projects[i+1:]...)
serializedProjects, err := json.Marshal(projects)
if err != nil {
return err
}
err = os.WriteFile(kemoForgeDir+"/projects.kf", serializedProjects, 0777)
if err != nil {
return err
}
return nil
}
}
}
projectFound := false
for i, project := range projects {
if project.Name == info.Name {
projectFound = true
projects[i] = info
serializedProjects, err := json.Marshal(projects)
if err != nil {
return err
}
err = os.WriteFile(kemoForgeDir+"/projects.kf", serializedProjects, 0777)
if err != nil {
return err
}
return nil
}
}
if !projectFound {
projects = append(projects, info)
serializedProjects, err := json.Marshal(projects)
if err != nil {
return err
}
err = os.WriteFile(kemoForgeDir+"/projects.kf", serializedProjects, 0777)
if err != nil {
return err
}
}
return nil
}
// ReadProjectInfo reads the project info from the project file
func ReadProjectInfo() ([]KFInfo, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
kemoForgeDir := homeDir + "/Documents/KemoForge"
//Check if the KemoForge directory exists
if _, err := os.Stat(kemoForgeDir); os.IsNotExist(err) {
//if it doesn't exist, create it
err = os.MkdirAll(kemoForgeDir, 0777)
if err != nil {
return nil, err
}
}
//Check if the projects.kf file exists
if _, err := os.Stat(kemoForgeDir + "/projects.kf"); os.IsNotExist(err) {
//if it doesn't exist, create it
err = os.WriteFile(kemoForgeDir+"/projects.kf", []byte("[]"), 0777)
if err != nil {
return nil, err
}
return nil, nil
}
//Read the file
file, err := os.ReadFile(kemoForgeDir + "/projects.kf")
if err != nil {
return nil, err
}
//unmarshal the json into a slice of structs
var projects []KFInfo
err = json.Unmarshal(file, &projects)
if err != nil {
return nil, err
}
//Sort the projects by the last opened date
for i := 0; i < len(projects); i++ {
for j := 0; j < len(projects); j++ {
if projects[i].OpenDate.After(projects[j].OpenDate) {
temp := projects[i]
projects[i] = projects[j]
projects[j] = temp
}
}
}
//return the slice of structs
return projects, nil
}
func OpenFromInfo(info KFInfo, window fyne.Window) error {
path := info.Path
//Check if the path even exists
if _, err := os.Stat(path); os.IsNotExist(err) {
newError := UpdateProjectInfo(info)
if newError != nil {
errors.Join(err, newError, KFError.NewErrProjectNotFound(info.Name))
}
return err
}
//Check if the path ends in .KFProject
if filepath.Ext(path) != ".KFProject" {
//Check if the path is a directory and if the directory contains a .KFProject file
if _, err := os.Stat(path + "/" + filepath.Base(path) + ".KFProject"); os.IsNotExist(err) {
return KFError.NewErrProjectNotFound(info.Name)
} else {
//If it does, set the path to the directory
path = path + "/" + filepath.Base(path) + ".KFProject"
}
}
//Read the project file
file, err := os.ReadFile(path)
if err != nil {
return err
}
//Deserialize the project
project, err := Deserialize(file)
if err != nil {
return err
}
err = project.Load(window)
if err != nil {
return err
}
return nil
}
func Deserialize(file []byte) (project KFInfo, err error) {
err = json.Unmarshal(file, &project)
if err != nil {
return KFInfo{}, err
}
return project, nil
}
// Load takes a deserialized project and loads it into the editor loading the scenes and functions as well
func (p KFInfo) Load(window fyne.Window) error {
Project := &KFProject{
Info: p,
}
//Walk the game directory of the project for the .KFConfig file
//If it doesn't exist, return an error
gameDir := filepath.Dir(p.Path) + "/data/"
//Look for the first file ending in .KFConfig
err := filepath.Walk(gameDir, func(path string, info os.FileInfo, err error) error {
if filepath.Ext(path) == ".KFConfig" {
file, err := os.Open(path)
if err != nil {
return err
}
err = Project.Config.Load(file)
if err != nil {
return err
}
}
return nil
})
err = UpdateProjectInfo(p)
if err != nil {
return err
}
ActiveProject = Project
// window.SetContent(CreateSceneEditor(window)) // TODO
return nil
}
func (p KFProject) Create(window fyne.Window) error {
shouldDelete := false
deletePath := ""
defer func() {
if shouldDelete && deletePath != "" {
deletePath = filepath.Clean(deletePath)
if fs.ValidPath(deletePath) {
vbox := container.NewVBox(
widget.NewLabel("An error occurred while creating the project."),
widget.NewLabel("Would you like to delete the broken project?"),
widget.NewLabel("Delete will remove: "+deletePath),
widget.NewLabel("This action cannot be undone."),
)
dialog.ShowCustomConfirm("Delete Broken Project?", "Yes", "No", vbox, func(b bool) {
if b {
err := os.RemoveAll(deletePath)
if err != nil {
dialog.ShowError(err, window)
}
}
}, window)
}
}
}()
loadingChannel := make(chan struct{})
loading := CalsWidgets.NewLoading(loadingChannel, 100*time.Millisecond, 100)
loading.SetProgress(0, "Creating Project: "+p.Config.Name)
//Pop up a dialog with a progress bar and a label that says "Creating Project"
progressDialog := dialog.NewCustomWithoutButtons("Creating Project", loading.Box, window)
progressDialog.Show()
defer progressDialog.Hide()
homeDir, err := os.UserHomeDir()
if err != nil {
return err
}
kemoForgeDir := homeDir + "/Documents/KemoForge"
projectsDir := fyne.CurrentApp().Preferences().StringWithFallback("projectDir", kemoForgeDir+"/projects")
//First check if the project directory already exists
projectDir := projectsDir + "/" + p.Config.Name
projectDir = filepath.Clean(projectDir)
if !fs.ValidPath(projectDir) {
return errors.New("invalid path")
}
loading.SetProgress(10, "Checking if project already exists")
_, err = os.Stat(projectDir)
if os.IsNotExist(err) {
err = os.MkdirAll(projectDir, os.ModePerm)
if err != nil {
return err
}
} else {
return KFError.NewErrProjectAlreadyExists(p.Config.Name)
}
//Create the project directory
loading.SetProgress(20, "Creating Project Directory")
err = os.MkdirAll(projectDir, os.ModePerm)
deletePath = projectDir
if err != nil {
shouldDelete = true
return err
}
//Create the project file
loading.SetProgress(30, "Creating Project File")
projectFilePath := projectDir + "/" + p.Config.Name + ".KFProject"
projectFilePath = filepath.Clean(projectFilePath)
if !fs.ValidPath(projectFilePath) {
shouldDelete = true
return errors.New("invalid path")
}
err = os.WriteFile(projectFilePath, p.SerializeInfo(), os.ModePerm)
if err != nil {
shouldDelete = true
return err
}
neededDirectories := []string{
"cmd/" + p.Config.Name,
"data/assets/image",
"data/assets/audio",
"data/assets/video",
"data/assets/other",
"data/scenes",
"internal/functions",
"internal/layouts",
"internal/widgets",
}
percentPerDir := 10 / len(neededDirectories)
for _, dir := range neededDirectories {
dirPath := projectDir + "/" + dir
dirPath = filepath.Clean(dirPath)
if !fs.ValidPath(dirPath) {
shouldDelete = true
return errors.New("invalid path")
}
loading.SetProgress(loading.GetProgress()+float64(percentPerDir), "Creating "+dir)
err = os.MkdirAll(dirPath, os.ModePerm)
if err != nil {
shouldDelete = true
return err
}
}
loading.SetProgress(50, "Saving Local config")
err = p.Config.Save(projectDir + "/data/Game.KFConfig")
if err != nil {
shouldDelete = true
return err
}
// templateCombo is a struct that holds the template path, destination path, and data to be used in the template
type templateCombo struct {
templatePath string
destinationPath string
data interface{}
}
// neededFiles is a slice of templateCombos that holds all the files that need to be created
var neededFiles []templateCombo
//cmd/ProjectName/ProjectName.go
mainGameData := struct {
LocalFileSystem string
LocalFunctions string
LocalLayouts string
LocalWidgets string
}{
LocalFileSystem: p.Config.Name + "/data",
LocalFunctions: p.Config.Name + "/internal/functions",
LocalLayouts: p.Config.Name + "/internal/layouts",
LocalWidgets: p.Config.Name + "/internal/widgets",
}
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/MainGame/MainGame.got",
destinationPath: projectDir + "/cmd/" + p.Config.Name + "/" + p.Config.Name + ".go",
data: mainGameData,
})
//data/FileSystem.go
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/FileLoader/FileLoader.got",
destinationPath: projectDir + "/data/FileSystem.go",
data: nil,
})
//internal/functions/CustomFunctions.go
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/CustomFunction/CustomFunction.got",
destinationPath: projectDir + "/internal/functions/CustomFunction.go",
data: nil,
})
//internal/layouts/CustomLayouts.go
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/CustomLayout/CustomLayout.got",
destinationPath: projectDir + "/internal/layouts/CustomLayout.go",
data: nil,
})
//internal/widgets/CustomWidgets.go
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/CustomWidget/CustomWidget.got",
destinationPath: projectDir + "/internal/widgets/CustomWidget.go",
data: nil,
})
//data/scenes/MainMenu.KFScene
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/Scenes/MainMenu.KFScene",
destinationPath: projectDir + "/data/scenes/MainMenu.KFScene",
data: nil,
})
//data/scenes/NewGame.KFScene
neededFiles = append(neededFiles, templateCombo{
templatePath: "Templates/Scenes/NewGame.KFScene",
destinationPath: projectDir + "/data/scenes/NewGame.KFScene",
data: nil,
})
percentPerFile := 30 / len(neededFiles)
for _, file := range neededFiles {
pathWithoutProject := strings.TrimPrefix(file.destinationPath, projectDir)
loading.SetProgress(loading.GetProgress()+float64(percentPerFile), "Creating "+pathWithoutProject)
err = MakeFromTemplate(file.templatePath, file.destinationPath, file.data)
if err != nil {
shouldDelete = true
return err
}
}
//Initialize the go mod file by running go mod init with os/exec
loading.SetProgress(80, "Initializing go mod file")
// Initialize the go mod
var stderr bytes.Buffer
cmd := exec.Command("go", "mod", "init", p.Config.Name)
cmd.Stderr = &stderr
cmd.Dir = projectDir
err = cmd.Run()
if err != nil {
shouldDelete = true
log.Printf("Error initializing go mod file: %v", err)
log.Printf("Stderr: %s", stderr.String())
return errors.New("error initializing go mod file")
}
loading.SetProgress(90, "Installing fyne")
cmd = exec.Command("go", "get", "fyne.io/fyne/v2@latest")
cmd.Stderr = &stderr
cmd.Dir = projectDir
err = cmd.Run()
if err != nil {
log.Printf("Error installing fyne: %v", err)
log.Printf("Stderr: %s", stderr.String())
return errors.New("error installing fyne")
}
loading.SetProgress(95, "Installing KemoForge")
// cmd = exec.Command("go", "get", "g.r-io.lu/shynd/kemoforge@latest")
// cmd.Stderr = &stderr
// cmd.Dir = projectDir
// err = cmd.Run()
// if err != nil {
// log.Printf("Error installing KemoForge: %v", err)
// log.Printf("Stderr: %s", stderr.String())
// return errors.New("error installing KemoForge")
// }
//Run go mod tidy
loading.SetProgress(97, "Running go mod tidy")
cmd = exec.Command("go", "mod", "tidy")
cmd.Stderr = &stderr
cmd.Dir = projectDir
err = cmd.Run()
if err != nil {
log.Printf("Error running go mod tidy: %v", err)
log.Printf("Stderr: %s", stderr.String())
return errors.New("error running go mod tidy")
}
loading.SetProgress(100, "Project Created")
time.Sleep(1 * time.Second)
return nil
}
func (p KFProject) SerializeInfo() []byte {
//Marshal the project to JSON
serializedProject, err := json.MarshalIndent(p.Info, "", " ")
if err != nil {
return nil
}
return serializedProject
}
func MakeFromTemplate(templatePath, destinationPath string, data interface{}) error {
destinationPath = filepath.Clean(destinationPath)
if !fs.ValidPath(destinationPath) {
return errors.New("invalid path")
}
_, err := fs.Stat(Templates, templatePath)
if err != nil {
return err
}
destinationFile, err := os.Create(destinationPath)
defer destinationFile.Close()
if err != nil {
return err
}
t, err := template.ParseFS(Templates, templatePath)
if err != nil {
return err
}
err = t.Execute(destinationFile, data)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,60 @@
package KFEditor
import (
"errors"
"log"
"os/exec"
"regexp"
"strings"
"fyne.io/fyne/v2"
)
// SanitizeProjectName sanitizes the project name to ensure it's a valid Go identifier
func SanitizeProjectName(name string) (bool, error) {
// initialize regular expression
rgx := regexp.MustCompile(`[^\w\s]`)
if rgx.MatchString(name) {
return false, errors.New("project name contains invalid characters")
}
sanitizedName := rgx.ReplaceAllString(name, " ")
// remove space
rgx = regexp.MustCompile(`\s`)
if rgx.MatchString(sanitizedName) {
return false, errors.New("project name cannot contain spaces")
}
sanitizedName = rgx.ReplaceAllString(sanitizedName, "")
// check for reserved file names (Windows)
reservedNames := []string{"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}
for _, reserved := range reservedNames {
if strings.EqualFold(sanitizedName, reserved) {
return false, errors.New("project name cannot be a system reserved name")
}
}
// check for length limitations
maxLength := 255
if len(sanitizedName) > maxLength {
return false, errors.New("project name cannot exceed maximum length")
}
if sanitizedName == "" {
return false, errors.New("project name cannot be empty")
}
return true, nil
}
func CheckAndInstallDependencies(window fyne.Window) {
// check if Go is installed using exec.Command("go", "version")
// if it is not installed, prompt the user to install it
goCmd := exec.Command("go", "version")
err := goCmd.Run()
if err != nil {
log.Printf("Go is not installed, prompting user to install it...")
// TODO: Add in an automatic install og Go...
}
}

View File

@ -0,0 +1,459 @@
package KFEditor
import (
"log"
"os"
"path/filepath"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/storage"
"fyne.io/fyne/v2/widget"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFConfig"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFError"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFLog"
)
// CreateMainContent updates the loading variable as the KemoForge content is created
func CreateMainContent(window fyne.Window) {
// runs "go version" to check if Go is installed
// loading.SetProgress(10, "Checking dependencies")
CheckAndInstallDependencies(window)
// creates a main menu to hold the buttons below
// loading.SetProgress(30, "Creating main menu")
CreateMainMenu(window)
// create a grid layout for the four main buttons
// loading.SetProgress(50, "Creating main content")
grid := CreateMainGrid(window)
// fetch the buttons for the grid to allow for disabling them
openRecentButton := grid.(*fyne.Container).Objects[2].(*widget.Button)
continueLastButton := grid.(*fyne.Container).Objects[3].(*widget.Button)
// loading.SetProgress(70, "Initializing buttons")
err := InitButtons(window, continueLastButton, openRecentButton)
if err != nil {
// show an error dialog
dialog.ShowError(err, window)
return
}
// loading.SetProgress(100, "Setting content")
window.SetContent(grid)
// loading.Complete()
}
func InitButtons(window fyne.Window, continueLastButton *widget.Button, openRecentButton *widget.Button) error {
projects, err := ReadProjectInfo()
if err != nil {
//Show an error dialog
dialog.ShowError(err, window)
return err
}
var project KFInfo
if len(projects) == 0 {
continueLastButton.Disable()
openRecentButton.Disable()
} else {
//Get the most recently opened project
project = projects[0]
for i := 0; i < len(projects); i++ {
if projects[i].OpenDate.After(project.OpenDate) {
project = projects[i]
}
}
//Os stat the project path to see if it still exists
_, err = os.Stat(project.Path)
if err != nil {
//If the project does not exist, disable the continue last button
continueLastButton.Disable()
}
}
continueLastButton.OnTapped = func() {
err = OpenFromInfo(project, window)
if err != nil {
//Show an error dialog
dialog.ShowError(err, window)
}
}
return nil
}
// CreateMainGrid creates the main grid layout for the main window
func CreateMainGrid(window fyne.Window) fyne.CanvasObject {
grid := container.New(layout.NewGridLayout(2))
// create a 'New Project' button in the top left
newProjectButton := widget.NewButton("New Project", func() {
NewProjectDialog(window)
})
// create a 'Open Project' button in the top right
openProjectButton := widget.NewButton("Open Project", func() {
OpenProjectDialog(window)
})
// create a 'Open Recent' button in the bottom left
var openRecentButton *widget.Button
openRecentButton = widget.NewButton("Open Recent", func() {
err := OpenRecentDialog(window)
if err != nil {
// if there are no projects, or another issue occurs, disable the button
openRecentButton.Disable()
}
})
// create a 'Continue' button in the bottom right
continueLastButton := widget.NewButton("Continue Last", func() {}) // TODO
// add the buttons to the grid
grid.Add(newProjectButton)
grid.Add(openProjectButton)
grid.Add(openRecentButton)
grid.Add(continueLastButton)
return grid
}
type OpenPage int // Enum for the different pages of the editor to allow changing the menu bar based on the page
const (
HomePage OpenPage = iota
EditorPage
)
func CreateMainMenu(window fyne.Window) {
openRecentMenu := fyne.NewMenuItem("Open Recent", func() {
_ = OpenRecentDialog(window)
})
projectMenu := fyne.NewMenu("Project")
projects, err := ReadProjectInfo()
if err != nil {
//Show an error dialog
dialog.ShowError(err, window)
}
if len(projects) == 0 {
openRecentMenu.Disabled = true
} else {
openRecentMenu.Disabled = false
//Create a list of all the projects as menu items of the child menu
for i := 0; i < len(projects); i++ {
notFound := false
_, err := os.Stat(projects[i].Path)
if err != nil {
notFound = true
}
var newMenuItem *fyne.MenuItem
if notFound {
newMenuItem = fyne.NewMenuItem(projects[i].Name+" (Not Found)", func() {
err = UpdateProjectInfo(projects[i])
if err != nil {
//Show an error dialog
dialog.ShowError(err, window)
}
CreateMainMenu(window)
})
} else {
newMenuItem = fyne.NewMenuItem(projects[i].Name, func() {
err = OpenFromInfo(projects[i], window)
if err != nil {
dialog.ShowError(err, window)
}
})
}
projectMenu.Items = append(projectMenu.Items, newMenuItem)
}
}
openRecentMenu.ChildMenu = projectMenu
mainMenu := fyne.NewMainMenu(
fyne.NewMenu("File",
fyne.NewMenuItem("Home", func() {
grid := CreateMainGrid(window)
continueButton := grid.(*fyne.Container).Objects[3].(*widget.Button)
openRecentButton := grid.(*fyne.Container).Objects[2].(*widget.Button)
err = InitButtons(window, continueButton, openRecentButton)
if err != nil {
dialog.ShowError(err, window)
} else {
CreateMainMenu(window)
window.SetContent(grid)
}
}),
fyne.NewMenuItem("New Project", func() {
NewProjectDialog(window)
}),
fyne.NewMenuItem("Open Project", func() {
OpenProjectDialog(window)
}),
openRecentMenu,
fyne.NewMenuItem("Quit", func() {
os.Exit(0)
}),
),
fyne.NewMenu("View",
fyne.NewMenuItem("Full Screen", func() {
window.SetFullScreen(!window.FullScreen())
}),
fyne.NewMenuItem("Terminal", func() {
KFLog.ShowDialog(window)
}),
),
fyne.NewMenu("Help",
fyne.NewMenuItem("Documentation", func() {
//TODO: Open the documentation page
}),
fyne.NewMenuItem("About", func() {
//TODO: Open a dialog that shows the about information
}),
),
)
window.SetMainMenu(mainMenu)
}
func OpenRecentDialog(window fyne.Window) error {
box := container.NewVBox()
newDialog := dialog.NewCustom("Open Recent", "Close", box, window)
projects, err := ReadProjectInfo()
if err != nil {
//Show an error dialog
dialog.ShowError(err, window)
return err
}
if len(projects) == 0 {
//Show an error dialog
dialog.ShowError(KFError.ErrNoProjects, window)
return err
}
//Create a scrollable list of all the projects
list := widget.NewList(
func() int {
return len(projects) + 1
},
func() fyne.CanvasObject {
nameLabel := widget.NewLabel("Name")
lastOpenedLabel := widget.NewLabel("Last Opened")
//Set the style of the labels to bold
nameLabel.TextStyle = fyne.TextStyle{Bold: true}
lastOpenedLabel.TextStyle = fyne.TextStyle{Bold: true}
// Align the labels to the center
nameLabel.Alignment = fyne.TextAlignCenter
lastOpenedLabel.Alignment = fyne.TextAlignCenter
return container.NewHBox(widget.NewLabel("Name"), layout.NewSpacer(), widget.NewLabel("Last Opened"))
},
func(id widget.ListItemID, item fyne.CanvasObject) {
if id == 0 {
return
}
//Set the first label to the project name and the second to the last opened date
project := projects[id-1]
_, err := os.Stat(project.Path)
if err != nil {
item.(*fyne.Container).Objects[0].(*widget.Label).SetText(project.Name + " (Not Found, click to hide)")
} else {
item.(*fyne.Container).Objects[0].(*widget.Label).SetText(project.Name)
}
item.(*fyne.Container).Objects[2].(*widget.Label).SetText(project.OpenDate.Format("01/02/2006 13:04"))
})
list.OnSelected = func(id widget.ListItemID) {
if id == 0 {
return
}
project := projects[id-1]
_, err := os.Stat(project.Path)
if err != nil {
err := UpdateProjectInfo(project)
if err != nil {
dialog.ShowError(err, window)
return
}
projects, err = ReadProjectInfo()
if err != nil {
dialog.ShowError(err, window)
return
}
list.Refresh()
return
}
err = OpenFromInfo(projects[id-1], window)
if err != nil {
//Show an error dialog
errDialog := dialog.NewError(err, window)
errDialog.SetOnClosed(func() {
newDialog.Hide()
})
errDialog.Show()
} else {
newDialog.Hide()
}
}
scrollBox := container.NewVScroll(list)
scrollBox.SetMinSize(fyne.NewSize(400, 300))
box.Add(scrollBox)
newDialog.Resize(fyne.NewSize(800, 600))
newDialog.Show()
return nil
}
func OpenProjectDialog(window fyne.Window) {
// Create a custom file dialog
fileDialog := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
if err != nil {
log.Println("Error opening file")
// Show an error dialog
dialog.ShowError(err, window)
return
}
//If the reader is nil, then the user canceled the dialog and nothing should happen
if reader == nil {
return
}
// Read the file
file, err := os.ReadFile(reader.URI().Path())
if err != nil {
log.Println("Error reading file")
// Show an error dialog
dialog.ShowError(err, window)
return
}
// Deserialize the project
project, err := Deserialize(file)
if err != nil {
log.Println("Error deserializing project")
// Show an error dialog
dialog.ShowError(err, window)
return
}
err = project.Load(window)
if err != nil {
// Show an error dialog
log.Println("Error loading project")
dialog.ShowError(err, window)
return
}
}, window)
// Set filter for .KFProject files
fileDialog.SetFilter(storage.NewExtensionFileFilter([]string{".KFProject"}))
// Set the starting directory to the Projects directory
projectURI, err := storage.ListerForURI(storage.NewFileURI("projects"))
if err == nil {
fileDialog.SetLocation(projectURI)
}
//Set the size of the dialog to 800x600
fileDialog.Resize(fyne.NewSize(800, 600))
// Show the dialog
fileDialog.Show()
}
func NewProjectDialog(window fyne.Window) {
//Create a dialog that allows the user to create a new project
var err error
box := container.NewVBox()
projectDialog := dialog.NewCustom("New Project", "Cancel", container.NewVBox(layout.NewSpacer(), box, layout.NewSpacer()), window)
projectInfo := KFInfo{}
projectName := ""
nameEntry := widget.NewEntry()
nameValidationLabel := widget.NewLabelWithStyle("", fyne.TextAlignLeading, fyne.TextStyle{Italic: true})
nameEntry.SetPlaceHolder("Project Name")
nameConfirmButton := widget.NewButton("Confirm", func() {})
authorEntry := widget.NewEntry()
authorEntry.SetPlaceHolder("Author")
authorConfirmButton := widget.NewButton("Create Project", func() {})
authorBackButton := widget.NewButton("Back", func() {})
//Add everything to the box
box.Add(nameValidationLabel)
box.Add(nameEntry)
box.Add(nameConfirmButton)
box.Add(authorEntry)
box.Add(authorConfirmButton)
box.Add(authorBackButton)
authorConfirmButton.Hide()
authorBackButton.Hide()
authorEntry.Hide()
nameEntry.Validator = func(s string) error {
_, err = SanitizeProjectName(s)
if err != nil {
nameValidationLabel.SetText(err.Error())
} else {
nameValidationLabel.SetText("")
}
return err
}
nameEntry.SetOnValidationChanged(func(e error) {
if e != nil {
nameConfirmButton.Disable()
} else {
nameConfirmButton.Enable()
}
})
nameConfirmButton.OnTapped = func() {
_, err = SanitizeProjectName(nameEntry.Text)
if err != nil {
dialog.ShowError(err, window)
nameEntry.SetValidationError(err)
return
}
projectName = nameEntry.Text
nameEntry.Hide()
nameConfirmButton.Hide()
nameValidationLabel.Hide()
authorEntry.Show()
authorConfirmButton.Show()
authorBackButton.Show()
}
authorConfirmButton.OnTapped = func() {
config := KFConfig.NewConfig(projectName, "0.0.1", authorEntry.Text, "Created with KemoForge")
homeDir, err := os.UserHomeDir()
if err != nil {
log.Println("Error getting home directory")
dialog.ShowError(err, window)
return
}
kemoForgeDir := homeDir + "/Documents/KemoForge"
projectInfo.Name = projectName
projectsDir := fyne.CurrentApp().Preferences().StringWithFallback("projectDir", kemoForgeDir+"/projects")
projectPath := projectsDir + "/" + projectName + "/" + projectName + ".KFProject"
projectPath = filepath.Clean(projectPath)
projectInfo.Path = projectPath
projectInfo.OpenDate = time.Now()
newProject := KFProject{
Info: projectInfo,
Config: config,
}
err = newProject.Create(window)
if err != nil {
dialog.ShowError(err, window)
return
}
log.Printf("Created project %s", newProject.Info.Name)
projectDialog.Hide()
err = newProject.Info.Load(window)
if err != nil {
return
}
}
authorBackButton.OnTapped = func() {
authorConfirmButton.Hide()
authorBackButton.Hide()
authorEntry.Hide()
nameEntry.Show()
nameConfirmButton.Show()
nameValidationLabel.Show()
}
projectDialog.Resize(fyne.NewSize(400, 300))
projectDialog.Show()
}

View File

@ -0,0 +1,40 @@
package CustomFunction
import (
"log"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/dialog"
"g.r-io.lu/shynd/kemoforge/pkg/KFData"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFFunction"
)
// Import is a function used to allow importing of the custom function package
// while still allowing the package to be used normally and not be yelled at by the compiler
func Import() {}
func init() {
log.Printf("Registering ExampleFunction")
customFunction := KFFunction.Function{
Name: "ExampleFunction",
Type: "CustomFunction.ExampleFunction",
RequiredArgs: KFData.NewNFInterfaceMap(),
OptionalArgs: KFData.NewNFInterfaceMap(),
}
customFunction.Register(ExampleFunctionHandler)
}
func ExampleFunctionHandler(window fyne.Window, args *KFData.NFInterfaceMap) (*KFData.NFInterfaceMap, error) {
//Get the message from the args
var message string
err := args.Get("message", &message)
if err != nil {
return nil, err
}
//Do something
log.Println("Example button was pressed!")
dialog.ShowInformation("Example", "Example button was pressed!\n Message: "+message, window)
return args, nil
}

View File

@ -0,0 +1,38 @@
package CustomLayout
import (
"log"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"g.r-io.lu/shynd/kemoforge/pkg/KFData"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFLayout"
)
// Import is a function used to allow importing of the custom layout package
// while still allowing the package to be used normally and not be yelled at by the compiler
func Import() {}
func init() {
log.Printf("Registering ExampleLayouts")
customLayout := KFLayout.Layout{
Type: "ExampleLayout",
RequiredArgs: KFData.NewNFInterfaceMap(),
OptionalArgs: KFData.NewNFInterfaceMap(),
}
customLayout.Register(ExampleLayoutHandler)
}
func ExampleLayoutHandler(window fyne.Window, _ *KFData.NFInterfaceMap, l *KFLayout.Layout) (fyne.CanvasObject, error) {
vbox := container.NewVBox()
vbox.Add(widget.NewLabel("Example Layout"))
for _, child := range l.Children {
parsedChild, err := child.Parse(window)
if err != nil {
return nil, err
}
vbox.Add(parsedChild)
}
return vbox, nil
}

View File

@ -0,0 +1,49 @@
package CustomWidget
import (
"log"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/widget"
"g.r-io.lu/shynd/kemoforge/pkg/KFData"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFFunction"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFWidget"
)
// Import is a function used to allow importing of the custom widget package
// while still allowing the package to be used normally and not be yelled at by the compiler
func Import() {}
func init() {
log.Printf("Registering ExampleWidget")
customWidget := KFWidget.Widget{
Type: "ExampleWidget",
RequiredArgs: KFData.NewNFInterfaceMap(
KFData.NewKeyVal("action", ""),
KFData.NewKeyVal("message", ""),
),
OptionalArgs: KFData.NewNFInterfaceMap(),
}
customWidget.Register(ExampleWidgetHandler)
}
func ExampleWidgetHandler(window fyne.Window, args *KFData.NFInterfaceMap, w *KFWidget.Widget) (fyne.CanvasObject, error) {
//Get the action from the args
var action string
err := args.Get("action", &action)
if err != nil {
return nil, err
}
var message string
err = args.Get("message", &message)
if err != nil {
return nil, err
}
button := widget.NewButton("Example Button", func() {
//Do something
_, _ = KFFunction.ParseAndRun(window, action, KFData.NewNFInterfaceMap(KFData.NewKeyVal("message", message)))
})
return button, nil
}

View File

@ -0,0 +1,40 @@
package main
import (
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFFunction"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFFunction/DefaultFunctions"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFLayout"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFLayout/DefaultLayouts"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFWidget"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFWidget/DefaultWidgets"
)
func init() {
DefaultFunctions.Import()
DefaultWidgets.Import()
DefaultLayouts.Import()
//Add some form of function from the asset pack you want to import here
//i.e ExampleAssetPack.Import()
//Otherwise go fmt and some ide's may remove the function.
//Most asset packs should register their assets on init,
//but if they do not you will need to run their registration here to make sure
//They are added to the registered assets before the export is run for all registered.
}
func main() {
KFLayout.ExportRegistered()
KFWidget.ExportRegistered()
KFFunction.ExportRegistered()
/*
!~~~~~~~~~~~IMPORTANT~~~~~~~~~~~~~!
NO NEW EXPORT REGISTERS OR ANY OTHER CODE IS NEEDED HERE
JUST ADD THE IMPORTS AND THEY WILL BE EXPORTED
AS LONG AS THEY WERE REGISTERED CORRECTLY
THIS FILE IS PURELY FOR EDITOR USE AND WILL NOT BE INCLUDED IN THE GAME WHEN IT IS BUILT
RUNNING THIS MAIN FUNCTION EXPORTS THE NEEDED FILES FOR THE EDITOR TO ASSIST YOU WITH
POPULATING THE DATA FOR WIDGETS LAYOUTS AND FUNCTIONS
!~~~~~~~~~~~IMPORTANT~~~~~~~~~~~~~!
*/
}

View File

@ -0,0 +1,14 @@
package game
import (
"embed"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFFS"
)
//go:embed {{.EmbedFiles}}
var embeddedFS embed.FS
func init() {
KFFS.EmbedFS(embeddedFS)
}

View File

@ -0,0 +1,25 @@
package game
import (
"embed"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFFS"
)
// TODO: allow full editing of this file by creating a new file with an init on build that will allow embedding more files
// ---*** you can add any embedded files located within the game directory simply by adding them after the Game.KFConfig file ***---
// ---*** you need to separate each file with a space, not a comma or newline ***---
// ---*** example: Game.KFConfig Game.KFScene Game.KFLayout or even directories like Game.KFConfig data assets ***---
// ---*** the file will be loaded into the embedded filesystem and can be accessed using the KFFS package ***---
//go:embed Game.KFConfig
var gameFS embed.FS
// Import - This needs to be run for the embedded filesystem to work
func Import() {}
func init() {
// set the embedded filesystem to use for loading files
KFFS.EmbedFS(gameFS)
}

View File

@ -0,0 +1,3 @@
{
"hello": "world"
}

View File

@ -0,0 +1,357 @@
package main
import (
"log"
"os"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"g.r-io.lu/shynd/kemoforge/pkg/KFData"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFConfig"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFFS"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFLog"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFFunction"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFFunction/DefaultFunctions"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFLayout/DefaultLayouts"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFScene"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFObjects/KFWidget/DefaultWidgets"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFSave"
GameFS "{{.LocalFileSystem}}"
ExampleFunctions "{{.LocalFunctions}}"
ExampleLayouts "{{.LocalLayouts}}"
ExampleWidgets "{{.LocalWidgets}}"
)
// init is a function that is called when the program starts it runs in order of the deepest import first
//
// init is run BEFORE main in ALL cases and should not be manually called from anywhere in the program
func init() {
//This is the default name for the config, just make sure the file here exists and is in the config format
// ALL KFFS Functions act locally to the game directory, so if you want to access a file in the game directory you can just use the file name
// You CANNOT access files outside the game directory with KFFS functions, however, it can also access all embedded file systems
// If they have been added via the KFFS.EmbedFS function
file, err := KFFS.Open("Game.KFConfig", KFFS.NewConfiguration(true))
if err != nil {
log.Fatal(err)
}
err = KFConfig.Game.Load(file)
//These functions allow specifying which functions, layouts, and widgets are available to the game
//
//Third party packages *should* have their own import functions that are called here to add their functions, layouts, and widgets
//But as longs as they have an init that runs registration and the packages are imported in the main.go file they should work
DefaultFunctions.Import()
DefaultLayouts.Import()
DefaultWidgets.Import()
ExampleFunctions.Import()
ExampleLayouts.Import()
ExampleWidgets.Import()
GameFS.Import()
//Register all Scenes
//This function uses the KFFS functions, so it is ALSO limited to the game directory and the embedded filesystem
//(You do not need to add "game" to the path as it functions relative to the game directory)
err = KFScene.RegisterAll("data/scenes")
if err != nil {
log.Println(err)
}
}
// main is the main function for the game, it is where the game is run from
//
// main is the entry point for the program and is where the program starts running after init
//
// main is run AFTER init in ALL cases and should not be manually called from anywhere in the program
func main() {
//gameApp is the main app for the game to run on, when in a desktop environment this is the window manager that allows multiple windows to be open
// The ID needs to be unique to the game, it is used to store preferences and other things if you overlap with another game, you may have issues with preferences and other things
gameApp := app.NewWithID("com.kemoforge." + KFConfig.Game.Name)
//window is the main window for the game, this is where the game is displayed and scenes are rendered
window := gameApp.NewWindow(KFConfig.Game.Name + " " + KFConfig.Game.Version)
userHome, err := os.UserHomeDir()
if err != nil {
//NFInterface is a custom type that stores any data type mapping it to a string key for easy access there are two methods of declaring it,
// the first is to use the NewNFInterface function like this before running set like you see below
functionArgs := KFData.NewNFInterfaceMap()
functionArgs.Set("Error", "Error Getting User Home Directory: "+err.Error())
/*
//The second method is to declare the key value in the function call like this. Please note that the first method is safer as it will catch any errors thrown by the Set function,
//but this method allows you to declare multiple key value pairs in the function call
functionArgs = KFData.NewNFInterfaceMap(KFData.NewKeyVal("Error", "Error Getting User Home Directory: "+err.Error()))
//An example of setting multiple values at once (You can use the same format in the NewNFInterface function SetMulti is just a method to do it after the initial declaration)
functionArgs.SetMulti(KFData.NewKeyVal("Error", "Error Getting User Home Directory: "+err.Error()), KFData.NewKeyVal("Test", "Test"))
*/
//When calling a function it returns two values, the first is the data returned by the function, the second is an error if there is one, if there is not it will be nil
returnArgs, funcErr := DefaultFunctions.CustomError(window, functionArgs)
if funcErr != nil {
log.Println(err.Error())
}
//to get a value from the arguments you first declare the variable and its type before passing it in to the Get Function as a ref
var message string
//This function also returns an error but as you can see we have declared the err as _ which in go means it is ignored, you can ignore any value by using _
//and it will prevent the compiler from throwing an error if you don't use the value
//You can see this used a lot later throughout the code when we don't care about the return value of a function
//Also as you may have picked up on there are two ways of declaring variables in go, the first is to use := which is shorthand for declaring and assigning a variable to the type of the value
//The second is to use var and then the variable name and type, this is used when you want to declare a variable but not assign it a value
//When using _ to ignore a value, you never declare the value so if all return values are ignored you can just use = function() instead of := function()
//if you are only ignoring one of multiple return values you need to declare the variable or use := when calling the function to assign it, _ variables are not affected by this
_ = returnArgs.Get("TestGetMessage", &message)
}
KFSave.Directory = gameApp.Preferences().StringWithFallback("savesDir", userHome+"/MyGames/"+KFConfig.Game.Name+"/Saves")
KFLog.Directory = gameApp.Preferences().StringWithFallback("logDir", userHome+"/MyGames/"+KFConfig.Game.Name+"/Logs")
err = KFLog.SetUp(window, KFLog.Directory)
if err != nil {
log.Fatal(err)
}
splashScreen := gameApp.Preferences().BoolWithFallback("splashScreen", true)
startupSettings := gameApp.Preferences().BoolWithFallback("startupSettings", true)
if startupSettings {
ShowStartupSettings(window, splashScreen)
} else {
ShowGame(window, "MainMenu", splashScreen)
}
window.ShowAndRun()
}
func createSplashScreen() fyne.Window {
if drv, ok := fyne.CurrentApp().Driver().(desktop.Driver); ok {
splash := drv.CreateSplashWindow()
splash.SetContent(container.NewVBox(
widget.NewLabelWithStyle(KFConfig.Game.Name, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewLabelWithStyle("Version: "+KFConfig.Game.Version, fyne.TextAlignCenter, fyne.TextStyle{Italic: true}),
widget.NewLabelWithStyle("Developed By: "+KFConfig.Game.Author, fyne.TextAlignCenter, fyne.TextStyle{Italic: true}),
widget.NewLabelWithStyle("Powered By: KemoForge and Fyne", fyne.TextAlignCenter, fyne.TextStyle{Italic: true}),
))
return splash
}
return nil
}
func ShowGame(window fyne.Window, scene string, screen bool) {
window.SetMaster()
if screen {
splash := createSplashScreen()
window.Hide()
splash.Show()
time.Sleep(1 * time.Second)
splash.Close()
window.Show()
}
currentApp := fyne.CurrentApp()
window.SetFullScreen(currentApp.Preferences().BoolWithFallback("fullscreen", false))
window.SetContent(container.NewVBox())
window.SetTitle(KFConfig.Game.Name + " " + KFConfig.Game.Version)
window.SetCloseIntercept(func() {
dialog.ShowConfirm("Are you sure you want to quit?", "Are you sure you want to quit?", func(b bool) {
if b {
window.Close()
}
}, window)
})
window.SetFixedSize(false)
window.Resize(fyne.NewSize(800, 600))
window.CenterOnScreen()
window.SetMainMenu(fyne.NewMainMenu(
fyne.NewMenu("Game",
fyne.NewMenuItem("Settings", func() {
dialog.ShowCustomWithoutButtons("Settings", CreateSettings(false, window), window)
}),
fyne.NewMenuItem("New", func() {
functionArgs := KFData.NewNFInterfaceMap()
_, _ = DefaultFunctions.NewGame(window, functionArgs)
}),
fyne.NewMenuItem("Load", func() {
functionArgs := KFData.NewNFInterfaceMap()
_, _ = DefaultFunctions.LoadGame(window, functionArgs)
}),
fyne.NewMenuItem("Save", func() {
err := KFSave.Active.Save()
if err != nil {
functionArgs := KFData.NewNFInterfaceMap()
functionArgs.Set("Error", "Error Saving Game: "+err.Error())
_, _ = DefaultFunctions.CustomError(window, functionArgs)
return
}
}),
fyne.NewMenuItem("Save As", func() {
functionArgs := KFData.NewNFInterfaceMap()
_, _ = DefaultFunctions.SaveAs(window, functionArgs)
}),
fyne.NewMenuItem("Quit", func() {
functionArgs := KFData.NewNFInterfaceMap()
_, _ = DefaultFunctions.Quit(window, functionArgs)
}),
),
fyne.NewMenu("View",
fyne.NewMenuItem("Fullscreen", func() {
window.SetFullScreen(!window.FullScreen())
//Set the app preferences to the current fullscreen state for next time
gameApp := fyne.CurrentApp()
gameApp.Preferences().SetBool("fullscreen", window.FullScreen())
}),
fyne.NewMenuItem("Console", func() {
KFLog.ShowDialog(window)
}),
),
))
if len(KFScene.SceneMap) == 0 {
//There are actually two ways to call a function, the first is the way we have been doing it so far, the second is to parse it as it happens in the scene parser
//This parsing method looks for the function by name in our loaded functions and then calls it with the arguments passed to it
functionArgs := KFData.NewNFInterfaceMap()
functionArgs.Set("Error", "No Scenes Found")
_, _ = KFFunction.ParseAndRun(window, "Error", functionArgs) // This Error function is just DefaultFunctions.CustomError, this is how scenes can store functions in their data files
return
}
startupScene, err := KFScene.Get(scene)
if err != nil {
functionArgs := KFData.NewNFInterfaceMap()
functionArgs.Set("Error", "Error Getting Scene: "+err.Error())
_, _ = DefaultFunctions.CustomError(window, functionArgs)
return
}
//SceneParser parses the scene and returns a fyne.CanvasObject that can be added to the window
sceneObject, err := startupScene.Parse(window)
if err != nil {
functionArgs := KFData.NewNFInterfaceMap()
functionArgs.Set("Error", "Error Parsing Scene: "+err.Error())
_, _ = DefaultFunctions.CustomError(window, functionArgs)
return
}
window.SetContent(sceneObject)
}
func ShowStartupSettings(window fyne.Window, splashScreen bool) {
settingsBox := CreateSettings(true, window)
var creditsModal *widget.PopUp
creditsCloseButton := widget.NewButton("Close", func() {
creditsModal.Hide()
})
creditsModal = widget.NewModalPopUp(
container.NewVBox(
widget.NewLabelWithStyle("Credits", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewLabelWithStyle(KFConfig.Game.Credits, fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
creditsCloseButton,
),
window.Canvas(),
)
creditsButton := widget.NewButton("Credits", func() {
creditsModal.Show()
})
startButton := widget.NewButton("Start Game", func() {
go ShowGame(window, "MainMenu", splashScreen)
})
settingsBox.(*fyne.Container).Add(container.NewVBox(creditsButton, startButton))
window.SetFixedSize(true)
window.Resize(fyne.NewSize(300, 400))
window.CenterOnScreen()
window.SetContent(settingsBox)
window.SetTitle("Startup Settings")
}
func CreateSettings(isStartup bool, window fyne.Window) fyne.CanvasObject {
currentApp := fyne.CurrentApp()
fullScreenBinding := binding.NewBool()
err := fullScreenBinding.Set(currentApp.Preferences().BoolWithFallback("fullscreen", false))
if err != nil {
panic(err)
}
fullScreenBinding.AddListener(binding.NewDataListener(func() {
//Set the app preferences to the current fullscreen state for next time
fullscreen, err := fullScreenBinding.Get()
if err != nil {
panic(err)
}
currentApp.Preferences().SetBool("fullscreen", fullscreen)
if !isStartup {
window.SetFullScreen(fullscreen)
}
}))
splashScreenBinding := binding.NewBool()
err = splashScreenBinding.Set(currentApp.Preferences().BoolWithFallback("splashScreen", true))
if err != nil {
panic(err)
}
splashScreenBinding.AddListener(binding.NewDataListener(func() {
//Set the app preferences to the current fullscreen state for next time
splashScreen, err := splashScreenBinding.Get()
if err != nil {
panic(err)
}
currentApp.Preferences().SetBool("splashScreen", splashScreen)
}))
startUpSettingBinding := binding.NewBool()
err = startUpSettingBinding.Set(currentApp.Preferences().BoolWithFallback("startupSettings", true))
if err != nil {
panic(err)
}
startUpSettingBinding.AddListener(binding.NewDataListener(func() {
//Set the app preferences to the current fullscreen state for next time
startUpSetting, err := startUpSettingBinding.Get()
if err != nil {
panic(err)
}
currentApp.Preferences().SetBool("startupSettings", startUpSetting)
}))
musicBinding := binding.NewFloat()
err = musicBinding.Set(currentApp.Preferences().FloatWithFallback("musicVolume", 100))
if err != nil {
panic(err)
}
musicBinding.AddListener(binding.NewDataListener(func() {
//Set the app preferences to the current fullscreen state for next time
musicVolume, err := musicBinding.Get()
if err != nil {
panic(err)
}
currentApp.Preferences().SetFloat("musicVolume", musicVolume)
//TODO Add the audio manager and set the music volume
}))
effectsBinding := binding.NewFloat()
err = effectsBinding.Set(currentApp.Preferences().FloatWithFallback("effectsVolume", 100))
if err != nil {
panic(err)
}
effectsBinding.AddListener(binding.NewDataListener(func() {
//Set the app preferences to the current fullscreen state for next time
effectsVolume, err := effectsBinding.Get()
if err != nil {
panic(err)
}
currentApp.Preferences().SetFloat("effectsVolume", effectsVolume)
//TODO Add the audio manager and set the effects volume
}))
SettingsScrollBox := container.NewScroll(
widget.NewForm(
widget.NewFormItem("Music Volume", widget.NewSlider(0, 100)),
widget.NewFormItem("Effects Volume", widget.NewSlider(0, 100)),
widget.NewFormItem("Fullscreen", widget.NewCheckWithData("", fullScreenBinding)),
widget.NewFormItem("Show Settings on Startup", widget.NewCheckWithData("", startUpSettingBinding)),
widget.NewFormItem("Show Splash Screen on Startup", widget.NewCheckWithData("", splashScreenBinding)),
),
)
SettingsScrollBox.SetMinSize(fyne.NewSize(300, 50))
settingsBox := container.NewVBox(
widget.NewLabelWithStyle("Startup Settings", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
widget.NewLabelWithStyle(KFConfig.Game.Name, fyne.TextAlignCenter, fyne.TextStyle{Italic: true}),
widget.NewLabelWithStyle("Version: "+KFConfig.Game.Version, fyne.TextAlignLeading, fyne.TextStyle{Italic: true}),
widget.NewLabelWithStyle("Author: "+KFConfig.Game.Author, fyne.TextAlignLeading, fyne.TextStyle{Italic: true}),
widget.NewLabelWithStyle("Game Settings", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
SettingsScrollBox,
layout.NewSpacer(),
)
return settingsBox
}

View File

@ -0,0 +1,73 @@
{
"Name": "MainMenu",
"Layout": {
"Type": "VBox",
"Widgets": [
{
"ID": "MainMenu.Label#1",
"Type": "Label",
"Children": null,
"Args": {
"Data": {
"Text": "Main Menu"
}
}
},
{
"ID": "MainMenu.Button#1",
"Type": "Button",
"Children": null,
"Args": {
"Data": {
"OnTapped": "NewGame",
"OnTappedArgs": {
"Data": {
"NewGameScene": "NewGame"
}
},
"Text": "New Game"
}
}
},
{
"ID": "MainMenu.Button#2",
"Type": "Button",
"Children": null,
"Args": {
"Data": {
"OnTapped": "LoadGame",
"Text": "Load Game"
}
}
},
{
"ID": "MainMenu.Button#3",
"Type": "Button",
"Children": null,
"Args": {
"Data": {
"OnTapped": "Settings",
"Text": "Settings"
}
}
},
{
"ID": "MainMenu.Button#4",
"Type": "Button",
"Children": null,
"Args": {
"Data": {
"OnTapped": "Quit",
"Text": "Quit"
}
}
}
],
"Args": {
"Data": {}
}
},
"Args": {
"Data": {}
}
}

View File

@ -0,0 +1,25 @@
{
"Name": "NewGame",
"Layout": {
"Type": "ExampleLayout",
"Widgets": [
{
"ID": "NewGame.ExampleWidget#1",
"Type": "ExampleWidget",
"Children": null,
"Args": {
"Data": {
"action": "CustomFunction.ExampleFunction",
"message": "Hello World"
}
}
}
],
"Args": {
"Data": {}
}
},
"Args": {
"Data": {}
}
}

View File

@ -0,0 +1,84 @@
package KFConfig
import (
"encoding/json"
"io"
"io/fs"
"os"
"g.r-io.lu/shynd/kemoforge/pkg/KFData/KFError"
)
// Game is the config for the currently running game if any
var Game = NewBlankConfig()
// KFConfig is the struct that holds all the information about a config
type KFConfig struct {
// Name of the project
Name string `json:"Name"`
// Author of the project
Author string `json:"Author"`
// Version of the project
Version string `json:"Version"`
// Credits for the project
Credits string `json:"Credits"`
// Webpage for the project
Webpage string `json:"Webpage"`
// Icon for the project
Icon string `json:"Icon"`
// EncryptionKey for the project
EncryptionKey string `json:"EncryptionKey"`
}
// NewConfig creates a new config with given name, author, version and credits
func NewConfig(name, version, author, credits string) *KFConfig {
return &KFConfig{
Name: name,
Version: version,
Author: author,
Credits: credits,
}
}
// NewBlankConfig creates a new blank config
func NewBlankConfig() *KFConfig {
return &KFConfig{}
}
// Load loads the config from the given file
func (c *KFConfig) Load(file fs.File) error {
defer file.Close()
// read the file
data, err := io.ReadAll(file)
if err != nil {
return KFError.NewErrConfigLoad(err.Error())
}
// unmarshal the data
err = json.Unmarshal(data, c)
if err != nil {
return KFError.NewErrConfigLoad(err.Error())
}
return nil
}
// Save saves the config to the given file
//
// this is an editor only function and should not be used in the final game
func (c *KFConfig) Save(path string) error {
//Marshal the data
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return KFError.NewErrConfigSave(err.Error())
}
//Write the data to the file
err = os.WriteFile(path, data, os.ModePerm)
if err != nil {
return KFError.NewErrConfigSave(err.Error())
}
return nil
}

View File

@ -0,0 +1,92 @@
package KFError
import (
"errors"
"fmt"
"github.com/google/uuid"
)
var (
ErrConfigLoad = errors.New("error loading config")
ErrConfigSave = errors.New("error saving config")
ErrFileGet = errors.New("error getting file")
ErrNoProjects = errors.New("no projects found")
ErrInvalidArgument = errors.New("invalid argument")
ErrKeyAlreadyExists = errors.New("key already exists")
ErrMissingArgument = errors.New("missing argument")
ErrTypeMismatch = errors.New("type mismatch")
ErrKeyNotFound = errors.New("key not found")
ErrNotImplemented = errors.New("not implemented")
ErrProjectNotFound = errors.New("project not found")
ErrProjectAlreadyExists = errors.New("project already exists")
ErrSceneSave = errors.New("error saving scene")
ErrSceneValidation = errors.New("scene validation failed")
ErrCriticalSceneValidation = errors.New("critical scene validation failure")
ErrNotFound = errors.New("not found")
ErrWidgetParse = errors.New("error parsing widget")
)
func NewErrInvalidArgument(arg, reason string) error {
return fmt.Errorf("%w: %s: %s", ErrInvalidArgument, arg, reason)
}
func NewErrKeyAlreadyExists(key string) error {
return fmt.Errorf("%w: %s", ErrKeyAlreadyExists, key)
}
func NewErrMissingArgument(interfaceName string, key string) error {
return fmt.Errorf("%w: interface %s missing argument: %s", ErrMissingArgument, interfaceName, key)
}
func NewErrTypeMismatch(expected, actual string) error {
return fmt.Errorf("%w: type %s does not match type %s", ErrTypeMismatch, expected, actual)
}
func NewErrKeyNotFound(key string) error {
return fmt.Errorf("%w: %s", ErrKeyNotFound, key)
}
func NewErrNotImplemented(method string) error {
return fmt.Errorf("%w: %s", ErrNotImplemented, method)
}
func NewErrProjectNotFound(project string) error {
return fmt.Errorf("%w: %s", ErrProjectNotFound, project)
}
func NewErrProjectAlreadyExists(project string) error {
return fmt.Errorf("%w: %s", ErrProjectAlreadyExists, project)
}
func NewErrFileGet(file, reason string) error {
return fmt.Errorf("%w: %s: %s", ErrFileGet, file, reason)
}
func NewErrConfigLoad(reason string) error {
return fmt.Errorf("%w: %s", ErrConfigLoad, reason)
}
func NewErrConfigSave(s string) error {
return fmt.Errorf("%w: %s", ErrConfigSave, s)
}
func NewErrSceneValidation(s string) error {
return fmt.Errorf("%w: %s", ErrSceneValidation, s)
}
func NewErrCriticalSceneValidation(s string) error {
return fmt.Errorf("%w: %s", ErrCriticalSceneValidation, s)
}
func NewErrSceneSave(s string) error {
return fmt.Errorf("%w: %s", ErrSceneSave, s)
}
func NewErrNotFound(s string) error {
return fmt.Errorf("%w: %s", ErrNotFound, s)
}
func NewErrWidgetParse(widgetName, widgetType string, widgetUUID uuid.UUID, reason string) error {
return fmt.Errorf("%w: %s of type %s with UUID %v: %s", ErrWidgetParse, widgetName, widgetType, widgetUUID, reason)
}

202
pkg/KFData/KFLog/KFLog.go Normal file
View File

@ -0,0 +1,202 @@
package KFLog
import (
"io"
"log"
"os"
"strings"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
var Directory = ""
var LogList *widget.List
var LogDialog *dialog.CustomDialog
type entry struct {
Date string
Time string
File string
Line string
Message string
}
var entries []entry
type writer chan<- string
func (c writer) Write(p []byte) (n int, err error) {
c <- string(p)
return len(p), nil
}
// ShowDialog shows the log dialog
func ShowDialog(window fyne.Window) {
// if the log dialog is nil, create it
if LogDialog == nil {
err := Setup(window)
if err != nil {
return
}
}
LogDialog.Show()
}
// GetDirectory returns the directory that the log files are stored in
func GetDirectory() string {
return Directory
}
// SetDirectory sets the directory that the log files are stored in and creates it if it doesn't exist
func SetDirectory(dir string) {
// make sure it ends in a slash and only one slash
if !strings.HasSuffix(dir, "/") {
dir += "/"
}
// check if the directory exists
if _, err := os.Stat(dir); os.IsNotExist(err) {
// create the directory
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
panic(err)
}
}
Directory = dir
}
func Setup(window fyne.Window, dir ...string) error {
if len(dir) > 0 {
SetDirectory(dir[0])
} else {
SetDirectory("logs/")
}
// check if a log.txt file already exists
fi, err := os.Stat(Directory + "log.txt")
if err != nil {
// create a new log.txt file
_, err = os.Create(Directory + "log.txt")
if err != nil {
return err
}
}
// copy the new log file to one named with its creation date
if fi != nil {
err = os.Rename(Directory+"log.txt", Directory+fi.ModTime().Format("2006-01-02-1504")+".txt")
if err != nil {
panic(err)
}
// create a new log.txt file
_, err = os.Create(Directory + "log.txt")
if err != nil {
panic(err)
}
}
// open the log file for writing
logFile, err := os.OpenFile(Directory+"log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return err
}
logChannel := make(chan string)
channelWriter := writer(logChannel)
multi := io.MultiWriter(os.Stdout, logFile, channelWriter)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.SetOutput(multi)
LogList = widget.NewList(
func() int {
return len(entries)
},
func() fyne.CanvasObject {
return container.NewHBox(
widget.NewButtonWithIcon("", theme.ContentCopyIcon(), func() {}),
widget.NewLabel("Template List Item"),
)
},
func(i widget.ListItemID, o fyne.CanvasObject) {
// create a message with everything but the date and time
message := entries[i].File + ":" + entries[i].Line + " | " + entries[i].Message
dateTime := entries[i].Date + "-" + entries[i].Time
// set the button to copy the message to the clipboard
o.(*fyne.Container).Objects[0].(*widget.Button).OnTapped = func() {
window.Clipboard().SetContent(dateTime + " | " + entries[i].File + ":" + entries[i].Line + " | " + entries[i].Message)
}
// set the label to the message
o.(*fyne.Container).Objects[1].(*widget.Label).SetText(message)
// set the button text to the date and time
o.(*fyne.Container).Objects[0].(*widget.Button).SetText(dateTime)
},
)
LogDialog = dialog.NewCustom("Log", "Close", LogList, window)
// listen for a Escape keypress, hide dialog on fired event
window.Canvas().SetOnTypedKey(func(key *fyne.KeyEvent) {
if key.Name == fyne.KeyEscape {
LogDialog.Hide()
}
})
go func() {
for {
if LogDialog != nil {
// get the size of the screen
size := window.Canvas().Size()
// set the size of the dialog to 80% of the screen
LogDialog.Resize(fyne.NewSize(size.Width*0.8, size.Height*0.8))
}
time.Sleep(time.Millisecond)
}
}()
go func() {
for logEntry := range logChannel {
e := parseLogEntry(logEntry)
entries = append(entries, e)
if LogDialog != nil {
LogDialog.Refresh()
}
}
}()
return nil
}
func parseLogEntry(logEntry string) entry {
// split the log entry into its parts based on spaces
parts := strings.SplitN(logEntry, " ", 4)
// create a new entry
e := entry{}
// set the date and time
e.Date = parts[0]
e.Time = parts[1]
// further split the "file:line" into file and line
fileAndLine := strings.Split(parts[2], ":")
if len(fileAndLine) >= 2 {
e.File = fileAndLine[0]
e.Line = fileAndLine[1]
} else {
e.File = parts[2] // if it doesn't split, use it as is
e.Line = "unknown" // we couldn't determine the line number
}
// set the message
e.Message = parts[3]
return e
}

View File

@ -0,0 +1,84 @@
package CalsWidgets
import (
"log"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/widget"
)
type Loading struct {
progress binding.Float
complete chan struct{}
status binding.String
timeToSleep time.Duration
bar *widget.ProgressBar
label *widget.Label
Box fyne.CanvasObject
}
func NewLoading(loadingChan chan struct{}, timeToSleep time.Duration, minMax ...float64) *Loading {
loading := &Loading{
progress: binding.NewFloat(),
complete: loadingChan,
status: binding.NewString(),
}
loading.bar = widget.NewProgressBarWithData(loading.progress)
loading.label = widget.NewLabelWithData(loading.status)
loading.label.Alignment = fyne.TextAlignCenter
loading.label.TextStyle = fyne.TextStyle{Bold: true, Italic: true}
loading.Box = container.NewVBox(loading.label, loading.bar)
loading.timeToSleep = timeToSleep
switch len(minMax) {
case 1:
loading.bar.Min = 0
loading.bar.Max = minMax[0]
case 2:
loading.bar.Min = minMax[0]
loading.bar.Max = minMax[1]
default:
loading.bar.Min = 0
loading.bar.Max = 1
}
return loading
}
func (l *Loading) SetProgress(progress float64, status ...string) {
err := l.progress.Set(progress)
if err != nil {
log.Println(err)
}
if len(status) > 0 {
err = l.status.Set(status[0])
if err != nil {
log.Println(err)
}
}
log.Println(status)
time.Sleep(l.timeToSleep)
}
func (l *Loading) BindProgress() binding.Float {
return l.progress
}
func (l *Loading) BindStatus() binding.String {
return l.status
}
func (l *Loading) SetStatus(status string) {
err := l.status.Set(status)
if err != nil {
log.Println(err)
} else {
log.Println(status)
}
}
func (l *Loading) Complete() {
time.Sleep(1 * time.Second)
l.complete <- struct{}{}
l.SetStatus("Complete")
}
func (l *Loading) GetProgress() float64 {
return l.bar.Value
}