Compare commits

...

137 commits
v0.0.2 ... main

Author SHA1 Message Date
90299a1405 fix(deps): update github.com/gomarkdown/markdown digest to a660076
Some checks failed
Go build / build (pull_request) Failing after 24m41s
Go build / build (push) Successful in 8m9s
2023-11-16 21:11:13 +00:00
95520b4308 fix(deps): update github.com/gomarkdown/markdown digest to 5421fef
All checks were successful
Go build / build (pull_request) Successful in 3m12s
Go build / build (push) Successful in 3m13s
2023-11-14 21:51:52 +00:00
4a94a6a7a5 fix(deps): update module github.com/fsnotify/fsnotify to v1.7.0
All checks were successful
Go build / build (pull_request) Successful in 3m7s
Go build / build (push) Successful in 2m22s
2023-11-14 21:46:10 +00:00
2055dcf0e1 fix(deps): update module github.com/google/uuid to v1.4.0
Some checks reported warnings
Go build / build (pull_request) Successful in 3m5s
Go build / build (push) Has been cancelled
2023-11-14 21:41:24 +00:00
79342350b2
ci: onboard Gitea actions
All checks were successful
continuous-integration/drone/push Build is passing
Go build / build (push) Successful in 5m51s
2023-11-12 11:29:42 +01:00
9da1f15533 fix(deps): update module github.com/spf13/viper to v1.17.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-05 22:30:03 +00:00
9889cc33de fix(deps): update module github.com/spf13/cobra to v1.8.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-05 00:11:07 +00:00
7fc6ac7cb9 fix(deps): update github.com/gomarkdown/markdown digest to 7478c23
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-09-17 00:30:18 +00:00
72ae56d636 chore(deps): update docker.io/golang docker tag to v1.21
All checks were successful
continuous-integration/drone/push Build is passing
2023-09-16 16:59:40 +00:00
a2e9523d48 fix(deps): update module github.com/google/uuid to v1.3.1
Some checks are pending
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is running
2023-09-15 12:10:45 +00:00
e971ec9bc3 fix(deps): update module github.com/sirupsen/logrus to v1.9.3
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-18 19:48:00 +00:00
78ee048537 fix(deps): update module github.com/spf13/viper to v1.16.0
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-07-18 19:42:29 +00:00
a7aeb323e2 fix(deps): update github.com/gomarkdown/markdown digest to 531d2d7
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-18 19:41:27 +00:00
bc4ef87627 fix(ci): run only on push to main
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-07-18 19:39:26 +00:00
232e0bffee refactor(ci): use arm64 platform by default
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-18 19:27:34 +00:00
33c894c820 fix(deps): update module github.com/spf13/cobra to v1.7.0
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
2023-04-05 00:23:46 +00:00
3398effbf0 fix(deps): update github.com/gomarkdown/markdown digest to c84983b
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-03-23 00:24:23 +00:00
2af8215778
fix(ci): make sure Git LFS is pulled
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build is passing
2023-03-02 12:09:39 +01:00
1d203dc5c2
fix(release): fetch tags
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build is failing
2023-03-01 21:17:08 +01:00
eb0e3f5c6c
fix(release): update Gitea config
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/tag Build is failing
2023-03-01 20:10:36 +01:00
efef50e89d
feat(ci): use cache volume
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing
2023-02-28 22:07:33 +01:00
f4509111b1
chore: upgrade to Go 1.20
Some checks are pending
continuous-integration/drone/push Build is running
2023-02-28 22:04:23 +01:00
a3f2445762
chore(ci): remove concourse config 2023-02-28 22:04:23 +01:00
80ffbb17a8
feat(ci): prepare release 2023-02-28 22:04:15 +01:00
50f7dc1c48
feat(ci): move to Drone 2023-02-28 21:12:24 +01:00
5af982cf25 fix(deps): update module github.com/spf13/viper to v1.15.0
All checks were successful
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/test/gotestsum Run unit tests
2023-01-20 12:48:41 +00:00
5d19995e99 fix(deps): update module github.com/spf13/cobra to v1.6.1
All checks were successful
concourse-ci/golangci-lint golangci-lint run
concourse-ci/gotestsum gotestsum
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/test/gotestsum Run unit tests
2023-01-01 15:47:27 +00:00
05ebc5f3ed fix(deps): update module github.com/spf13/viper to v1.14.0
All checks were successful
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/test/gotestsum Run unit tests
concourse-ci/gotestsum gotestsum
concourse-ci/golangci-lint golangci-lint run
2023-01-01 15:44:38 +00:00
1d2546cd78 fix(deps): update module go.uber.org/multierr to v1.9.0
All checks were successful
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/test/gotestsum Run unit tests
concourse-ci/gotestsum gotestsum
concourse-ci/golangci-lint golangci-lint run
2023-01-01 14:23:09 +00:00
391c236b23 fix(deps): update module github.com/fsnotify/fsnotify to v1.6.0
All checks were successful
concourse-ci/test/gotestsum Run unit tests
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/gotestsum gotestsum
concourse-ci/golangci-lint golangci-lint run
2023-01-01 13:34:43 +00:00
75aee1eb8d fix(deps): update github.com/gomarkdown/markdown digest to 663e250
All checks were successful
concourse-ci/test/gotestsum Run unit tests
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/golangci-lint golangci-lint run
concourse-ci/gotestsum gotestsum
2023-01-01 13:04:47 +00:00
e6e08b7b95
fix(deps): update module github.com/masterminds/sprig/v3 to v3.2.3
All checks were successful
concourse-ci/gotestsum gotestsum
concourse-ci/golangci-lint golangci-lint run
concourse-ci/lint/golangci-lint Lint Go files
concourse-ci/test/gotestsum Run unit tests
2023-01-01 13:54:08 +01:00
03a9ec64ab
fix(ci): execute tests with task
All checks were successful
concourse-ci/golangci-lint golangci-lint run
concourse-ci/gotestsum gotestsum
2023-01-01 13:53:55 +01:00
c05c301dfd
fix(ci): test image
All checks were successful
concourse-ci/golangci-lint golangci-lint run
concourse-ci/gotestsum gotestsum
2023-01-01 13:50:40 +01:00
d4d2cbd586
feat(ci): prepare concourse pipelines
All checks were successful
concourse-ci/golangci-lint golangci-lint run
concourse-ci/gotestsum gotestsum
2023-01-01 13:48:50 +01:00
51b6ffbff3
chore(ci): remove Agola config
All checks were successful
concourse-ci/golangci-lint golangci-lint run
concourse-ci/gotestsum gotestsum
2022-11-16 22:15:18 +01:00
Peter Kurfer
15045103ec
chore(deps): upgrade peer deps
All checks were successful
agola/goveal/Test and lint The run finished successfully
concourse-ci golangci-lint
concourse-ci/golangci-lint golangci-lint
2022-09-25 14:26:12 +02:00
Peter Kurfer
38471371c9
refactor: migrate to Gitea server
All checks were successful
agola/goveal/Test and lint The run finished successfully
2022-09-25 14:23:46 +02:00
renovate[bot]
2538fd2e5c Update module github.com/spf13/viper to v1.13.0
All checks were successful
agola/goveal/Test and lint The run finished successfully
2022-09-22 20:53:22 +00:00
Peter Kurfer
64f37ec224
chore(ci): add Agola config
All checks were successful
agola/goveal/Test and lint The run finished successfully
2022-09-22 22:08:51 +02:00
662e5da818
Merge pull request #34 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 7b278df
2022-09-08 16:32:25 +02:00
renovate[bot]
3a8ebdacf4
Update github.com/gomarkdown/markdown digest to 7b278df 2022-09-05 20:07:25 +00:00
82fd493b43
chore: update to Go 1.19 2022-08-31 18:42:38 +02:00
3f5d97b0c7
Merge pull request #33 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 01a3c37
2022-08-31 18:41:10 +02:00
renovate[bot]
978ae2ae1f
Update github.com/gomarkdown/markdown digest to 01a3c37 2022-08-30 03:42:25 +00:00
dbd867da25
Merge pull request #31 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 2206187
2022-08-18 18:34:01 +02:00
renovate[bot]
115af8d657
Update github.com/gomarkdown/markdown digest to 2206187 2022-08-18 02:00:07 +00:00
c02b482b98
Merge pull request #28 from baez90/renovate/github.com-spf13-cobra-1.x
Update module github.com/spf13/cobra to v1.5.0
2022-07-28 21:25:50 +02:00
renovate[bot]
0d963808f8
Update module github.com/spf13/cobra to v1.5.0 2022-07-28 14:23:13 +00:00
520cb0717a
Merge pull request #30 from baez90/renovate/github.com-sirupsen-logrus-1.x
Update module github.com/sirupsen/logrus to v1.9.0
2022-07-28 16:22:40 +02:00
renovate[bot]
ebc0fe9890
Update module github.com/sirupsen/logrus to v1.9.0 2022-07-19 10:49:41 +00:00
df47a79d25
Merge pull request #29 from baez90/renovate/github.com-gomarkdown-markdown-digest 2022-06-27 20:02:34 +02:00
renovate[bot]
61fe1e052f
Update github.com/gomarkdown/markdown digest to e9a8110 2022-06-27 17:18:01 +00:00
44bafce6e7
Merge pull request #27 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 45f7c05
2022-06-07 21:44:07 +02:00
Renovate Bot
bd1c9c7a1e
Update github.com/gomarkdown/markdown digest to 45f7c05 2022-06-07 19:35:33 +00:00
da5a5efb34
Merge pull request #26 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 8f3b341
2022-06-03 15:21:31 +02:00
Renovate Bot
0e61fbd1f9
Update github.com/gomarkdown/markdown digest to 8f3b341 2022-06-03 12:56:31 +00:00
ec8710e389
Merge pull request #24 from baez90/renovate/github.com-spf13-viper-1.x 2022-05-31 07:58:33 +02:00
0f44a6c805
Merge pull request #25 from baez90/renovate/github.com-gomarkdown-markdown-digest 2022-05-31 07:57:58 +02:00
Renovate Bot
3223ac590d
Update github.com/gomarkdown/markdown digest to c82b80a 2022-05-27 23:26:40 +00:00
Renovate Bot
50c003a83a
Update module github.com/spf13/viper to v1.12.0 2022-05-26 17:09:49 +00:00
9dddfa9172
Merge pull request #23 from baez90/renovate/goreleaser-goreleaser-action-3.x
Update goreleaser/goreleaser-action action to v3
2022-05-22 11:48:42 +02:00
Renovate Bot
90e8bfd1d9
Update goreleaser/goreleaser-action action to v3 2022-05-22 01:12:10 +00:00
ab0efbf629
Merge pull request #22 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 2372b9a
2022-05-10 22:48:13 +02:00
Renovate Bot
01fb3029a5
Update github.com/gomarkdown/markdown digest to 2372b9a 2022-05-10 14:20:24 +00:00
0ff84b1c8a
Merge pull request #21 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to a57bf95
2022-05-09 21:16:52 +02:00
Renovate Bot
ba3e654347
Update github.com/gomarkdown/markdown digest to a57bf95 2022-05-09 10:29:56 +00:00
2aa4aee9b5
Merge pull request #20 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 567d164
2022-05-07 18:04:22 +02:00
Renovate Bot
bc51bf4c9c
Update github.com/gomarkdown/markdown digest to 567d164 2022-05-06 20:48:06 +00:00
b91b29f156
Merge pull request #19 from baez90/renovate/docker-login-action-2.x
Update docker/login-action action to v2
2022-05-05 21:40:36 +02:00
Renovate Bot
013a2ffd7d
Update docker/login-action action to v2 2022-05-05 19:35:41 +00:00
82cff0edac
Merge pull request #18 from baez90/renovate/github.com-fsnotify-fsnotify-1.x
Update module github.com/fsnotify/fsnotify to v1.5.4
2022-04-27 11:53:33 +02:00
Renovate Bot
516b0277ad
Update module github.com/fsnotify/fsnotify to v1.5.4 2022-04-27 03:06:19 +00:00
1f943cc4eb
Merge pull request #17 from baez90/renovate/github.com-fsnotify-fsnotify-1.x
Update module github.com/fsnotify/fsnotify to v1.5.3
2022-04-22 09:02:53 +02:00
Renovate Bot
b0c6c2034c
Update module github.com/fsnotify/fsnotify to v1.5.3 2022-04-21 20:04:56 +00:00
735a78248f
Merge pull request #16 from baez90/renovate/github.com-fsnotify-fsnotify-1.x
Update module github.com/fsnotify/fsnotify to v1.5.2
2022-04-21 14:43:54 +02:00
Renovate Bot
5fd44be619
Update module github.com/fsnotify/fsnotify to v1.5.2 2022-04-21 12:18:30 +00:00
2084894620
Merge pull request #15 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to 412bcf1
2022-04-20 19:48:29 +02:00
Renovate Bot
8ac5e7ab73
Update github.com/gomarkdown/markdown digest to 412bcf1 2022-04-19 20:03:43 +00:00
fa10f5547f
Merge pull request #14 from baez90/renovate/github.com-gomarkdown-markdown-digest
Update github.com/gomarkdown/markdown digest to a770b0c
2022-04-16 22:36:53 +02:00
Renovate Bot
4632d388c5
Update github.com/gomarkdown/markdown digest to a770b0c 2022-04-15 02:53:15 +00:00
d2d324a8ef
Merge pull request #13 from baez90/renovate/github.com-spf13-viper-1.x
Update module github.com/spf13/viper to v1.11.0
2022-04-13 21:01:12 +02:00
Renovate Bot
9c8a7190d6
Update module github.com/spf13/viper to v1.11.0 2022-04-13 15:52:45 +00:00
a6b6bdd9e6
Merge pull request #12 from baez90/upgrade-to-go-1.18
Upgrade to Go 1.18
2022-03-22 20:58:51 +01:00
f7548a2816
Upgrade to Go 1.18 2022-03-22 20:48:56 +01:00
c358848ed5
Cleanup GitHub actions 2022-03-16 11:41:28 +01:00
dd86250335
Upgrade golangci-lint GitHub action 2022-03-15 17:52:16 +01:00
82d205db98
Migrate to main branch 2022-03-15 17:44:11 +01:00
ded1c0f5ec
Merge pull request #7 from baez90/migrate-to-fiber
Rework goveal based on httprouter and gomarkdown
2022-03-15 17:39:37 +01:00
1582460b28
Add emoji section to Readme 2022-03-15 17:07:48 +01:00
424618ced3
Rename download task 2022-03-15 16:39:47 +01:00
4665345326
Update actions/checkout action to v3 2022-03-15 16:35:16 +01:00
21e41b0bcf
Add emoji support 2022-03-15 16:33:59 +01:00
f18164d48c
Replace fiber with httprouter 2022-03-15 14:53:24 +01:00
e7942ebf15
Make content-root size configurable 2022-02-16 14:18:08 +01:00
0637962da8
Improve mermaid handling and update deps 2022-02-16 09:50:07 +01:00
234929e5ed
Fix loading config from given path 2022-02-16 08:58:49 +01:00
7eb4d67f95
Add golangci-lint as Github action 2021-12-26 12:04:50 +01:00
f8daaf293e
Add container image and document usage 2021-12-23 12:20:10 +01:00
00109b62b4
Fix GoReleaser config 2021-12-23 11:17:14 +01:00
6be2b5aeb5
Cleanup
- update deps
- add open-browser option
- allow to change port and host
2021-12-22 23:05:59 +01:00
7d3d3a5abf
Split slides first before rendering them
- add some more config options
- cleanup code
- remove state machine
2021-12-22 22:31:45 +01:00
33dadaff87
Add golangci-lint 2021-12-22 20:27:43 +01:00
3f847e0b8b
Live update Reveal config 2021-12-22 20:19:05 +01:00
82a651cd5d
First draft of new fiber based server
- replaced previous net/http based muxer and all other components with fiber
- replaced polling with SSE to handle file changes
- auto-update custom stylesheets
- natively support mermaid diagrams
- handle custom element attributes for lists properly
- reload on config changes
- replace JS templating with actual JS code and an API
2021-12-22 11:52:02 +01:00
e4cc33f098
Auto detect line ending on startup 2021-11-15 15:23:10 +01:00
2cc48cfbb0
Fix CRLF vs. LF for slide separators on windows and everything else 2021-11-15 14:37:04 +01:00
fd73f607d6
Merge pull request #4 from baez90/renovate/github.com-masterminds-sprig-v3-3.x
fix(deps): update module github.com/masterminds/sprig/v3 to v3.2.2
2021-11-12 14:45:29 +01:00
Renovate Bot
5f1c147093
fix(deps): update module github.com/masterminds/sprig/v3 to v3.2.2 2021-11-12 13:18:33 +00:00
d118355138
Complete sprig upgrade 2021-11-12 14:17:34 +01:00
55c5842fb8
Delete obsolete Makefile 2021-11-12 14:10:24 +01:00
6e2bca883c
Merge pull request #2 from baez90/renovate/github.com-masterminds-sprig-3.x
fix(deps): update module github.com/masterminds/sprig to v3
2021-11-12 14:08:48 +01:00
Renovate Bot
79bd92653b
fix(deps): update module github.com/masterminds/sprig to v3 2021-11-12 12:38:43 +00:00
60f65859a6
Merge pull request #1 from baez90/renovate/configure
Configure Renovate
2021-11-12 13:37:46 +01:00
35c844c1dc
Fix build 2021-11-12 13:34:54 +01:00
2b242d2cdd
Merge branch 'master' into renovate/configure 2021-11-12 13:24:47 +01:00
26a619dc7b
Update Readme and Github actions pipeline 2021-11-12 13:22:35 +01:00
Renovate Bot
aad73c8e36
chore(deps): add renovate.json 2021-11-12 12:21:27 +00:00
565e199f33
Update Readme 2021-11-12 13:14:12 +01:00
e8d6de4d3c
Update to Reveal.JS 4.1.2 and Go 1.17
- Replace pkger with Go 1.17 embedded file system
2021-11-12 13:08:56 +01:00
b953310df8
Do not cache files that are monitored
- rename repo to goveal
- add working directory option
- remove most CLI switches in favor of config file
2020-12-06 12:18:42 +01:00
d56271ef50
Update to latest task version 2020-12-05 12:17:07 +01:00
48d857d3c7
Add GitHub actions 2020-12-05 12:08:10 +01:00
bd27081aa6
Add auto reload
- reload markdown if changed
- allow to monitor other files if wanted
- disable HTTP cache to force reload
- extend example
- replace almost everything with layered file systems
2020-12-04 16:40:08 +01:00
8a977bad26
fix CI 2020-12-02 13:42:12 +01:00
f1118a1ba1
Update and cleanup
- move to Taskfile
- update RevealJS
- remove annoying special paths and build overlay file system
2020-12-02 13:33:47 +01:00
4327421a69
Update to Reveal 4.0 2020-08-13 09:03:48 +02:00
798943f04b
chore: updated module dependencies 2019-09-09 22:13:12 +02:00
bbbbb0b141
feat: added switches to configure slide numbers 2019-06-03 15:42:16 +02:00
667bc0e2cb
fix: added missing fonts to assets 2019-05-16 12:18:28 +02:00
755931a7cb
fix: fixed speaker mode due to missing .html file 2019-05-16 11:44:45 +02:00
97718aedd7
fix: moved packr2 installation to CircleCI steps 2019-05-16 10:12:14 +02:00
02851fb477
fix: tried to modify setup command of packr2 to avoid go.{mod,sum} manipulation 2019-05-16 10:04:52 +02:00
fb32d95541
fix: cleaned go module dependencies 2019-05-16 09:58:52 +02:00
2f0086b870
doc: added CircleCI badge to README 2019-05-16 09:52:24 +02:00
2004e383e0
doc: Extended README to cover new options 2019-05-16 09:40:46 +02:00
bb66990c98
feat: added support for custom CSS, side menu plugin and a few more config options
- added support to add one or multiple CSS files as theme overrides
- added side menu plugin to control themes, transitions, switch to print view and so on
- added config options to set transition and navigation modes
2019-05-16 03:18:02 +02:00
58 changed files with 2952 additions and 789 deletions

