From 0ca987ca2ec23074505b9b8001a6c89475139d9d Mon Sep 17 00:00:00 2001 From: shynd Date: Wed, 26 Jun 2024 03:08:19 +0200 Subject: [PATCH] init commit --- .gitignore | 38 + README.md | 1 + cmd/KemoForge/main.go | 64 ++ go.mod | 36 + go.sum | 663 ++++++++++++++++++ internal/KFEditor/EditorProject.go | 527 ++++++++++++++ internal/KFEditor/EditorValidation.go | 60 ++ internal/KFEditor/EditorWindow.go | 459 ++++++++++++ .../CustomFunction/CustomFunction.got | 40 ++ .../Templates/CustomLayout/CustomLayout.got | 38 + .../Templates/CustomWidget/CustomWidget.got | 49 ++ .../Templates/EditorAssets/EditorAssets.go | 40 ++ .../KFEditor/Templates/Embedding/Embed.got | 14 + .../Templates/FileLoader/FileLoader.got | 25 + .../Templates/FileLoader/Game.KFConfig | 3 + .../KFEditor/Templates/MainGame/MainGame.got | 357 ++++++++++ .../Templates/Scenes/MainMenu.KFScene | 73 ++ .../KFEditor/Templates/Scenes/NewGame.KFScene | 25 + pkg/KFData/KFConfig/KFConfig.go | 84 +++ pkg/KFData/KFError/KFError.go | 92 +++ pkg/KFData/KFLog/KFLog.go | 202 ++++++ .../KFWidget/CalsWidgets/LoadingBar.go | 84 +++ 22 files changed, 2974 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cmd/KemoForge/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/KFEditor/EditorProject.go create mode 100644 internal/KFEditor/EditorValidation.go create mode 100644 internal/KFEditor/EditorWindow.go create mode 100644 internal/KFEditor/Templates/CustomFunction/CustomFunction.got create mode 100644 internal/KFEditor/Templates/CustomLayout/CustomLayout.got create mode 100644 internal/KFEditor/Templates/CustomWidget/CustomWidget.got create mode 100644 internal/KFEditor/Templates/EditorAssets/EditorAssets.go create mode 100644 internal/KFEditor/Templates/Embedding/Embed.got create mode 100644 internal/KFEditor/Templates/FileLoader/FileLoader.got create mode 100644 internal/KFEditor/Templates/FileLoader/Game.KFConfig create mode 100644 internal/KFEditor/Templates/MainGame/MainGame.got create mode 100644 internal/KFEditor/Templates/Scenes/MainMenu.KFScene create mode 100644 internal/KFEditor/Templates/Scenes/NewGame.KFScene create mode 100644 pkg/KFData/KFConfig/KFConfig.go create mode 100644 pkg/KFData/KFError/KFError.go create mode 100644 pkg/KFData/KFLog/KFLog.go create mode 100644 pkg/KFData/KFObjects/KFWidget/CalsWidgets/LoadingBar.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0a0605 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..81a9f9d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# kemoforge \ No newline at end of file diff --git a/cmd/KemoForge/main.go b/cmd/KemoForge/main.go new file mode 100644 index 0000000..70c4d1e --- /dev/null +++ b/cmd/KemoForge/main.go @@ -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() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..72c5ab9 --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bca4bf1 --- /dev/null +++ b/go.sum @@ -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= diff --git a/internal/KFEditor/EditorProject.go b/internal/KFEditor/EditorProject.go new file mode 100644 index 0000000..29da842 --- /dev/null +++ b/internal/KFEditor/EditorProject.go @@ -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 +} diff --git a/internal/KFEditor/EditorValidation.go b/internal/KFEditor/EditorValidation.go new file mode 100644 index 0000000..adbb2a5 --- /dev/null +++ b/internal/KFEditor/EditorValidation.go @@ -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... + } +} diff --git a/internal/KFEditor/EditorWindow.go b/internal/KFEditor/EditorWindow.go new file mode 100644 index 0000000..be7b75f --- /dev/null +++ b/internal/KFEditor/EditorWindow.go @@ -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() +} diff --git a/internal/KFEditor/Templates/CustomFunction/CustomFunction.got b/internal/KFEditor/Templates/CustomFunction/CustomFunction.got new file mode 100644 index 0000000..2000ed8 --- /dev/null +++ b/internal/KFEditor/Templates/CustomFunction/CustomFunction.got @@ -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 +} diff --git a/internal/KFEditor/Templates/CustomLayout/CustomLayout.got b/internal/KFEditor/Templates/CustomLayout/CustomLayout.got new file mode 100644 index 0000000..95bda2a --- /dev/null +++ b/internal/KFEditor/Templates/CustomLayout/CustomLayout.got @@ -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 +} diff --git a/internal/KFEditor/Templates/CustomWidget/CustomWidget.got b/internal/KFEditor/Templates/CustomWidget/CustomWidget.got new file mode 100644 index 0000000..5041cd9 --- /dev/null +++ b/internal/KFEditor/Templates/CustomWidget/CustomWidget.got @@ -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 +} diff --git a/internal/KFEditor/Templates/EditorAssets/EditorAssets.go b/internal/KFEditor/Templates/EditorAssets/EditorAssets.go new file mode 100644 index 0000000..b6dae7c --- /dev/null +++ b/internal/KFEditor/Templates/EditorAssets/EditorAssets.go @@ -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~~~~~~~~~~~~~! + */ +} diff --git a/internal/KFEditor/Templates/Embedding/Embed.got b/internal/KFEditor/Templates/Embedding/Embed.got new file mode 100644 index 0000000..16f16c0 --- /dev/null +++ b/internal/KFEditor/Templates/Embedding/Embed.got @@ -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) +} diff --git a/internal/KFEditor/Templates/FileLoader/FileLoader.got b/internal/KFEditor/Templates/FileLoader/FileLoader.got new file mode 100644 index 0000000..76b327f --- /dev/null +++ b/internal/KFEditor/Templates/FileLoader/FileLoader.got @@ -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) +} diff --git a/internal/KFEditor/Templates/FileLoader/Game.KFConfig b/internal/KFEditor/Templates/FileLoader/Game.KFConfig new file mode 100644 index 0000000..d02fab0 --- /dev/null +++ b/internal/KFEditor/Templates/FileLoader/Game.KFConfig @@ -0,0 +1,3 @@ +{ + "hello": "world" +} \ No newline at end of file diff --git a/internal/KFEditor/Templates/MainGame/MainGame.got b/internal/KFEditor/Templates/MainGame/MainGame.got new file mode 100644 index 0000000..53df8d3 --- /dev/null +++ b/internal/KFEditor/Templates/MainGame/MainGame.got @@ -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 +} diff --git a/internal/KFEditor/Templates/Scenes/MainMenu.KFScene b/internal/KFEditor/Templates/Scenes/MainMenu.KFScene new file mode 100644 index 0000000..d3b7574 --- /dev/null +++ b/internal/KFEditor/Templates/Scenes/MainMenu.KFScene @@ -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": {} + } +} \ No newline at end of file diff --git a/internal/KFEditor/Templates/Scenes/NewGame.KFScene b/internal/KFEditor/Templates/Scenes/NewGame.KFScene new file mode 100644 index 0000000..d7bdbc7 --- /dev/null +++ b/internal/KFEditor/Templates/Scenes/NewGame.KFScene @@ -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": {} + } +} \ No newline at end of file diff --git a/pkg/KFData/KFConfig/KFConfig.go b/pkg/KFData/KFConfig/KFConfig.go new file mode 100644 index 0000000..fa2c543 --- /dev/null +++ b/pkg/KFData/KFConfig/KFConfig.go @@ -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 +} diff --git a/pkg/KFData/KFError/KFError.go b/pkg/KFData/KFError/KFError.go new file mode 100644 index 0000000..19ed7d8 --- /dev/null +++ b/pkg/KFData/KFError/KFError.go @@ -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) +} diff --git a/pkg/KFData/KFLog/KFLog.go b/pkg/KFData/KFLog/KFLog.go new file mode 100644 index 0000000..823f1fc --- /dev/null +++ b/pkg/KFData/KFLog/KFLog.go @@ -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 +} diff --git a/pkg/KFData/KFObjects/KFWidget/CalsWidgets/LoadingBar.go b/pkg/KFData/KFObjects/KFWidget/CalsWidgets/LoadingBar.go new file mode 100644 index 0000000..22aaee2 --- /dev/null +++ b/pkg/KFData/KFObjects/KFWidget/CalsWidgets/LoadingBar.go @@ -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 +}