View file

@ -1,30 +0,0 @@
version: 2
jobs:
build:
docker:
- image: circleci/golang:1.12
steps:
- checkout
- run: make compile
release:
docker:
- image: circleci/golang:1.12
steps:
- checkout
- run: curl -sL https://git.io/goreleaser | bash
workflows:
version: 2
build_and_release:
jobs:
- build:
filters:
tags:
only: /.*/
- release:
requires:
- build
filters:
branches:
ignore: /.*/
tags:
only: /^v.*$/

109
.drone.yml Normal file
View file

@ -0,0 +1,109 @@
---
kind: pipeline
type: docker
name: default
platform:
os: linux
arch: arm64
trigger:
branch:
- main
event:
- push
- pull_request
- tag
steps:
- name: Lint
image: docker.io/golangci/golangci-lint:latest
environment:
GO111MODULE: "on"
CGO_ENABLED: "0"
GOMEMLIMIT: "1150MiB"
volumes:
- name: go-cache
path: /go
commands:
- golangci-lint run -v
- name: Test
image: docker.io/golang:1.21-bullseye
network_mode: host
environment:
GO111MODULE: "on"
CGO_ENABLED: "1"
volumes:
- name: go-cache
path: /go
commands:
- go install gotest.tools/gotestsum@latest
- sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin
- task test
- name: Snapshot release
image: ghcr.io/goreleaser/goreleaser
when:
event:
- push
- pull_request
volumes:
- name: go-cache
path: /go
commands:
- goreleaser release --clean --snapshot
- name: Release
image: ghcr.io/goreleaser/goreleaser
when:
event:
- tag
volumes:
- name: go-cache
path: /go
commands:
- apk add -U --no-cache git-lfs
- git lfs install
- git fetch --tags
- git lfs pull
- docker login -u prskr -p "$${GITEA_TOKEN}" code.icb4dc0.de
- goreleaser release --clean
environment:
GITEA_TOKEN:
from_secret: gitea_token
network_mode: host
volumes:
- name: go-cache
temp: { }
---
kind: pipeline
type: docker
name: housekeeping
platform:
os: linux
arch: arm64
trigger:
event:
- cron
cron:
- housekeeping
steps:
- name: Renovate
image: code.icb4dc0.de/prskr/ci-images/renovate:latest
commands:
- renovate "${DRONE_REPO}"
environment:
RENOVATE_TOKEN:
from_secret: gitea_token
GITHUB_COM_TOKEN:
from_secret: github_token
RENOVATE_PLATFORM: gitea
RENOVATE_AUTODISCOVER: "false"
RENOVATE_ENDPOINT: https://code.icb4dc0.de/api/v1
LOG_LEVEL: info

27
.editorconfig Normal file
View file

@ -0,0 +1,27 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
tab_width = 4
indent_style = space
insert_final_newline = false
max_line_length = 120
trim_trailing_whitespace = true
[*.go]
indent_style = tab
ij_smart_tabs = true
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
ij_go_group_stdlib_imports = true
ij_go_import_sorting = goimports
ij_go_local_group_mode = project
ij_go_move_all_imports_in_one_declaration = true
ij_go_move_all_stdlib_imports_in_one_group = true
ij_go_remove_redundant_import_aliases = true
[*.{yml,yaml}]
indent_size = 2
tab_width = 2
insert_final_newline = true

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.jpg filter=lfs diff=lfs merge=lfs -text

36
.gitea/workflows/go.yaml Normal file
View file

@ -0,0 +1,36 @@
name: Go build
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: '0'
lfs: 'true'
fetch-tags: 'true'
- name: Setup Go 1.21.x
uses: actions/setup-go@v4
with:
# Semantic version range syntax or exact version of Go
go-version: '1.21.x'
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
- name: Install Task
uses: arduino/setup-task@v1
- run: |
go install gotest.tools/gotestsum@latest
task test
- uses: goreleaser/goreleaser-action@v4
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
with:
distribution: goreleaser
version: latest
args: release --clean --snapshot

View file

@ -0,0 +1,21 @@
name: Renovate
on:
schedule:
# The "*" (#42, asterisk) character has special semantics in YAML, so this
# string has to be quoted.
- cron: '42 3 * * *'
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- uses: docker://code.icb4dc0.de/prskr/ci-images/renovate:latest
with:
args: renovate "${{ github.repository }}"
env:
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }}
RENOVATE_PLATFORM: gitea
RENOVATE_AUTODISCOVER: "false"
RENOVATE_ENDPOINT: https://code.icb4dc0.de/api/v1
LOG_LEVEL: info

19
.gitignore vendored
View file

@ -5,6 +5,8 @@
*.so
*.dylib
emoji.json
# Test binary, build with `go test -c`
*.test
@ -18,14 +20,17 @@
.vscode/
.history
assets/reveal
# CI
bin/
# packr2 generated
*-packr.go
assets/reveal/
assets/mermaid/
assets/twemoji/
# binaries
goveal
!goveal/
pkged.go
# gorelease
dist/
dist/
#Taskfile
.task/

125
.golangci.yml Normal file
View file

@ -0,0 +1,125 @@
linters-settings:
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
gci:
local-prefixes: code.icb4dc0.de/prskr/goveal
goconst:
min-len: 2
min-occurrences: 2
gocritic:
enabled-tags:
- diagnostic
- opinionated
- performance
disabled-checks:
- ifElseChain
- octalLiteral
- wrapperFunc
settings:
hugeParam:
sizeThreshold: 200
gocyclo:
min-complexity: 15
goimports:
local-prefixes: code.icb4dc0.de/prskr/goveal
golint:
min-confidence: 0
gomnd:
settings:
mnd:
# don't include the "operation" and "assign"
checks:
- argument
- case
- condition
- return
govet:
check-shadowing: true
importas:
no-unaliased: true
alias: []
lll:
line-length: 140
maligned:
suggest-new: true
misspell:
locale: US
nolintlint:
allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space)
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
linters:
# please, do not use `enable-all`: it's deprecated and will be removed soon.
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- contextcheck
- dogsled
- dupl
- errcheck
- errchkjson
- errname
- errorlint
- exhaustive
- exportloopref
- funlen
- gocognit
- goconst
# - gocritic
- gocyclo
- godox
- gofumpt
- goimports
- gomoddirectives
- gomnd
- gosec
- gosimple
- govet
- importas
- ineffassign
# - ireturn - enable later
- lll
- misspell
- nakedret
- nestif
- nilnil
- noctx
- nolintlint
- paralleltest
- prealloc
- predeclared
- promlinter
- staticcheck
- stylecheck
- testpackage
- thelper
# - typecheck
- unconvert
- unparam
- whitespace
- unused
- wastedassign
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- gomnd
- funlen
new: true
fix: true
run:
build-tags:
- sudo
skip-dirs:
- internal/mock
skip-files:
- ".*\\.pb\\.go$"
- ".*.mock.\\.go$"

View file

@ -1,14 +1,10 @@
project_name: goveal
before:
hooks:
- mkdir -p ./assets/reveal
- ./download_revealjs.sh
- go mod download
- go build -v ./...
- go get -u github.com/gobuffalo/packr/v2/packr2
- packr2
gitea_urls:
api: https://code.icb4dc0.de/api/v1
download: https://code.icb4dc0.de
builds:
- main: ./cmd/goveal/main.go
- id: goveal
main: ./cmd/goveal/
binary: goveal
goos:
- linux
@ -24,18 +20,33 @@ builds:
- CGO_ENABLED=0
ignore:
- goos: darwin
goarch: 386
goarch: '386'
- goos: linux
goarch: arm
goarm: 7
goarm: '7'
release:
gitea:
owner: prskr
name: goveal
mode: replace
kos:
- id: goveal
build: goveal
base_image: gcr.io/distroless/static:nonroot
repository: code.icb4dc0.de/prskr/goveal
platforms:
- linux/amd64
- linux/arm64
tags:
- latest
- '{{.Tag}}'
bare: true
base_import_paths: true
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides:
- format_overrides:
- goos: windows
format: zip
files:

18
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,18 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/tekwizely/pre-commit-golang
rev: v1.0.0-beta.5
hooks:
- id: go-mod-tidy-repo
- id: go-fumpt
args:
- -w
- id: go-imports
args:
- -local=github.com/baez90/goveal
- -w
- id: golangci-lint-repo-mod
args:
- --fast
- --fix

119
Makefile
View file

@ -1,119 +0,0 @@
VERSION = $(shell git describe --dirty --tags --always)
REPO = github.com/baez90/go-reveal-slides
BUILD_PATH = $(REPO)/cmd/goveal
PKGS = $(shell go list ./... | grep -v /vendor/)
TEST_PKGS = $(shell find . -type f -name "*_test.go" -printf '%h\n' | sort -u)
GOARGS = GOOS=linux GOARCH=amd64
GO_BUILD_ARGS = -ldflags="-w -s"
BINARY_NAME = goveal
DIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
DEBUG_PORT = 2345
REVEALJS_VERSION = 3.8.0
GORELEASER_VERSION = 0.106.0
.PHONY: all clean clean-all clean-vendor rebuild format revive test deps compile run debug watch watch-test cloc docs serve-docs serve-godoc ensure-revive ensure-reflex ensure-delve ensure-godoc ensure-packr2 ensure-goreleaser
export CGO_ENABLED:=0
all: format compile
clean-all: clean clean-vendor
rebuild: clean format compile
format:
@go fmt $(PKGS)
revive: ensure-revive
@revive --config $(DIR)assets/lint/config.toml -exclude $(DIR)vendor/... -formatter friendly $(DIR)...
clean: ensure-packr2
@rm -f debug $(BINARY_NAME)
@rm -rf dist
@packr2 clean
clean-vendor:
rm -rf vendor/
test:
@go test -coverprofile=./cov-raw.out -v $(TEST_PKGS)
@cat ./cov-raw.out | grep -v "generated" > ./cov.out
cli-cover-report:
@go tool cover -func=cov.out
html-cover-report:
@go tool cover -html=cov.out -o .coverage.html
deps:
@go build -v ./...
compile: deps ensure-packr2
@$(GOARGS) packr2 build $(GO_BUILD_ARGS) -o $(DIR)/$(BINARY_NAME) $(BUILD_PATH)
run:
@go run $(BUILD_PATH)
debug: ensure-delve
@dlv debug \
--headless \
--listen=127.10.10.2:$(DEBUG_PORT) \
--api-version=2 $(BUILD_PATH) \
--build-flags="-tags debug" \
-- serve --config ./examples/goveal.yaml $(DIR)/examples/slides.md
download-reveal:
@mkdir -p $(DIR)/assets/reveal
@curl -sL https://github.com/hakimel/reveal.js/archive/$(REVEALJS_VERSION).tar.gz | tar -xvz --strip-components=1 -C $(DIR)/assets/reveal --wildcards "*.js" --wildcards "*.css" --exclude "test" --exclude "gruntfile.js"
watch: ensure-reflex
@reflex -r '\.go$$' -s -- sh -c 'make debug'
watch-test: ensure-reflex
@reflex -r '_test\.go$$' -s -- sh -c 'make test'
cloc:
@cloc --vcs=git --exclude-dir=.idea,.vscode,.theia,public,docs, .
serve-godoc: ensure-godoc
@godoc -http=:6060
serve-docs: ensure-reflex docs
@reflex -r '\.md$$' -s -- sh -c 'mdbook serve -d $(DIR)/public -n 127.0.0.1 $(DIR)/docs'
docs:
@mdbook build -d $(DIR)/public $(DIR)/docs`
test-release: ensure-goreleaser ensure-packr2
@goreleaser --snapshot --skip-publish --rm-dist
ensure-revive:
ifeq (, $(shell which revive))
$(shell go get -u github.com/mgechev/revive)
endif
ensure-delve:
ifeq (, $(shell which dlv))
$(shell go get -u github.com/go-delve/delve/cmd/dlv)
endif
ensure-reflex:
ifeq (, $(shell which reflex))
$(shell go get -u github.com/cespare/reflex)
endif
ensure-godoc:
ifeq (, $(shell which godoc))
$(shell go get -u golang.org/x/tools/cmd/godoc)
endif
ensure-packr2:
ifeq (, $(shell which packr2))
$(shell go get -u github.com/gobuffalo/packr/v2/packr2)
endif
ensure-goreleaser:
ifeq (, $(shell which goreleaser))
$(shell curl -sL https://github.com/goreleaser/goreleaser/releases/download/v$(GORELEASER_VERSION)/goreleaser_Linux_x86_64.tar.gz | tar -xvz --exclude "*.md" -C $$GOPATH/bin)
endif

127
README.md
View file

@ -1,35 +1,124 @@
# Goveal
Goveal is very small an very simple tool that reads Markdown from a given file, renders it into a HTML template and serves it as local HTTP server.
Right now Goveal uses Reveal.js 3.8.0 to create presentations and therefore also includes all features of Reveal.js 3.8.0.
[![Actions Status](https://github.com/baez90/goveal/workflows/Go/badge.svg)](https://github.com/baez90/goveal/actions)
Goveal is very small and very simple tool that reads Markdown from a given file, renders it into an HTML template and serves it as local HTTP server.
Right now Goveal uses Reveal.js 4.3.0 to create presentations and therefore also includes all features of Reveal.js.
In contrary to Reveal.js `goveal` ships with its own Markdown parser and renderer which is why some features are working slightly different from Reveal.js.
See [Markdown](#markdown) for further details.
Besides all features from Reveal.js `goveal` comes with first class support for [mermaid-js](https://mermaid-js.github.io/).
Just inline your diagrams as code and enjoy!
An example can be found in the [examples](examples/slides.md).
## Install
The easiest and fastest way to install Goveal is to use a pre-built binary from the [releases](https://github.com/baez90/goveal/releases/latest).
There's also a pre-built container image available you can use if you don't want to download the binary.
### Build from source
If you have Go in the latest version installed you can also build your own version of Goveal:
```shell
task build
```
Requirements:
- [task](https://taskfile.dev/)
- _Optional:_ [goreleaser](https://goreleaser.com/) (for `task snapshot-release` to build all binaries)
_Note: All script tasks in the [Taskfile.yml](Taskfile.yml) are meant to be executed with Linux/MacOS. Binaries for
Windows are provided but not tested!_
## Usage
The easiest way to use `goveal` is to download a release, and run it from your `$PATH`.
### Local installation
```bash
goveal serve ./slides.md
```
| Param | Description | Default value |
| ------------------------ | --------------------------------------------- | ----------------------- |
| `--host` | Hostname the binary is listening on | `localhost` |
| `--port` | Port the binary is listening on | `2233` |
| `--code-theme` | highlight.js theme to use | `monokai` |
| `--config` | Path to the config file see [config](#config) | `$HOME/goveal:./goveal` |
| `-h` / `--help` | shows help | |
| `--horizontal-separator` | horizontal separator to split slides | `===` |
| `--vertical-separator` | vertical separator to split slides | `---` |
| `--theme` | reveal.js theme to use | `white` |
| Param | Description | Default value |
|------------------|-----------------------------------------------|-------------------------|
| `--host` | Hostname the binary is listening on | `127.0.0.1` |
| `--port` | Port the binary is listening on | `2233` |
| `--config` | Path to the config file see [config](#config) | `$HOME/goveal:./goveal` |
| `--open-browser` | Open a browser after starting the web server | `true` |
| `-h` / `--help` | shows help | |
### Container
Assuming your slides are in a file called `slides.md` in the current directory, you can start the presentation like
this:
```shell
podman/docker run --rm -ti -v `pwd`:/work -w /work -p 2233:2233 ghcr.io/baez90/goveal:0 serve --host 0.0.0.0 slides.md
```
By default `goveal` only listens on `127.0.0.1`. To allow traffic from outside the container you've to change the
binding to `0.0.0.0`.
## Config
Goveal supports multiple configuration mechanisms.
It tries to load a configuration file from `$HOME` or from `.` i.e. `$HOME/goveal.yaml` or `$HOME/goveal.yml` or `./goveal.yaml` and so on.
Goveal supports multiple configuration mechanisms. It tries to load a configuration file from `$HOME` or from `.`
i.e. `$HOME/goveal.yaml` or `$HOME/goveal.yml` or `./goveal.yaml` and so on.
Most options that can be set via commandline flags can also be set via configuration file (actually all but the `--config` switch does not make sense in the configuration file, does it? :wink:).
It is more a convenience feature to be able to set a theme and so on and so forth for the presentation without having to pass it every time as parameter.
The config allows setting a lot of options exposed by Reveal.js.
There are still a few missing, and I won't guarantee to support all options in the future.
Furthermore goveal supports configuration hot reloading i.e. you can play around with different themes and the rendered theme will be changed whenever you hit the save button!
Furthermore, `goveal` supports configuration hot reloading i.e. you can play around with different themes and the rendered
theme will be changed whenever you hit the save button!
See also an example at [`./examples/goveal.yaml`](./examples/goveal.yaml).
See also an example at [`./examples/goveal.yaml`](./examples/goveal.yaml).
I try to keep the example up to date to cover **all** supported config options as kind of documentation.
## Markdown
A good point to start is the [slides.md](examples/slides.md) in the `examples` directory.
I try to showcase everything possible with `goveal` in this presentation.
The most remarkable difference between `goveal` and the `marked` driven Reveal.js markdown is how line numbers in listings are working:
Reveal.js:
<pre lang="no-highlight"><code>```cs [1-2|3|4]
var i = 10;
for (var j = 0; j < i; j++) {
Console.WriteLine($"{j}");
}
```</code></pre>
`goveal`:
<pre lang="no-highlight"><code>
{line-numbers="1-2|3|4"}
```cs
var i = 10;
for (var j = 0; j < i; j++) {
Console.WriteLine($"{j}");
}
```</code></pre>
This is because the Markdown parser used in `goveal` currently does not support additional attributes for code blocks.
## Custom CSS
To add custom CSS as theme overrides use a config file and add the `stylesheets` property. It takes a list of relative (
mandatory!) paths to CSS files that are included automatacally after the page was loaded so that they really overload
everything added by Reveal and plugins.
Changes in the custom CSS files are monitored and propagated via SSE to the presentation immediately.
No page reload necessary!
The sample configuration file [`./examples/goveal.yaml`](./examples/goveal.yaml) also contains a sample how to add
custom CSS.
## Emojis
Aside of UTF character emojis `goveal` also supports emoji references like Github flavored markdown supports them.
For example`:winking_face:` becomes :winking_face: .
All supported keywords can be found [here](https://unpkg.com/emojilib@latest).

128
Taskfile.yml Normal file
View file

@ -0,0 +1,128 @@
version: '3'
vars:
DEBUG_PORT: 2345
REVEALJS_VERSION: 4.3.0
HIGHLIGHTJS_VERSION: 11.5.0
MERMAID_VERSION: 8.14.0
BINARY_NAME: goveal
OUT_DIR: ./out
GO_BUILD_ARGS: -ldflags="-w -s"
CMD_PACKAGE: github.com/baez90/goveal/cmd/goveal
env:
CGO_ENABLED: 0
GOBIN:
sh: echo "$(pwd)/bin/"
tasks:
clean:
cmds:
- rm -f debug {{ .BINARY_NAME }}
- rm -rf ./dist {{ .OUT_DIR }}
fmt:
desc: Run gofumpt against code
deps:
- gofumpt
- goimports
sources:
- "**/*.go"
cmds:
- "{{ .GOBIN }}gofumpt -l -w ./"
- "{{ .GOBIN }}goimports -w -local github.com/baez90/goveal ./."
deps:
deps:
- download-assets
sources:
- go.mod
- go.sum
cmds:
- go mod download
- go mod tidy -compat=1.21
test:
sources:
- ./**/*.go
deps:
- deps
- fmt
cmds:
- mkdir -p {{ .OUT_DIR }}
- go test -coverprofile={{ .OUT_DIR }}/cov-raw.out -v ./...
- cat {{ .OUT_DIR }}/cov-raw.out | grep -v "generated" > {{ .OUT_DIR }}/cov.out
cli-cover-report:
deps:
- test
cmds:
- go tool cover -func={{ .OUT_DIR }}/cov.out
html-cover-report:
deps:
- test
cmds:
- go tool cover -html={{ .OUT_DIR }}/cov.out -o {{ .OUT_DIR }}/coverage.html
build:
deps:
- deps
sources:
- ./**/*.go
cmds:
- go build {{ .GO_BUILD_ARGS }} -o {{ .BINARY_NAME }} {{ .CMD_PACKAGE }}
run:
deps:
- deps
dir: ./examples
cmds:
- go run {{ .CMD_PACKAGE }} serve ./slides.md --config ./goveal.yaml
debug:
cmds:
- dlv debug --headless --listen=127.10.10.2:{{ .DEBUG_PORT }} --api-version=2 {{ .CMD_PACKAGE }} --build-flags="-tags debug" -- serve --config ./examples/goveal.yaml ./examples/slides.md
snapshot-release:
cmds:
- goreleaser --snapshot --skip-publish --rm-dist
download-assets:
sources:
- Taskfile.yml
cmds:
- rm -rf ./assets/reveal
- mkdir -p ./assets/reveal ./assets/mermaid
- curl -sL https://github.com/hakimel/reveal.js/archive/{{ .REVEALJS_VERSION }}.tar.gz | tar -xvz --strip-components=1 -C ./assets/reveal --wildcards "*.js" --wildcards "*.css" --wildcards "*.html" --wildcards "*.woff" --wildcards "*.ttf" --exclude "test" --exclude "gulpfile.js" --exclude "gruntfile.js" --exclude "demo.html" --exclude "index.html" --exclude "examples/*.html"
- mkdir -p ./assets/reveal/plugin/menu ./assets/reveal/plugin/mouse-pointer
- git clone https://github.com/denehyg/reveal.js-menu.git ./assets/reveal/plugin/menu
- curl -L -o ./assets/reveal/plugin/mouse-pointer/mouse-pointer.js https://raw.githubusercontent.com/caiofcm/plugin-revealjs-mouse-pointer/master/mouse-pointer.js
- rm -f ./assets/reveal/plugin/menu/{bower.json,CONTRIBUTING.md,LICENSE,package.json,README.md,.gitignore,gulpfile.js,package-lock.json}
- curl -L https://github.com/highlightjs/highlight.js/archive/{{ .HIGHLIGHTJS_VERSION }}.tar.gz | tar -xvz --strip-components=3 -C ./assets/reveal/plugin/highlight --wildcards "*.css" highlight.js-{{ .HIGHLIGHTJS_VERSION }}/src/styles/
- curl -L https://registry.npmjs.org/mermaid/-/mermaid-{{ .MERMAID_VERSION }}.tgz | tar -xvz -C ./assets/mermaid/ package/dist --strip-components=2
- curl -L -o rendering/emoji/emoji.json https://unpkg.com/emojilib@latest
go-get-tool:
vars:
PACKAGE: '{{ default "" .PACKAGE }}'
cmds:
- go install {{ .PACKAGE }}
gofumpt:
desc: Download gofumpt locally if necessary
cmds:
- task: go-get-tool
vars:
PACKAGE: mvdan.cc/gofumpt@v0.2.0
status:
- test -f {{ .GOBIN }}gofumpt
goimports:
desc: Download goimports locally if necessary
cmds:
- task: go-get-tool
vars:
PACKAGE: golang.org/x/tools/cmd/goimports@v0.1.7
status:
- test -f {{ .GOBIN }}goimports

36
api/config.go Normal file
View file

@ -0,0 +1,36 @@
package api
import (
"encoding/json"
"net/http"
"github.com/julienschmidt/httprouter"
"code.icb4dc0.de/prskr/goveal/config"
)
type ConfigAPI struct {
cfg *config.Components
}
func RegisterConfigAPI(router *httprouter.Router, cfg *config.Components) {
cfgAPI := &ConfigAPI{cfg: cfg}
router.GET("/api/v1/config/reveal", cfgAPI.RevealConfig)
router.GET("/api/v1/config/mermaid", cfgAPI.MermaidConfig)
}
func (a *ConfigAPI) RevealConfig(writer http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
writer.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(writer)
if err := enc.Encode(a.cfg.Reveal); err != nil {
writer.WriteHeader(http.StatusInternalServerError)
}
}
func (a *ConfigAPI) MermaidConfig(writer http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
writer.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(writer)
if err := enc.Encode(a.cfg.Mermaid); err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
}
}

92
api/events.go Normal file
View file

@ -0,0 +1,92 @@
package api
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"code.icb4dc0.de/prskr/goveal/events"
)
type ContentEventHandler chan events.ContentEvent
func (h ContentEventHandler) OnEvent(ce events.ContentEvent) error {
const enqueueTimeout = 50 * time.Millisecond
select {
case h <- ce:
return nil
case <-time.After(enqueueTimeout):
return errors.New("failed to enqueue due to timeout")
}
}
func (h ContentEventHandler) Close() (err error) {
defer func() {
if rec := recover(); rec != nil {
err = fmt.Errorf("failed to close event handler: %v", rec)
}
}()
close(h)
return
}
type Events struct {
logger *log.Logger
hub *events.EventHub
}
func RegisterEventsAPI(router *httprouter.Router, hub *events.EventHub, logger *log.Logger) {
ev := &Events{hub: hub, logger: logger}
router.GET("/api/v1/events", ev.EventHandler)
}
func (e *Events) EventHandler(writer http.ResponseWriter, req *http.Request, _ httprouter.Params) {
writer.Header().Set("Content-Type", "text/event-stream")
writer.Header().Set("Cache-Control", "no-cache")
writer.Header().Set("Connection", "keep-alive")
writer.Header().Set("Access-Control-Allow-Origin", "*")
writer.Header().Set("Transfer-Encoding", "chunked")
writer.Header().Set("Access-Control-Allow-Headers", "Cache-Control")
writer.Header().Set("Access-Control-Allow-Credentials", "true")
var (
handler = make(ContentEventHandler)
clientID = e.hub.Subscribe(handler)
buf = new(bytes.Buffer)
enc = json.NewEncoder(buf)
)
defer func() {
if err := e.hub.Unsubscribe(clientID); err != nil {
e.logger.Warnf("Error occurred while unsubscribing: %v", err)
}
}()
for {
select {
case ev := <-handler:
if err := enc.Encode(ev); err != nil {
e.logger.Errorf("Failed to marshal to JSON: %v", err)
continue
} else if _, err = fmt.Fprintf(writer, "data: %s\n\n", buf.String()); err != nil {
e.logger.Errorf("Failed to write to client: %v", err)
return
} else if f, ok := writer.(http.Flusher); !ok {
e.logger.Errorf("Cannot flush data")
writer.WriteHeader(http.StatusBadRequest)
return
} else {
f.Flush()
}
case <-req.Context().Done():
return
}
}
}

View file

@ -0,0 +1,12 @@
package api
import (
"net/http"
)
func NoCache(handler http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Cache-Control", "no-cache")
handler.ServeHTTP(writer, request)
})
}

33
api/reveal.go Normal file
View file

@ -0,0 +1,33 @@
package api
import (
"io"
"net/http"
"path"
"strings"
"time"
"code.icb4dc0.de/prskr/goveal/assets"
"code.icb4dc0.de/prskr/goveal/fs"
"code.icb4dc0.de/prskr/goveal/web"
)
func FileSystemMiddleware(fallthroughHandler http.Handler, wdfs fs.FS) http.Handler {
layers := []fs.FS{wdfs}
layers = append([]fs.FS{web.WebFS, assets.Assets}, layers...)
layeredFS := fs.Layered{Layers: layers}
return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {
reqPath := strings.TrimPrefix(req.URL.Path, "/")
if f, err := layeredFS.Open(reqPath); err != nil {
fallthroughHandler.ServeHTTP(writer, req)
return
} else if readSeeker, ok := f.(io.ReadSeeker); ok {
http.ServeContent(writer, req, path.Base(reqPath), time.Now(), readSeeker)
_ = f.Close()
} else {
_ = f.Close()
fallthroughHandler.ServeHTTP(writer, req)
}
})
}

103
api/views.go Normal file
View file

@ -0,0 +1,103 @@
package api
import (
"encoding/hex"
"errors"
"hash/fnv"
"html/template"
"io"
"net/http"
"path"
"github.com/Masterminds/sprig/v3"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"code.icb4dc0.de/prskr/goveal/config"
"code.icb4dc0.de/prskr/goveal/fs"
"code.icb4dc0.de/prskr/goveal/rendering"
"code.icb4dc0.de/prskr/goveal/web"
)
var indexTmpl *template.Template
func init() {
if t, err := template.
New("index").
Funcs(sprig.FuncMap()).
Funcs(map[string]any{
"fileId": func(fileName string) string {
h := fnv.New32a()
return hex.EncodeToString(h.Sum([]byte(path.Base(fileName))))
},
}).
ParseFS(web.WebFS, "*.gohtml"); err != nil {
panic(err)
} else {
indexTmpl = t
}
}
type Views struct {
logger *log.Logger
cfg *config.Components
wdfs fs.FS
mdFilepath string
}
func RegisterViews(router *httprouter.Router, logger *log.Logger, wdfs fs.FS, mdFilepath string, cfg *config.Components) {
p := &Views{
logger: logger,
cfg: cfg,
wdfs: wdfs,
mdFilepath: mdFilepath,
}
router.GET("/", p.IndexPage)
router.GET("/index.html", p.IndexPage)
router.GET("/index.htm", p.IndexPage)
router.GET("/slides", p.RenderedMarkdown)
}
func (p *Views) IndexPage(writer http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
writer.Header().Set("Content-Type", "text/html")
if err := indexTmpl.ExecuteTemplate(writer, "index.gohtml", map[string]any{
"Reveal": p.cfg.Reveal,
"Rendering": p.cfg.Rendering,
}); err != nil {
p.logger.Errorf("Failed to render template: %v", err)
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte(err.Error()))
}
}
func (p *Views) RenderedMarkdown(writer http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
f, err := p.wdfs.Open(p.mdFilepath)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte(err.Error()))
return
}
defer func() {
err = errors.Join(err, f.Close())
}()
data, err := io.ReadAll(f)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte(err.Error()))
return
}
writer.Header().Set("Content-Type", "text/html")
var rendered []byte
if rendered, err = rendering.ToHTML(string(data), p.cfg.Rendering); err != nil {
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte(err.Error()))
return
} else if _, err = writer.Write(rendered); err != nil {
writer.WriteHeader(http.StatusInternalServerError)
_, _ = writer.Write([]byte(err.Error()))
return
}
}

6
assets/assets.go Normal file
View file

@ -0,0 +1,6 @@
package assets
import "embed"
//go:embed reveal mermaid/mermaid.min.js
var Assets embed.FS

View file

@ -1,46 +0,0 @@
ignoreGeneratedHeader = false
severity = "warning"
confidence = 0.8
errorCode = 1
warningCode = 1
empty-block = true
confusing-naming = true
get-return = true
deep-exit = true
unused-parameter = true
unreachable-code = true
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
[rule.if-return]
[rule.increment-decrement]
[rule.var-naming]
[rule.var-declaration]
[rule.package-comments]
[rule.range]
[rule.receiver-naming]
[rule.time-naming]
[rule.unexported-return]
[rule.indent-error-flow]
[rule.errorf]
[rule.empty-block]
[rule.superfluous-else]
[rule.unused-parameter]
[rule.unreachable-code]
[rule.redefines-builtin-id]
[argument-limit]
arguments =[4]
[line-length-limit]
arguments =[80]
[rule.add-constant]
arguments = [{maxLitCount = "3",allowStrs ="\"\"",allowInts="0,1,2",allowFloats="0.0,0.,1.0,1.,2.0,2."}]

View file

@ -6,52 +6,95 @@
<title>goveal</title>
<link rel="stylesheet" href="/reveal/css/reveal.css">
<link rel="stylesheet" href="/reveal/css/theme/{{ .Reveal.Theme }}.css" id="theme">
<link rel="stylesheet" href="/dist/reveal.css">
<link rel="stylesheet" href="/dist/reset.css">
<link rel="stylesheet" href="/dist/theme/{{ .Reveal.Theme }}.css" id="theme">
<link rel="stylesheet" href="/reveal/lib/css/{{ .Reveal.CodeTheme }}.css">
<link rel="stylesheet" href="/plugin/highlight/{{ .Reveal.CodeTheme }}.css">
</head>
<body>
<div class="reveal">
<div class="slides">
<section data-markdown="/markdown/content.md"
data-separator="^\n{{ .Reveal.HorizontalSeparator }}\n"
data-separator-vertical="^\n{{ .Reveal.VerticalSeparator }}\n"
<section data-markdown="/content.md"
data-separator="^{{ .Reveal.LineEnding.Escaped -}}{{ .Reveal.HorizontalSeparator }}{{- .Reveal.LineEnding.Escaped }}"
data-separator-vertical="^{{ .Reveal.LineEnding.Escaped -}}{{ .Reveal.VerticalSeparator }}{{- .Reveal.LineEnding.Escaped }}"
data-separator-notes="^Note:"
data-charset="iso-8859-15">
</section>
</div>
</div>
<script src="/reveal/js/reveal.js"></script>
<script src="/dist/reveal.js"></script>
<script src="/plugin/markdown/markdown.js"></script>
<script src="/plugin/highlight/highlight.js"></script>
<script src="/plugin/notes/notes.js"></script>
<script src="/plugin/menu/menu.js"></script>
<script src="/js/reload.js"></script>
<script>
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
slideNumber: true,
hash: true,
transition: '{{ .Reveal.Transition }}', // none/fade/slide/convex/concave/zoom
navigationMode: {{ .Reveal.NavigationMode | trim | quote }},
showSlideNumber: {{ .Reveal.SlideNumberVisibility | trim | quote }},
slideNumber: '{{ .Reveal.SlideNumberFormat }}',
markdown: {
smartypants: true,
smartLists: true
},
pdfSeparateFragments: false,
menu: {
numbers: true,
useTextContentForMissingTitles: true,
custom: [
{
title: 'Print',
icon: '<i class="fas fa-print"></i>',
content: '<a href="/?print-pdf">Go to print view<a/>'
}
],
themes: [
{name: 'Beige', theme: '/dist/theme/beige.css'},
{name: 'Black', theme: '/dist/theme/black.css'},
{name: 'Blood', theme: '/dist/theme/blood.css'},
{name: 'League', theme: '/dist/theme/league.css'},
{name: 'Moon', theme: '/dist/theme/moon.css'},
{name: 'Night', theme: '/dist/theme/night.css'},
{name: 'Serif', theme: '/dist/theme/serif.css'},
{name: 'Simple', theme: '/dist/theme/simple.css'},
{name: 'Sky', theme: '/dist/theme/sky.css'},
{name: 'Solarized', theme: '/dist/theme/solarized.css'},
{name: 'White', theme: '/dist/theme/white.css'}
],
transitions: true,
},
plugins: [ RevealMarkdown, RevealHighlight, RevealNotes, RevealMenu ]
});
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
slideNumber: true,
hash: true,
transition: 'slide', // none/fade/slide/convex/concave/zoom
markdown:{
smartypants: true,
smartLists: true,
},
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: '/reveal/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: '/reveal/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: '/reveal/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: '/reveal/plugin/notes/notes.js' }
]
});
{{ if .Reveal.StyleSheets }}
{{ range $idx, $style := .Reveal.StyleSheets }}
var additionalStyleSheet = document.createElement('link');
additionalStyleSheet.rel = 'stylesheet';
additionalStyleSheet.type = 'text/css';
additionalStyleSheet.href = '/{{- $style }}';
document.getElementsByTagName('head')[0].appendChild(additionalStyleSheet);
{{ end }}
{{ end }}
{{ if .Reveal.FilesToMonitor }}
{{ range $idx, $file := .Reveal.FilesToMonitor }}
subscribeForUpdates("/{{ $file }}")
{{ end }}
{{ end }}
subscribeForUpdates("/content.md")
</script>

View file

@ -14,8 +14,12 @@
package main
import "github.com/baez90/go-reveal-slides/internal/app/cmd"
import (
log "github.com/sirupsen/logrus"
)
func main() {
cmd.Execute()
if err := rootCmd.Execute(); err != nil {
log.Errorf("Failed to run command: %v", err)
}
}

52
cmd/goveal/root.go Normal file
View file

@ -0,0 +1,52 @@
// Copyright © 2019 Peter Kurfer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"os"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"code.icb4dc0.de/prskr/goveal/config"
)
//nolint:lll // explanations are rather long
var (
cfgFile string
rootCmd = &cobra.Command{
Use: "goveal",
Short: "goveal is a small reveal.js server",
Long: `goveal is a single static binary to host your reveal.js based markdown presentation.
It is running a small web server that loads your markdown file, renders a complete HTML page and delivers it including all the reveal.js assets.
It is not required to restart the server when you edit the markdown - a simple reload of the page is doing all the required magic.`,
PersistentPreRunE: func(*cobra.Command, []string) (err error) {
log.SetFormatter(&log.TextFormatter{
ForceColors: true,
})
if workingDir, err = os.Getwd(); err != nil {
return err
}
cfg, err = config.Load(workingDir, cfgFile)
return err
},
}
)
func init() {
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-reveal-slides.yaml)")
}

110
cmd/goveal/serve.go Normal file
View file

@ -0,0 +1,110 @@
package main
import (
"errors"
"fmt"
"hash/fnv"
"net/http"
"os/exec"
"path/filepath"
"runtime"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"code.icb4dc0.de/prskr/goveal/api"
"code.icb4dc0.de/prskr/goveal/config"
"code.icb4dc0.de/prskr/goveal/events"
"code.icb4dc0.de/prskr/goveal/fs"
)
const (
defaultListeningPort uint16 = 2233
defaultHost = "127.0.0.1"
)
var (
workingDir string
cfg *config.Components
host string
port uint16
openBrowser bool
serveCmd = &cobra.Command{
Use: "serve",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) (err error) {
var wdfs *fs.Watching
if wdfs, err = fs.NewWatching(fs.Dir(workingDir)); err != nil {
return err
}
defer func() {
err = errors.Join(err, wdfs.Close())
}()
var mdFile fs.File
if mdFile, err = wdfs.Open(args[0]); err != nil {
return err
}
_ = mdFile.Close()
router := httprouter.New()
hub := events.NewEventHub(
log.StandardLogger(),
wdfs,
fnv.New32a(),
events.MutationReloadForFile(args[0]),
events.MutationConfigReloadForFile(filepath.Base(cfg.ConfigFileInUse)),
)
api.RegisterViews(router, log.StandardLogger(), wdfs, args[0], cfg)
api.RegisterEventsAPI(router, hub, log.StandardLogger())
api.RegisterConfigAPI(router, cfg)
handler := api.FileSystemMiddleware(router, wdfs)
handler = api.NoCache(handler)
if openBrowser {
log.Info("Opening browser...")
openBrowserInBackground(fmt.Sprintf("http://%s:%d", host, port))
}
log.Infof("Listening on %s:%d", host, port)
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", host, port), handler); err != nil {
if errors.Is(err, http.ErrServerClosed) {
return nil
}
return err
}
return nil
},
}
)
func init() {
serveCmd.Flags().Uint16Var(&port, "port", defaultListeningPort, "port to listen on")
serveCmd.Flags().StringVar(&host, "host", defaultHost, "address/hostname to bind on")
serveCmd.Flags().BoolVar(&openBrowser, "open-browser", true, "Open browser when starting")
rootCmd.AddCommand(serveCmd)
}
func openBrowserInBackground(url string) {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
if err != nil {
log.Warn(err)
}
}

85
config/components.go Normal file
View file

@ -0,0 +1,85 @@
package config
const (
defaultWidth uint = 960
defaultHeight uint = 700
)
var defaults = map[string]any{
"mermaid.theme": "forest",
"theme": "beige",
"width": defaultWidth,
"height": defaultHeight,
"codeTheme": "monokai",
"verticalSeparator": `\*\*\*`,
"horizontalSeparator": `---`,
"transition": TransitionNone,
"controlsLayout": ControlsLayoutEdges,
"controls": true,
"progress": true,
"history": true,
"center": true,
"slideNumber": true,
"menu.numbers": true,
"menu.useTextContentForMissingTitles": true,
"menu.transitions": true,
"menu.hideMissingTitles": true,
"menu.markers": true,
"menu.openButton": true,
}
const (
TransitionNone Transition = "none"
TransitionFade Transition = "fade"
TransitionSlide Transition = "slide"
TransitionConvex Transition = "convex"
TransitionConcave Transition = "concave"
TransitionZoom Transition = "zoom"
ControlsLayoutBottomRight ControlsLayout = "bottom-right"
ControlsLayoutEdges ControlsLayout = "edges"
)
type (
Transition string
ControlsLayout string
Mermaid struct {
Theme string `json:"theme"`
}
Rendering struct {
VerticalSeparator string
HorizontalSeparator string
Stylesheets []string
}
Reveal struct {
Theme string `json:"theme"`
CodeTheme string `json:"codeTheme"`
Transition Transition `json:"transition"`
Controls bool `json:"controls"`
ControlsLayout ControlsLayout `json:"controlsLayout"`
Progress bool `json:"progress"`
History bool `json:"history"`
Center bool `json:"center"`
SlideNumber bool `json:"slideNumber"`
Width uint `json:"width"`
Height uint `json:"height"`
Menu struct {
Numbers bool `json:"numbers"`
UseTextContentForMissingTitles bool `json:"useTextContentForMissingTitles"`
Transitions bool `json:"transitions"`
HideMissingTitles bool `json:"hideMissingTitles"`
Markers bool `json:"markers"`
OpenButton bool `json:"openButton"`
} `json:"menu"`
}
Components struct {
ConfigFileInUse string `mapstructure:"-"`
Reveal Reveal `mapstructure:",squash"`
Rendering Rendering `mapstructure:",squash"`
Mermaid Mermaid
}
)
func (t Transition) String() string {
return string(t)
}

51
config/load.go Normal file
View file

@ -0,0 +1,51 @@
package config
import (
"github.com/fsnotify/fsnotify"
"github.com/mitchellh/go-homedir"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
func Load(workingDir, configFile string) (cfg *Components, err error) {
var (
loader = viper.New()
home string
)
cfg = new(Components)
for k, v := range defaults {
loader.SetDefault(k, v)
}
if configFile != "" {
loader.SetConfigFile(configFile)
} else if home, err = homedir.Dir(); err != nil {
return nil, err
} else {
loader.AddConfigPath(home)
loader.AddConfigPath(workingDir)
loader.SetConfigName("goveal")
loader.SetConfigType("yaml")
}
loader.AutomaticEnv()
if err = loader.ReadInConfig(); err == nil {
log.Info("Using config file:", loader.ConfigFileUsed())
cfg.ConfigFileInUse = loader.ConfigFileUsed()
loader.WatchConfig()
} else {
return nil, err
}
loader.OnConfigChange(func(in fsnotify.Event) {
if in.Op == fsnotify.Write {
_ = loader.Unmarshal(cfg)
}
})
err = loader.Unmarshal(cfg)
return cfg, err
}

View file

@ -1,4 +0,0 @@
#!/usr/bin/env bash
mkdir -p ./assets/reveal
curl -sL "https://github.com/hakimel/reveal.js/archive/${1:-3.8.0}.tar.gz" | tar -xvz --strip-components=1 -C ./assets/reveal --wildcards *.js --wildcards *.css --exclude test --exclude gruntfile.js

156
events/event_hub.go Normal file
View file

@ -0,0 +1,156 @@
package events
import (
"encoding/hex"
"fmt"
"hash"
"io"
"path"
"path/filepath"
"strconv"
"strings"
"sync"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"code.icb4dc0.de/prskr/goveal/fs"
)
const (
baseDecimal = 10
)
type (
EventMutation interface {
OnEvent(in ContentEvent, ev fs.Event) (out ContentEvent)
}
ContentEvent struct {
File string `json:"file"`
FileNameHash string `json:"fileNameHash"`
Timestamp string `json:"ts"`
ForceReload bool `json:"forceReload"`
ReloadConfig bool `json:"reloadConfig"`
}
EventSource interface {
io.Closer
fs.FS
Events() chan fs.Event
}
EventHandler interface {
OnEvent(ev ContentEvent) error
Close() error
}
EventHandlerFunc func(ev ContentEvent) error
MutationReloadForFile string
MutationConfigReloadForFile string
)
func (t MutationReloadForFile) OnEvent(in ContentEvent, ev fs.Event) (out ContentEvent) {
if strings.EqualFold(filepath.Base(ev.File), filepath.Base(string(t))) {
in.ForceReload = true
}
return in
}
func (t MutationConfigReloadForFile) OnEvent(in ContentEvent, ev fs.Event) (out ContentEvent) {
if strings.EqualFold(filepath.Base(ev.File), string(t)) {
in.ReloadConfig = true
}
return in
}
func (f EventHandlerFunc) OnEvent(ev ContentEvent) error {
return f(ev)
}
func NewEventHub(logger *log.Logger, eventSource EventSource, fileNameHash hash.Hash, mutations ...EventMutation) *EventHub {
hub := &EventHub{
Logger: logger,
FileNameHash: fileNameHash,
mutations: mutations,
source: eventSource,
subscriptions: make(map[uuid.UUID]EventHandler),
done: make(chan struct{}),
}
go hub.processEvents()
return hub
}
type EventHub struct {
FileNameHash hash.Hash
Logger *log.Logger
mutations []EventMutation
lock sync.RWMutex
done chan struct{}
source EventSource
subscriptions map[uuid.UUID]EventHandler
}
func (h *EventHub) Subscribe(handler EventHandler) (id uuid.UUID) {
h.lock.Lock()
defer h.lock.Unlock()
clientID := uuid.New()
h.subscriptions[clientID] = handler
return clientID
}
func (h *EventHub) Unsubscribe(id uuid.UUID) (err error) {
h.lock.Lock()
defer h.lock.Unlock()
if handler, ok := h.subscriptions[id]; ok {
err = handler.Close()
delete(h.subscriptions, id)
return err
}
return nil
}
func (h *EventHub) Close() error {
close(h.done)
return h.source.Close()
}
func (h *EventHub) processEvents() {
events := h.source.Events()
for {
select {
case ev, more := <-events:
if !more {
return
}
h.notifySubscribers(ev)
case _, more := <-h.done:
if !more {
return
}
}
}
}
func (h *EventHub) notifySubscribers(ev fs.Event) {
h.lock.RLock()
defer h.lock.RUnlock()
ce := ContentEvent{
File: fmt.Sprintf("/%s", ev.File),
Timestamp: strconv.FormatInt(ev.Timestamp.Unix(), baseDecimal),
FileNameHash: hex.EncodeToString(h.FileNameHash.Sum([]byte(path.Base(ev.File)))),
}
for idx := range h.mutations {
ce = h.mutations[idx].OnEvent(ce, ev)
}
for _, handler := range h.subscriptions {
if err := handler.OnEvent(ce); err != nil {
h.Logger.Errorf("Failed to propagate event: %v", err)
}
}
}

7
examples/custom.css Normal file
View file

@ -0,0 +1,7 @@
.reveal h1 {
color: yellow;
}
p {
color: green;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 130 B

View file

@ -1,4 +1,22 @@
theme: night
code-theme: monokai
horizontal-separator: ===
vertical-separator: ---
codeTheme: monokai
horizontalSeparator: ---
verticalSeparator: '\*\*\*'
transition: convex
controlsLayout: edges
controls: true
progress: true
history: true
center: true
slideNumber: true
menu:
numbers: false
useTextContentForMissingTitles: true
transitions: true
hideMissingTitles: false
markers: true
openButton: true
mermaid:
theme: forest
stylesheets:
- custom.css

View file

@ -4,46 +4,106 @@
Content 1.1
Note: This will only appear in the speaker notes window.
---
***
## External 1.2
Content 1.2
===
Note:
This will only display in the notes window.
***
### List & fragments
* unordered <!-- .element: class="fragment" -->
* list <!-- .element: class="fragment" -->
* with `code` <!-- .element: class="fragment" -->
* with __bold__ text <!-- .element: class="fragment" -->
Notes:
- some other points only
- in the nodes
***
### List in HTML
<ul>
<li>List</li>
<li class="fragment">with <code>code</code></li>
<li class="fragment">with <strong>bold</strong> text</li>
</ul>
---
## External 2
Content 2.1
===
---
## External 3.1
Content 3.1
---
***
## External 3.2
Content 3.2
---
***
## External 3.3
![External Image](https://s3.amazonaws.com/static.slid.es/logo/v2/slides-symbol-512x512.png)
===
---
<!-- .slide data-menu-title="Abla Habla" -->
## External 4.1
![Local image](/local/examples/gopher.jpg)
![Local image](/gopher.jpg) <!-- .element: class="r-stretch" -->
---
***
## External 4.2
<a href="https://www.google.com">Google</a>
<a href="https://www.google.com">Google</a>
---
## Code
```csharp
var i = 10;
for (var j = 0; j < i; j++) {
Console.WriteLine($"{j}");
}
```
***
### Mermaid
```mermaid
%%{init: {'theme': 'neutral'}}%%
flowchart LR
a --> b & c--> d
```
***
### The inadequacy of a non-highlighted being
{line-numbers="1-2|3|4"}
```js
let a = 1;
let b = 2;
let c = x => 1 + 2 + x;
c(3);
```

26
fs/layered.go Normal file
View file

@ -0,0 +1,26 @@
package fs
import (
"io/fs"
"os"
)
type (
FS = fs.FS
File = fs.File
)
var Dir = os.DirFS
type Layered struct {
Layers []FS
}
func (l Layered) Open(name string) (file fs.File, err error) {
for idx := range l.Layers {
if file, err = l.Layers[idx].Open(name); err == nil {
return file, nil
}
}
return nil, err
}

66
fs/watching_fs.go Normal file
View file

@ -0,0 +1,66 @@
package fs
import (
"io/fs"
"path/filepath"
"time"
"github.com/fsnotify/fsnotify"
log "github.com/sirupsen/logrus"
)
type Event struct {
File string
Timestamp time.Time
}
func NewWatching(backing FS) (*Watching, error) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return nil, err
}
return &Watching{
watcher: watcher,
backing: backing,
}, nil
}
type Watching struct {
events chan Event
watcher *fsnotify.Watcher
backing FS
}
func (w *Watching) Open(name string) (file fs.File, err error) {
file, err = w.backing.Open(name)
if err == nil {
dir := filepath.Dir(name)
if watchErr := w.watcher.Add(dir); watchErr != nil {
log.Errorf("Failed to watch %s: %v", dir, watchErr)
}
return file, nil
}
return nil, err
}
func (w *Watching) Events() chan Event {
if w.events == nil {
w.events = make(chan Event)
go transportEvents(w.watcher.Events, w.events)
}
return w.events
}
func (w *Watching) Close() error {
return w.watcher.Close()
}
func transportEvents(in <-chan fsnotify.Event, out chan<- Event) {
for ev := range in {
ev.Name = filepath.Base(ev.Name)
out <- Event{
File: ev.Name,
Timestamp: time.Now(),
}
}
}

61
go.mod
View file

@ -1,22 +1,49 @@
module github.com/baez90/go-reveal-slides
module code.icb4dc0.de/prskr/goveal
go 1.12
go 1.20
require (
github.com/fsnotify/fsnotify v1.4.7
github.com/gobuffalo/flect v0.1.3 // indirect
github.com/gobuffalo/packr/v2 v2.2.0
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pty v1.1.4 // indirect
github.com/Masterminds/sprig/v3 v3.2.3
github.com/fsnotify/fsnotify v1.7.0
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd
github.com/google/uuid v1.4.0
github.com/julienschmidt/httprouter v1.3.0
github.com/mitchellh/go-homedir v1.1.0
github.com/sirupsen/logrus v1.4.1
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.2
github.com/stretchr/objx v0.2.0 // indirect
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 // indirect
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba // indirect
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a // indirect
golang.org/x/tools v0.0.0-20190420000508-685fecacd0a0 // indirect
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.17.0
github.com/valyala/bytebufferpool v1.0.0
)
require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

690
go.sum
View file

@ -1,125 +1,611 @@
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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.44.3/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.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
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/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=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
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/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e h1:JbHBQOMhE0wmpSuejnSkdnL2rULqQTwEGgVe85o7+No=
github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5 h1:f3Fpd5AqsFuTHUEhUeEMIFJkX8FpVnzdW+GpYxIyXkA=
github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs=
github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk=
github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0 h1:P6naWPiHm/7R3eYx/ub3VhaW9G+1xAMJ6vzACePaGPI=
github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A=
github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4=
github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
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/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/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/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c h1:iyaGYbCmcYK0Ja9a3OUa2Fo+EaN0cbLu0eKpBwPFzc8=
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a h1:AWZzzFrqyjYlRloN6edwTLTUbKxf5flLXNuTBDm3Ews=
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12 h1:uK3X/2mt4tbSGoHvbLBHUny7CKiuwUip3MArtukol4E=
github.com/gomarkdown/markdown v0.0.0-20230716120725-531d2d74bc12/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20230916125811-7478c230c7cd h1:laCEzrtkKEkT2424vMTGl6N1m0xN8kq371hksD5Be+8=
github.com/gomarkdown/markdown v0.0.0-20230916125811-7478c230c7cd/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd h1:PppHBegd3uPZ3Y/Iax/2mlCFJm1w4Qf/zP1MdW4ju2o=
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
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-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karrick/godirwalk v1.8.0 h1:ycpSqVon/QJJoaT1t8sae0tp1Stg21j+dyuS7OoagcA=
github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
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/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
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/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4/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.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
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/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.3.0 h1:zT7VEGWC2DTflmccN/5T1etyKvxSxpHsjb9cJvm4SvQ=
github.com/sagikazarmark/locafero v0.3.0/go.mod h1:w+v7UsPNFwzF1cHuOajOOzoq4U7v/ig1mpRjqV+Bu1U=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc=
github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
github.com/spf13/viper v1.17.0/go.mod h1:BmMMMLQXSbcHK6KAOiFLz0l5JHrU89OdIRHvsk0+yVI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
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/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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.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.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
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.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 h1:O5YqonU5IWby+w98jVUG9h7zlCWCcH4RHyPVReBmhzk=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
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-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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
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-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/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
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/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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20190419010253-1f3472d942ba/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-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
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/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-20190412183630-56d357773e84 h1:IqXQ59gzdXv58Jmm2xn0tSOR9i6HqroaOFRQ3wR/dJQ=
golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be h1:mI+jhqkn68ybP0ORJqunXn+fq+Eeb4hHKqLQcFICjAc=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a h1:XCr/YX7O0uxRkLq2k1ApNQMims9eCioF9UpzIPBDmuo=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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-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-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-20220715151400-c0bba94af5f8/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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
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/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190418235243-4796d4bd3df0 h1:lbNsjVP0Wn4gPLH85BefnAUMitP3PYyQQbeRTwdAPJw=
golang.org/x/tools v0.0.0-20190418235243-4796d4bd3df0/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190419195823-c39e7748f6eb h1:JbWwiXQ1L1jWKTGSwj6y63WT+bESGWOhXY8xoAs0yoo=
golang.org/x/tools v0.0.0-20190419195823-c39e7748f6eb/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420000508-685fecacd0a0 h1:pa1CyBALPFjblgkNQp7T7gEcFcG/GOG5Ck8IcnSVWGs=
golang.org/x/tools v0.0.0-20190420000508-685fecacd0a0/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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-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-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-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-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
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/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-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-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
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.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/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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/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/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -1,120 +0,0 @@
// Copyright © 2019 Peter Kurfer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/baez90/go-reveal-slides/internal/app/config"
"github.com/fsnotify/fsnotify"
"os"
"github.com/mitchellh/go-homedir"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const (
defaultTheme string = "white"
)
var (
cfgFile string
theme string
codeTheme string
horizontalSeparator string
verticalSeparator string
rootCmd = &cobra.Command{
Use: "goveal",
Short: "goveal is a small reveal.js server",
Long: `goveal is a single static binary to host your reveal.js based markdown presentation.
It is running a small web server that loads your markdown file, renders a complete HTML page and delivers it including all the reveal.js assets.
It is not required to restart the server when you edit the markdown - a simple reload of the page is doing all the required magic.`,
}
params config.RevealParams
)
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
cobra.OnInitialize(initLogging)
rootCmd.PersistentFlags().StringVar(&theme, "theme", defaultTheme, "reveal.js theme to use")
rootCmd.PersistentFlags().StringVar(&codeTheme, "code-theme", "monokai", "name of the code theme to use for highlighting")
rootCmd.PersistentFlags().StringVar(&horizontalSeparator, "horizontal-separator", "===", "horizontal separator in slides")
rootCmd.PersistentFlags().StringVar(&verticalSeparator, "vertical-separator", "---", "vertical separator in slides")
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-reveal-slides.yaml)")
}
func initLogging() {
log.SetFormatter(&log.TextFormatter{
ForceColors: true,
})
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
log.Infof("Failed to determine home directory: %v", err)
} else {
viper.AddConfigPath(home)
}
cwd, err := os.Getwd()
if err != nil {
log.Infof("Failed to determine current working directory: %v", err)
} else {
viper.AddConfigPath(cwd)
}
viper.SetConfigName("goveal")
}
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
log.Info("Using config file:", viper.ConfigFileUsed())
log.Info("Starting to watch config file...")
viper.WatchConfig()
viper.OnConfigChange(func(in fsnotify.Event) {
log.Info("Noticed configuration change...")
params.Load()
})
}
if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil {
log.Errorf("Failed to bind flags to viper")
}
params.Load()
}

View file

@ -1,83 +0,0 @@
// Copyright © 2019 Peter Kurfer peter.kurfer@googlemail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"fmt"
"github.com/baez90/go-reveal-slides/internal/app/rendering"
"github.com/gobuffalo/packr/v2"
"github.com/spf13/cobra"
"net/http"
"os"
log "github.com/sirupsen/logrus"
)
var (
host string
port uint16
serveCmd = &cobra.Command{
Use: "serve",
Args: cobra.ExactArgs(1),
Short: "",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
tmplRenderer, err := rendering.NewRevealRenderer(&params)
if err != nil {
log.Errorf("Failed to initialize reveal renderer due to error: %v", err)
os.Exit(1)
}
markdownHandler, err := rendering.NewMarkdownHandler(args[0])
if err != nil {
log.Errorf("Failed to initialize reveal renderer due to error: %v", err)
os.Exit(1)
}
// Packr2 handler to serve Reveal.js assets
log.Info("Setup reveal assets under route /reveal/ route...")
revealBox := packr.New("reveal-assets", "./../../../assets/reveal")
http.Handle("/reveal/", http.StripPrefix("/reveal/", http.FileServer(revealBox)))
// Static file handler under subroute to serve static files e.g. images
log.Info("Setup static file serving under /local/ route...")
fs := http.FileServer(http.Dir("."))
http.Handle("/local/", http.StripPrefix("/local/", fs))
// single file handler that only delivers the single Markdown file containing the slides
log.Info("Setup markdown handler under /markdown/content.md route...")
http.Handle("/markdown/", markdownHandler)
// entrypoint that delivers the rendered reveal.js index HTML page
http.Handle("/", tmplRenderer)
// start HTTP server
hostPort := fmt.Sprintf("%s:%d", host, port)
log.Infof("Running at addr http://%s/", hostPort)
if err := http.ListenAndServe(hostPort, nil); err != nil {
log.Error("Error while running serve command: %v", err)
}
},
}
)
func init() {
rootCmd.AddCommand(serveCmd)
serveCmd.Flags().StringVar(&host, "host", "localhost", "host the CLI should listen on")
serveCmd.Flags().Uint16Var(&port, "port", 2233, "port the CLI should listen on")
}

View file

@ -1,31 +0,0 @@
// Copyright © 2019 Peter Kurfer peter.kurfer@googlemail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import "github.com/spf13/viper"
type RevealParams struct {
Theme string
CodeTheme string
HorizontalSeparator string
VerticalSeparator string
}
func (params *RevealParams) Load() {
params.Theme = viper.GetString("theme")
params.CodeTheme = viper.GetString("code-theme")
params.HorizontalSeparator = viper.GetString("horizontal-separator")
params.VerticalSeparator = viper.GetString("vertical-separator")
}

View file

@ -1,52 +0,0 @@
// Copyright © 2019 Peter Kurfer peter.kurfer@googlemail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rendering
import (
"fmt"
"net/http"
"os"
"path"
)
type MarkdownHandler interface {
http.Handler
}
func NewMarkdownHandler(markdownPath string) (handler MarkdownHandler, err error) {
var info os.FileInfo
info, err = os.Stat(markdownPath)
if err != nil {
return
}
if info.IsDir() || path.Ext(info.Name()) != ".md" {
err = fmt.Errorf("path %s did not pass sanity checks for markdown files", markdownPath)
return
}
handler = &markdownHandler{
markdownPath: markdownPath,
}
return
}
type markdownHandler struct {
markdownPath string
}
func (handler *markdownHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
http.ServeFile(response, request, handler.markdownPath)
}

View file

@ -1,77 +0,0 @@
// Copyright © 2019 Peter Kurfer peter.kurfer@googlemail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package rendering
import (
"github.com/baez90/go-reveal-slides/internal/app/config"
"github.com/gobuffalo/packr/v2"
log "github.com/sirupsen/logrus"
"html/template"
"net/http"
)
type RevealRenderer interface {
http.Handler
init() error
}
func NewRevealRenderer(params *config.RevealParams) (renderer RevealRenderer, err error) {
renderer = &revealRenderer{
params: params,
}
err = renderer.init()
return
}
type revealRenderer struct {
template *template.Template
renderedTemplate string
params *config.RevealParams
}
func (renderer *revealRenderer) init() (err error) {
templateBox := packr.New("rendering", "./../../../assets/template")
templateString, err := templateBox.FindString("reveal-markdown.tmpl")
if err != nil {
return
}
renderer.template, err = template.New("index").Parse(templateString)
return
}
func (renderer *revealRenderer) ServeHTTP(response http.ResponseWriter, request *http.Request) {
if renderer.template == nil {
writeErrorResponse(500, "rendering is not set - probably error during startup", response)
return
}
err := renderer.template.Execute(response, struct {
Reveal config.RevealParams
}{Reveal: *renderer.params})
if err != nil {
writeErrorResponse(500, "Failed to render Markdown to rendering", response)
log.Errorf("Failed to render Markdown rendering: %v", err)
}
}
func writeErrorResponse(code int, msg string, response http.ResponseWriter) {
response.WriteHeader(code)
_, err := response.Write([]byte(msg))
log.Errorf("Failed to write error reponse: %v", err)
}

View file

@ -1,20 +0,0 @@
// Copyright © 2019 Peter Kurfer peter.kurfer@googlemail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package go_reveal_slides
import (
_ "github.com/baez90/go-reveal-slides/internal/app/cmd"
_ "github.com/baez90/go-reveal-slides/internal/app/rendering"
)

32
rendering/emoji/emoji.go Normal file
View file

@ -0,0 +1,32 @@
package emoji
import (
_ "embed"
"encoding/json"
)
func isValidEmoji(input []byte) bool {
_, exists := emojiMap[string(input)]
return exists
}
var (
//go:embed emoji.json
emojiMapRaw []byte
emojiMap map[string]string
)
func init() {
rawMap := make(map[string][]string)
if err := json.Unmarshal(emojiMapRaw, &rawMap); err != nil {
panic(err)
}
emojiMap = make(map[string]string, len(rawMap))
for emoji, keywords := range rawMap {
for i := range keywords {
emojiMap[keywords[i]] = emoji
}
}
}

View file

@ -0,0 +1,109 @@
package emoji
import (
"bytes"
"fmt"
"strconv"
"strings"
"unicode/utf8"
"github.com/gomarkdown/markdown/ast"
)
func NewEmojiParser() *EmojiParser {
return &EmojiParser{
seen: make(map[string]bool),
}
}
// Node is a node containing an emoji
type Node struct {
ast.Leaf
}
type EmojiParser struct {
seen map[string]bool
}
func (p *EmojiParser) EmojiParser(data []byte) (parsedNode ast.Node, result []byte, newLength int) {
if p.seen[string(data)] {
// Already processed
return nil, nil, 0
}
if bytes.Contains(data, []byte("class=\"emoji\"")) {
// Already processed
return nil, nil, 0
}
dataLen := len(data)
if dataLen <= 1 {
// Not long enough to be an emoji
return nil, nil, 0
}
if bytes.IndexByte(data, ':') == -1 {
// No emoji delimiters
return nil, nil, 0
}
// Translate emojis to HTML
resData := make([]byte, 0)
startIndex := bytes.IndexByte(data, ':')
resData = append(resData, data[0:startIndex]...)
for {
if startIndex >= len(data) {
// Done
break
}
endIndex := bytes.IndexByte(data[startIndex+1:], ':') + startIndex + 1
if endIndex > startIndex {
name := string(data[startIndex+1 : endIndex])
if isValidEmoji([]byte(name)) {
startIndex = endIndex + 1
url := fmt.Sprintf(`<img class="emoji" src=%q alt=":%s:"></img>`, GenerateEmojiURL(name), name)
resData = append(resData, []byte(url)...)
} else {
resData = append(resData, data[startIndex:endIndex]...)
startIndex = endIndex
}
if startIndex == dataLen {
break
}
} else {
break
}
}
if startIndex < dataLen {
resData = append(resData, data[startIndex:]...)
}
if !bytes.Contains(resData, []byte("class=\"emoji\"")) {
// Processed with no changes
p.seen[string(resData)] = true
}
return &ast.Softbreak{}, resData, dataLen
}
func GenerateEmojiURL(emoji string) string {
code, exists := emojiMap[emoji]
if !exists {
return ""
}
res := ""
chars := utf8.RuneCountInString(code)
curChar := 1
for _, c := range code {
tmp := strings.Trim(strings.ToLower(strconv.QuoteRuneToASCII(c)), "'")
if tmp != `\ufe0f` || (chars > 2 && curChar == chars) {
// Valid character to add
if curChar > 1 {
res += "-"
}
if len(tmp) == 1 {
res += fmt.Sprintf("%x", []byte(tmp))
} else {
res += strings.TrimLeft(tmp[2:], "0")
}
}
curChar++
}
return fmt.Sprintf("https://twemoji.maxcdn.com/2/svg/%s.svg", res)
}

90
rendering/html.go Normal file
View file

@ -0,0 +1,90 @@
package rendering
import (
"fmt"
"html/template"
"regexp"
"github.com/gomarkdown/markdown/parser"
"github.com/valyala/bytebufferpool"
"code.icb4dc0.de/prskr/goveal/config"
)
const (
parserExtensions = parser.NoIntraEmphasis | parser.Tables | parser.FencedCode |
parser.Autolink | parser.Strikethrough | parser.SpaceHeadings | parser.HeadingIDs |
parser.BackslashLineBreak | parser.DefinitionLists | parser.MathJax | parser.Titleblock |
parser.OrderedListStart | parser.Attributes
)
func ToHTML(markdown string, renderCfg config.Rendering) (rendered []byte, err error) {
var slides []rawSlide
if slides, err = splitIntoRawSlides(markdown, renderCfg); err != nil {
return nil, err
}
buf := bytebufferpool.Get()
defer func() {
buf.Reset()
bytebufferpool.Put(buf)
}()
for idx := range slides {
if rendered, err := slides[idx].ToHTML(); err != nil {
return nil, err
} else {
_, _ = buf.WriteString(string(rendered))
}
}
return buf.Bytes(), nil
}
type rawSlide struct {
Content string
Children []rawSlide
}
func (s rawSlide) HasNotes() bool {
return notesLineRegexp.MatchString(s.Content)
}
func (s rawSlide) ToHTML() (template.HTML, error) {
if rendered, err := renderTemplate("slide.gohtml", s); err != nil {
return "", err
} else {
//nolint:gosec // should not be sanitized
return template.HTML(rendered), nil
}
}
func splitIntoRawSlides(markdown string, renderCfg config.Rendering) ([]rawSlide, error) {
var (
verticalSplit, horizontalSplit *regexp.Regexp
err error
)
if verticalSplit, err = regexp.Compile(fmt.Sprintf(splitFormat, renderCfg.VerticalSeparator)); err != nil {
return nil, err
}
if horizontalSplit, err = regexp.Compile(fmt.Sprintf(splitFormat, renderCfg.HorizontalSeparator)); err != nil {
return nil, err
}
horizontalSlides := horizontalSplit.Split(markdown, -1)
slides := make([]rawSlide, 0, len(horizontalSlides))
for _, hs := range horizontalSlides {
s := rawSlide{
Content: hs,
}
verticalSlides := verticalSplit.Split(hs, -1)
s.Children = make([]rawSlide, 0, len(verticalSlides))
for _, vs := range verticalSlides {
s.Children = append(s.Children, rawSlide{Content: vs})
}
slides = append(slides, s)
}
return slides, nil
}

19
rendering/patterns.go Normal file
View file

@ -0,0 +1,19 @@
package rendering
import (
"fmt"
"regexp"
)
const (
// language=regexp
notesRegex = `(?i)notes?:`
// language=regexp
splitFormat = `\r?\n%s\r?\n`
)
var (
htmlElementAttributesRegexp = regexp.MustCompile(`(?P<key>[a-z]+(-[a-z]+)*)="(?P<value>.+)"`)
notesRegexp = regexp.MustCompile(fmt.Sprintf(`^%s`, notesRegex))
notesLineRegexp = regexp.MustCompile(fmt.Sprintf(`\r?\n%s\r?\n`, notesRegex))
)

View file

@ -0,0 +1,171 @@
package rendering
import (
"bytes"
"encoding/hex"
"hash"
"html"
"html/template"
"io"
"path"
"github.com/gomarkdown/markdown/ast"
)
const (
mermaidCodeBlock = "mermaid"
)
type RevealRenderer struct {
Hash hash.Hash
}
func (r *RevealRenderer) RenderHook(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
switch b := node.(type) {
case *ast.ListItem:
if entering {
return r.handleListItem(w, b)
}
return ast.GoToNext, false
case *ast.Text:
if !entering {
return ast.GoToNext, false
}
if notesRegexp.Match(b.Literal) {
_, err := w.Write([]byte(`<aside class="notes">`))
return ast.SkipChildren, err == nil
}
return ast.GoToNext, false
case *ast.CodeBlock:
if entering {
return r.handleCodeBlock(w, b)
}
return ast.GoToNext, false
case *ast.Image:
if entering {
return r.handleImage(w, b)
}
return ast.GoToNext, false
default:
return ast.GoToNext, false
}
}
func (r *RevealRenderer) handleCodeBlock(w io.Writer, code *ast.CodeBlock) (ast.WalkStatus, bool) {
code.Info = bytes.ToLower(code.Info)
switch string(code.Info) {
case mermaidCodeBlock:
output, err := renderCodeTemplate("mermaid.gohtml", code)
if err != nil {
return ast.GoToNext, false
}
_, err = w.Write(output)
return ast.GoToNext, err == nil
default:
output, err := renderCodeTemplate("any-code.gohtml", code)
if err != nil {
return ast.GoToNext, false
}
_, err = w.Write(output)
return ast.GoToNext, err == nil
}
}
func (r *RevealRenderer) handleListItem(w io.Writer, listItem *ast.ListItem) (ast.WalkStatus, bool) {
for _, child := range listItem.Children {
if p, ok := child.(*ast.Paragraph); ok {
if len(p.Children) == 0 {
return ast.GoToNext, false
}
data := map[string]any{
"Attributes": getAttributesFromChildSpan(p),
}
if rendered, err := renderTemplate("listItem.gohtml", data); err != nil {
return ast.GoToNext, false
} else if _, err = w.Write(rendered); err != nil {
return ast.GoToNext, false
}
return ast.GoToNext, true
}
}
return ast.GoToNext, false
}
func (r *RevealRenderer) handleImage(w io.Writer, img *ast.Image) (ast.WalkStatus, bool) {
var title string
if len(img.Children) >= 1 {
if txt, ok := img.Children[0].(*ast.Text); ok {
title = string(txt.Literal)
}
}
data := map[string]any{
"ID": hex.EncodeToString(r.Hash.Sum([]byte(path.Base(string(img.Destination))))),
"Attributes": getAttributesFromChildSpan(img.GetParent()),
"ImageSource": string(img.Destination),
"AlternativeText": html.EscapeString(title),
}
if rendered, err := renderTemplate("image.gohtml", data); err != nil {
return ast.GoToNext, false
} else if _, err = w.Write(rendered); err != nil {
return ast.GoToNext, false
}
return ast.SkipChildren, true
}
func getAttributesFromChildSpan(node ast.Node) []template.HTMLAttr {
if getChildren, ok := node.(interface{ GetChildren() []ast.Node }); ok {
childs := getChildren.GetChildren()
if len(childs) == 0 {
return nil
}
for idx := range childs {
if span, ok := childs[idx].(*ast.HTMLSpan); ok {
return extractElementAttributes(span)
}
}
}
return nil
}
func extractElementAttributes(htmlSpan *ast.HTMLSpan) (attrs []template.HTMLAttr) {
const expectedNumberOfMatches = 4
htmlComment := string(htmlSpan.Literal)
if htmlComment == "" {
return nil
}
matches := htmlElementAttributesRegexp.FindAllStringSubmatch(htmlComment, -1)
attrs = make([]template.HTMLAttr, 0, len(matches))
for idx := range matches {
if len(matches[idx]) != expectedNumberOfMatches {
continue
}
//nolint:gosec // it's the user's responsibility to not skrew this up here
attrs = append(attrs, template.HTMLAttr(matches[idx][0]))
}
return attrs
}
func renderCodeTemplate(templateName string, codeBlock *ast.CodeBlock) (output []byte, err error) {
data := map[string]any{
//nolint:gosec // need to embed the code in original format without escaping
"Code": template.HTML(codeBlock.Literal),
"LineNumbers": lineNumbers(codeBlock.Attribute),
}
return renderTemplate(templateName, data)
}
func lineNumbers(attrs *ast.Attribute) string {
if attrs == nil || attrs.Attrs == nil {
return ""
}
return string(attrs.Attrs["line-numbers"])
}

60
rendering/templates.go Normal file
View file

@ -0,0 +1,60 @@
package rendering
import (
"embed"
"hash/fnv"
"html/template"
"github.com/Masterminds/sprig/v3"
"github.com/gomarkdown/markdown"
mdhtml "github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/valyala/bytebufferpool"
"code.icb4dc0.de/prskr/goveal/rendering/emoji"
)
var (
//go:embed templates/*.gohtml
templatesFS embed.FS
templates *template.Template
)
func init() {
templates = template.New("rendering").
Funcs(sprig.FuncMap()).
Funcs(template.FuncMap{
"renderMarkdown": func(md string) template.HTML {
rr := &RevealRenderer{
Hash: fnv.New32a(),
}
emojis := emoji.NewEmojiParser()
mdParser := parser.NewWithExtensions(parserExtensions)
mdParser.Opts.ParserHook = emojis.EmojiParser
renderer := mdhtml.NewRenderer(mdhtml.RendererOptions{
Flags: mdhtml.CommonFlags | mdhtml.HrefTargetBlank,
RenderNodeHook: rr.RenderHook,
})
renderedHTML := markdown.ToHTML([]byte(md), mdParser, renderer)
//nolint:gosec // template should be esacped
return template.HTML(renderedHTML)
},
})
var err error
if templates, err = templates.ParseFS(templatesFS, "templates/*.gohtml"); err != nil {
panic(err)
}
}
func renderTemplate(templateName string, data any) (output []byte, err error) {
buffer := bytebufferpool.Get()
defer func() {
buffer.Reset()
bytebufferpool.Put(buffer)
}()
err = templates.ExecuteTemplate(buffer, templateName, data)
return buffer.Bytes(), err
}

View file

@ -0,0 +1,6 @@
<pre><code
{{ if .LineNumbers }} data-line-numbers="{{ .LineNumbers }}" {{ end }}
data-trim
data-noescape>
{{- .Code }}
</code></pre>

View file

@ -0,0 +1,8 @@
<img
data-src="{{ .ImageSource }}"
alt="{{ .AlternativeText }}"
id="{{ .ID }}"
{{ range .Attributes }}
{{- . }}
{{ end }}
/>

View file

@ -0,0 +1,5 @@
<li
{{ range $key, $value := .Attributes }}
{{ $value }}
{{ end }}
>

View file

@ -0,0 +1,3 @@
<div class="mermaid">
{{- .Code -}}
</div>

View file

@ -0,0 +1,12 @@
<section>
{{ if .Children }}
{{ range .Children }}
{{ .ToHTML }}
{{ end }}
{{ else }}
{{ renderMarkdown .Content }}
{{ end }}
{{ if .HasNotes }}
</aside>
{{ end }}
</section>

5
renovate.json Normal file
View file

@ -0,0 +1,5 @@
{
"extends": [
"config:base"
]
}

7
web/css/site.css Normal file
View file

@ -0,0 +1,7 @@
img.emoji {
cursor: pointer;
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}

37
web/index.gohtml Normal file
View file

@ -0,0 +1,37 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>goveal</title>
<link rel="stylesheet" href="/css/site.css">
<link rel="stylesheet" href="/reveal/dist/reveal.css">
<link rel="stylesheet" href="/reveal/dist/reset.css">
<link rel="stylesheet" href="/reveal/dist/theme/{{ .Reveal.Theme }}.css" id="theme">
<link rel="stylesheet" href="/reveal/plugin/highlight/{{ .Reveal.CodeTheme }}.css">
{{ range .Rendering.Stylesheets}}
<link id="{{ fileId . }}" rel="stylesheet" href="/{{ . }}">
{{ end }}
</head>
<body>
<div class="reveal">
<div id="content-root" class="slides">
</div>
</div>
<script src="/reveal/dist/reveal.js"></script>
<script src="/reveal/plugin/highlight/highlight.js"></script>
<script src="/reveal/plugin/notes/notes.js"></script>
<script src="/reveal/plugin/menu/menu.js"></script>
<script src="/mermaid/mermaid.min.js"></script>
<script src="/js/app.js"></script>
</body>
</html>

132
web/js/app.js Normal file
View file

@ -0,0 +1,132 @@
document.addEventListener("DOMContentLoaded", _ => {
Promise.all([initMermaid(), setSlidesContent()])
.then(() => {
return initReveal()
})
.then(() => {
subscribeToEvents()
})
});
async function setSlidesContent() {
let resp = await fetch("/slides")
let contentText = await resp.text()
let parser = new DOMParser()
let contentDocument = parser.parseFromString(contentText, 'text/html')
for (let mermaidElem of contentDocument.getElementsByClassName("mermaid")) {
let insertSVG = (svgCode, _) => {
mermaidElem.innerHTML = svgCode
}
mermaid.mermaidAPI.render('mermaid', mermaidElem.innerText, insertSVG)
}
document.getElementById("content-root").innerHTML = contentDocument.documentElement.innerHTML
}
async function getRevealConfig() {
let resp = await fetch('/api/v1/config/reveal')
return await resp.json()
}
async function initReveal() {
let cfg = await getRevealConfig()
Reveal.initialize({
controls: cfg.controls,
controlsLayout: cfg.controlsLayout,
progress: cfg.progress,
history: cfg.history,
center: cfg.center,
slideNumber: cfg.slideNumber,
transition: cfg.transition,
width: cfg.width,
height: cfg.height,
hash: true,
pdfSeparateFragments: false,
menu: {
numbers: cfg.menu.numbers,
useTextContentForMissingTitles: cfg.menu.useTextContentForMissingTitles,
transitions: cfg.menu.transitions,
hideMissingTitles: cfg.hideMissingTitles,
markers: cfg.menu.markers,
openButton: cfg.menu.openButton,
custom: [
{
title: 'Print',
icon: '<i class="fas fa-print"></i>',
content: '<a href="/?print-pdf">Go to print view<a/>'
}
],
themes: [
{name: 'Beige', theme: '/reveal/dist/theme/beige.css'},
{name: 'Black', theme: '/reveal/dist/theme/black.css'},
{name: 'Blood', theme: '/reveal/dist/theme/blood.css'},
{name: 'League', theme: '/reveal/dist/theme/league.css'},
{name: 'Moon', theme: '/reveal/dist/theme/moon.css'},
{name: 'Night', theme: '/reveal/dist/theme/night.css'},
{name: 'Serif', theme: '/reveal/dist/theme/serif.css'},
{name: 'Simple', theme: '/reveal/dist/theme/simple.css'},
{name: 'Sky', theme: '/reveal/dist/theme/sky.css'},
{name: 'Solarized', theme: '/reveal/dist/theme/solarized.css'},
{name: 'White', theme: '/reveal/dist/theme/white.css'}
],
},
plugins: [RevealHighlight, RevealNotes, RevealMenu]
})
}
async function initMermaid() {
let resp = await fetch('/api/v1/config/mermaid')
let cfg = await resp.json()
mermaid.parseError = (err, hash) => {
console.error(`Failed to parse Mermaid diagraph: ${err} - ${hash}`)
}
mermaid.initialize({
startOnLoad: false,
theme: cfg.theme,
securityLevel: 'loose',
});
}
function subscribeToEvents() {
let eventSource = new EventSource("/api/v1/events");
eventSource.onopen = (() => {
console.debug("eventsource connection open");
})
eventSource.onerror = (ev => {
if (ev.target.readyState === 0) {
console.debug("reconnecting to eventsource");
} else {
console.error("eventsource error", ev);
}
})
eventSource.onmessage = (ev => {
let obj = JSON.parse(ev.data);
switch (true) {
case obj.forceReload:
eventSource.close()
window.location.reload()
break
case obj.reloadConfig:
getRevealConfig().then(cfg => {
Reveal.configure(cfg)
})
break
default:
switch (true) {
case obj.file.endsWith(".css"):
let cssLink = document.querySelector(`link[rel=stylesheet][id="${obj.fileNameHash}"]`);
cssLink.href = `${obj.file}?ts=${obj.ts}`
break
default:
let elem = document.getElementById(obj.fileNameHash);
if (elem !== null) {
elem.src = `${obj.file}?ts=${obj.ts}`
}
}
}
})
}

6
web/web.go Normal file
View file

@ -0,0 +1,6 @@
package web
import "embed"
//go:embed js/* css/* index.gohtml
var WebFS embed.FS