Draft: First draft of new fiber based server #7

Merged
baez90 merged 16 commits from migrate-to-fiber into master 2022-03-15 16:39:37 +00:00
58 changed files with 1978 additions and 1289 deletions

1
.gitattributes vendored Normal file
View file

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

View file

@ -1,6 +1,13 @@
name: Go
on: [ push, pull_request ]
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
jobs:
@ -16,7 +23,7 @@ jobs:
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
lfs: true
@ -24,7 +31,7 @@ jobs:
uses: arduino/setup-task@v1
- name: Build & test
run: task download-reveal test
run: task download-assets test
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2

46
.github/workflows/golangci-lint.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: golangci-lint
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Task
uses: arduino/setup-task@v1
- name: Download assets
run: task download-assets
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
# Optional: working directory, useful for monorepos
# working-directory: somedir
# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0
# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true
# Optional: if set to true then the action will use pre-installed Go.
# skip-go-installation: true
# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true
# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true

4
.gitignore vendored
View file

@ -5,6 +5,8 @@
*.so
*.dylib
emoji.json
# Test binary, build with `go test -c`
*.test
@ -22,6 +24,8 @@
bin/
assets/reveal/
assets/mermaid/
assets/twemoji/
pkged.go

121
.golangci.yml Normal file
View file

@ -0,0 +1,121 @@
linters-settings:
dupl:
threshold: 100
funlen:
lines: 100
statements: 50
gci:
local-prefixes: github.com/baez90/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: github.com/baez90/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:
- deadcode
- dogsled
- dupl
- errcheck
- exhaustive
- exportloopref
- funlen
- gocognit
- goconst
- gocritic
- gocyclo
- godox
- gofumpt
- goimports
- gomnd
- gosec
- gosimple
- govet
- importas
- ineffassign
- lll
- misspell
- nakedret
- nestif
- noctx
- nolintlint
- paralleltest
- prealloc
- promlinter
- staticcheck
- structcheck
- stylecheck
- testpackage
- thelper
- typecheck
- unconvert
- unparam
- varcheck
- whitespace
#- unused
issues:
# Excluding configuration per-path, per-linter, per-text and per-source
exclude-rules:
- path: _test\.go
linters:
- gomnd
- funlen
run:
build-tags:
- sudo
skip-dirs:
- internal/mock
skip-files:
- ".*\\.pb\\.go$"
- ".*.mock.\\.go$"
service:
golangci-lint-version: 1.43.x # use the fixed version to not introduce new linters unexpectedly

View file

@ -1,6 +1,6 @@
project_name: goveal
builds:
- main: ./cmd/goveal/main.go
- main: ./cmd/goveal/
binary: goveal
goos:
- linux
@ -16,10 +16,23 @@ builds:
- CGO_ENABLED=0
ignore:
- goos: darwin
goarch: 386
goarch: '386'
- goos: linux
goarch: arm
goarm: 7
goarm: '7'
dockers:
- id: goveal
goos: linux
goarch: amd64
goarm: ''
ids:
- goveal
image_templates:
- ghcr.io/baez90/goveal:{{ .Tag }}
- ghcr.io/baez90/goveal:{{ .Major }}
dockerfile: ./build/docker/goveal.dockerfile
archives:
- replacements:
darwin: Darwin

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

View file

@ -2,14 +2,24 @@
[![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 a HTML template and
serves it as local HTTP server. Right now Goveal uses Reveal.js 4.1.2 to create presentations and therefore also
includes all features of Reveal.js 4.1.2.
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
@ -21,46 +31,94 @@ 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!_
_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
### Local installation
```bash
goveal serve ./slides.md
```
| Param | Description | Default value |
| ------------------------ | ---------------------------------------------------------------------------- | ----------------------- |
| `--host` | Hostname the binary is listening on | `localhost` |
|------------------|-----------------------------------------------|-------------------------|
| `--host` | Hostname the binary is listening on | `127.0.0.1` |
| `--port` | Port the binary is listening on | `2233` |
| `--code-theme` | highlight.js theme to use | `monokai` |
| `--transition` | Transition effect to show between slides | `none` |
| `--navigationMode` | Navigation mode to use when using the cursor keys to navigate through slides | `default` |
| `--config` | Path to the config file see [config](#config) | `$HOME/goveal:./goveal` |
| `--horizontal-separator` | horizontal separator to split slides | `===` |
| `--vertical-separator` | vertical separator to split slides | `---` |
| `--theme` | reveal.js theme to use | `white` |
| `--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.
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
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).
I try to keep the example up to date to cover **all** supported config options as kind of documentation.
### Custom CSS
## 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.
the sample configuration file [`./examples/goveal.yaml`](./examples/goveal.yaml) also contains a sample how to add
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).

View file

@ -2,8 +2,9 @@ version: '3'
vars:
DEBUG_PORT: 2345
REVEALJS_VERSION: 4.1.2
HIGHLIGHTJS_VERSION: 11.3.1
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"
@ -32,6 +33,8 @@ tasks:
- "{{ .GOBIN }}goimports -w -local github.com/baez90/goveal ./."
deps:
deps:
- download-assets
sources:
- go.mod
- go.sum
@ -85,18 +88,20 @@ tasks:
cmds:
- goreleaser --snapshot --skip-publish --rm-dist
download-reveal:
download-assets:
sources:
- Taskfile.yml
cmds:
- rm -rf ./assets/reveal
- mkdir -p ./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:

36
api/config.go Normal file
View file

@ -0,0 +1,36 @@
package api
import (
"encoding/json"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/baez90/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 {
writer.WriteHeader(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"
"github.com/baez90/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"
"github.com/baez90/goveal/assets"
"github.com/baez90/goveal/fs"
"github.com/baez90/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)
}
})
}

99
api/views.go Normal file
View file

@ -0,0 +1,99 @@
package api
import (
"encoding/hex"
"hash/fnv"
"html/template"
"io"
"net/http"
"path"
"github.com/Masterminds/sprig/v3"
"github.com/julienschmidt/httprouter"
log "github.com/sirupsen/logrus"
"go.uber.org/multierr"
"github.com/baez90/goveal/config"
"github.com/baez90/goveal/fs"
"github.com/baez90/goveal/rendering"
"github.com/baez90/goveal/web"
)
var indexTmpl *template.Template
func init() {
if t, err := template.
New("index").
Funcs(sprig.FuncMap()).
Funcs(map[string]interface{}{
"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]interface{}{
"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 multierr.AppendInvoke(&err, multierr.Close(f))
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
}
}

View file

@ -2,9 +2,5 @@ package assets
import "embed"
var (
//go:embed template/reveal-markdown.tmpl
Template []byte
//go:embed web reveal
Assets embed.FS
)
//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

@ -1,24 +0,0 @@
let knownHashes = {}
function getLatestHash(path) {
let request = new XMLHttpRequest()
request.open("GET", `/hash/md5${path}`)
request.onload = () => {
if(request.status === 200) {
let hashResp = JSON.parse(request.responseText)
if(path in knownHashes && knownHashes[path] !== hashResp["Hash"]) {
window.location.reload()
} else {
knownHashes[path] = hashResp["Hash"]
}
}
}
request.send()
}
function subscribeForUpdates(path) {
setInterval(() => {
getLatestHash(path)
}, 1000)
}

View file

@ -0,0 +1,9 @@
FROM gcr.io/distroless/static:nonroot
USER nonroot:nonroot
COPY --chown=nonroot:nonroot goveal /app/goveal
EXPOSE 2233
ENTRYPOINT ["/app/goveal"]

View file

@ -15,9 +15,11 @@
package main
import (
"github.com/baez90/goveal/internal/app/cmd"
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"
"github.com/baez90/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)")
}

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

@ -0,0 +1,109 @@
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"
"go.uber.org/multierr"
"github.com/baez90/goveal/api"
"github.com/baez90/goveal/config"
"github.com/baez90/goveal/events"
"github.com/baez90/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 multierr.AppendInvoke(&err, multierr.Close(wdfs))
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]interface{}{
"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
}

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"
"github.com/baez90/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)
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 130 B

View file

@ -1,9 +1,22 @@
theme: night
codeTheme: monokai
horizontalSeparator: ===
verticalSeparator: ---
transition: fade
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
filesToMonitor:
- ./**/*.css

View file

@ -4,51 +4,78 @@
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](/examples/gopher.jpg)
![Local image](/gopher.jpg) <!-- .element: class="r-stretch" -->
---
***
## External 4.2
<a href="https://www.google.com">Google</a>
===
---
## Code
@ -58,3 +85,25 @@ 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(),
}
}
}

29
go.mod
View file

@ -4,39 +4,42 @@ go 1.17
require (
github.com/Masterminds/sprig/v3 v3.2.2
github.com/bmatcuk/doublestar/v2 v2.0.4
github.com/fsnotify/fsnotify v1.5.1
github.com/imdario/mergo v0.3.12
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8
github.com/google/uuid v1.3.0
github.com/julienschmidt/httprouter v1.3.0
github.com/mitchellh/go-homedir v1.1.0
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.9.0
go.uber.org/multierr v1.7.0
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.10.1
go.uber.org/multierr v1.8.0
)
require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e // indirect
golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58 // indirect
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

292
go.sum
View file

@ -3,6 +3,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
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=
@ -15,14 +16,7 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.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=
@ -31,8 +25,6 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
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=
@ -42,6 +34,7 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
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=
@ -51,18 +44,7 @@ github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030I
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bmatcuk/doublestar/v2 v2.0.4 h1:6I6oUiT/sU27eE2OFcWqBhL1SwjyvQuOssxT4a1yidI=
github.com/bmatcuk/doublestar/v2 v2.0.4/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/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=
@ -70,10 +52,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -83,20 +62,12 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -108,8 +79,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -124,10 +93,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8 h1:YVvt637ygnOO9qjLBVmPOvrUmCz/i8YECSu/8UlOQW0=
github.com/gomarkdown/markdown v0.0.0-20220310201231-552c6011c0b8/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=
@ -138,15 +105,10 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -156,11 +118,7 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/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=
@ -168,38 +126,11 @@ 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/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/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
@ -210,102 +141,62 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
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.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
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/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/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 v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
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.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
@ -314,37 +205,27 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
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.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58 h1:L8CkJyVoa0/NslN3RUMLgasK5+KatNvyRGQ9QyCYAfc=
golang.org/x/crypto v0.0.0-20220314234724-5d542ad81a58/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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=
@ -368,7 +249,6 @@ golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRu
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
@ -379,11 +259,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -394,7 +271,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
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-20190923162816-aa69164e4478/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=
@ -411,13 +287,9 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/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=
@ -427,13 +299,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/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=
@ -444,12 +309,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -457,18 +318,12 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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=
@ -486,23 +341,12 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e h1:zeJt6jBtVDK23XK9QXcmG0FvO0elikp0dYZQZOeL1y0=
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
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=
@ -510,8 +354,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -523,7 +365,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@ -531,10 +372,8 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
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-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -557,7 +396,6 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
@ -566,13 +404,8 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/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.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -596,15 +429,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
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=
@ -635,7 +459,6 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
@ -647,23 +470,8 @@ google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-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=
@ -677,19 +485,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -700,20 +498,14 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/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/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View file

@ -1,132 +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"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"github.com/baez90/goveal/internal/app/rendering"
"github.com/mitchellh/go-homedir"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
workingDir 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 rendering.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(initLogging)
cobra.OnInitialize(initConfig)
var err error
workingDir, err = os.Getwd()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.go-reveal-slides.yaml)")
rootCmd.PersistentFlags().StringVar(&workingDir, "working-dir", workingDir, "working directory to use")
}
func initLogging() {
log.SetFormatter(&log.TextFormatter{
ForceColors: true,
})
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
var err error
if workingDir, err = filepath.Abs(workingDir); err != nil {
log.Warnf("Failed to determine absolute path for working dir %s: %v", workingDir, err)
return
}
var cwd string
if cwd, err = os.Getwd(); err != nil {
log.Warnf("Failed to determine current working directory")
return
}
if cwd != workingDir {
if err = os.Chdir(workingDir); err != nil {
log.Warnf("Failed to change working directory to %s", workingDir)
}
}
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)
}
viper.AddConfigPath(workingDir)
viper.SetConfigName("goveal")
viper.SetConfigType("yaml")
}
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...")
if err := params.Load(); err != nil {
log.Warnf("Failed to load config: %v", err)
}
})
}
params.WorkingDirectory = workingDir
if err := params.Load(); err != nil {
log.Warnf("Failed to load config: %v", err)
}
}

View file

@ -1,94 +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 (
"errors"
"fmt"
"net/http"
"os/exec"
"runtime"
"github.com/spf13/cobra"
"github.com/baez90/goveal/internal/app/server"
log "github.com/sirupsen/logrus"
)
var (
host string
port uint16
openBrowser bool
serveCmd = &cobra.Command{
Use: "serve",
Args: cobra.ExactArgs(1),
Short: "",
Long: ``,
RunE: func(cmd *cobra.Command, args []string) (err error) {
var srv *server.HTTPServer
srv, err = server.NewHTTPServer(server.Config{
Host: host,
Port: port,
MarkdownPath: args[0],
RevealParams: &params,
})
if err != nil {
log.Errorf("Error while setting up server: %v", err)
return
}
listenUrl := fmt.Sprintf("http://%s/", srv.ListenAddress())
log.Infof("Going to listen on %s", listenUrl)
if openBrowser {
log.Info("Opening browser...")
openBrowserInBackground(listenUrl)
}
if err = srv.Serve(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Errorf("Error while running serve command: %v", err)
}
return nil
},
}
)
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")
serveCmd.Flags().BoolVar(&openBrowser, "open-browser", true, "if the browser should be opened at the URL")
}
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)
}
}

View file

@ -1,89 +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 (
"path"
"path/filepath"
"github.com/bmatcuk/doublestar/v2"
"github.com/imdario/mergo"
"github.com/spf13/viper"
"github.com/baez90/goveal/internal/encoding"
)
var defaultParams = RevealParams{
Theme: "white",
CodeTheme: "vs",
Transition: "None",
NavigationMode: "default",
HorizontalSeparator: "===",
VerticalSeparator: "---",
SlideNumberVisibility: "all",
SlideNumberFormat: "h.v",
StyleSheets: make([]string, 0),
FilesToMonitor: make([]string, 0),
}
type RevealParams struct {
Theme string `mapstructure:"theme"`
CodeTheme string `mapstructure:"codeTheme"`
Transition string `mapstructure:"transition"`
NavigationMode string `mapstructure:"navigationMode"`
HorizontalSeparator string `mapstructure:"horizontalSeparator"`
VerticalSeparator string `mapstructure:"verticalSeparator"`
SlideNumberVisibility string `mapstructure:"slideNumberVisibility"`
SlideNumberFormat string `mapstructure:"slideNumberFormat"`
StyleSheets []string `mapstructure:"stylesheets"`
FilesToMonitor []string `mapstructure:"filesToMonitor"`
WorkingDirectory string `mapstructure:"working-dir"`
LineEnding encoding.LineEnding `mapstructure:"-"`
}
func (params *RevealParams) Load() error {
_ = viper.Unmarshal(params)
expandGlobs(params)
return mergo.Merge(params, &defaultParams)
}
func expandGlobs(params *RevealParams) {
var allFiles []string
for _, f := range params.FilesToMonitor {
var err error
f, err = filepath.Abs(f)
if err != nil {
continue
}
var matches []string
if matches, err = doublestar.Glob(f); err != nil {
continue
}
for idx := range matches {
if relative, err := filepath.Rel(params.WorkingDirectory, matches[idx]); err != nil {
continue
} else {
matches[idx] = path.Join("/", relative)
}
}
allFiles = append(allFiles, matches...)
}
params.FilesToMonitor = allFiles
}

View file

@ -1,70 +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 (
"html/template"
"net/http"
"github.com/Masterminds/sprig/v3"
log "github.com/sirupsen/logrus"
"github.com/baez90/goveal/assets"
)
type RevealRenderer interface {
http.Handler
init() error
}
func NewRevealRenderer(params *RevealParams) (renderer RevealRenderer, err error) {
renderer = &revealRenderer{
params: params,
}
err = renderer.init()
return
}
type revealRenderer struct {
template *template.Template
params *RevealParams
}
func (renderer *revealRenderer) init() (err error) {
renderer.template, err = template.New("index").Funcs(sprig.FuncMap()).Parse(string(assets.Template))
return
}
func (renderer *revealRenderer) ServeHTTP(response http.ResponseWriter, _ *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 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,47 +0,0 @@
package routing
import (
"errors"
"net/http"
"sync"
)
var ErrFileNotFound = errors.New("file not found in any layer")
func NewLayeredFileSystem(layers ...http.FileSystem) http.FileSystem {
return &layeredFileSystem{
resolveCache: make(map[string]http.FileSystem),
layers: layers,
}
}
type layeredFileSystem struct {
layers []http.FileSystem
resolveCache map[string]http.FileSystem
lock sync.Mutex
}
func (l *layeredFileSystem) Open(name string) (f http.File, err error) {
if cachedLayer, isCached := l.resolveCache[name]; isCached {
if cachedLayer != nil {
return cachedLayer.Open(name)
} else {
return nil, ErrFileNotFound
}
}
for idx := range l.layers {
layer := l.layers[idx]
if f, err = layer.Open(name); err == nil {
l.lock.Lock()
l.resolveCache[name] = layer
l.lock.Unlock()
return
}
}
l.lock.Lock()
l.resolveCache[name] = nil
l.lock.Unlock()
return nil, ErrFileNotFound
}

View file

@ -1,33 +0,0 @@
package routing
import (
"fmt"
"net/http"
"os"
"path/filepath"
)
type markdownFs struct {
destinationPath string
}
func NewMarkdownFS(path string) (fs http.FileSystem, err error) {
var info os.FileInfo
info, err = os.Stat(path)
if err != nil {
return
}
if info.IsDir() || filepath.Ext(info.Name()) != ".md" {
err = fmt.Errorf("path %s did not pass sanity checks for markdown files", path)
return
}
return &markdownFs{
destinationPath: path,
}, nil
}
func (m markdownFs) Open(_ string) (http.File, error) {
return os.Open(m.destinationPath)
}

View file

@ -1,56 +0,0 @@
package routing
import (
"net/http"
"strings"
"time"
)
var epoch = time.Unix(0, 0).Format(time.RFC1123)
var noCacheHeaders = map[string]string{
"Expires": epoch,
"Cache-Control": "no-cache, private, max-age=0",
"Pragma": "no-cache",
"X-Accel-Expires": "0",
}
var etagHeaders = []string{
"ETag",
"If-Modified-Since",
"If-Match",
"If-None-Match",
"If-Range",
"If-Unmodified-Since",
}
func NoCache(h http.Handler, pathsToDisableCache []string) http.Handler {
pathLookup := make(map[string]bool)
for idx := range pathsToDisableCache {
pathLookup[strings.ToLower(pathsToDisableCache[idx])] = true
}
fn := func(w http.ResponseWriter, r *http.Request) {
if _, shouldBeHandled := pathLookup[strings.ToLower(r.URL.Path)]; !shouldBeHandled {
h.ServeHTTP(w, r)
return
}
// Delete any ETag headers that may have been set
for _, v := range etagHeaders {
if r.Header.Get(v) != "" {
r.Header.Del(v)
}
}
// Set our NoCache headers
for k, v := range noCacheHeaders {
w.Header().Set(k, v)
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}

View file

@ -1,38 +0,0 @@
package routing
import (
"net/http"
"regexp"
)
type regexpRule struct {
pattern *regexp.Regexp
handler http.Handler
}
type RegexpRouter struct {
rules []regexpRule
}
func (r *RegexpRouter) AddRule(pattern string, handler http.Handler) (err error) {
var exp *regexp.Regexp
if exp, err = regexp.Compile(pattern); err != nil {
return
}
r.rules = append(r.rules, regexpRule{
pattern: exp,
handler: handler,
})
return
}
func (r *RegexpRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
for idx := range r.rules {
rule := r.rules[idx]
if rule.pattern.MatchString(request.URL.Path) {
rule.handler.ServeHTTP(writer, request)
return
}
}
writer.WriteHeader(404)
}

View file

@ -1,103 +0,0 @@
package server
import (
"crypto"
"encoding/hex"
"encoding/json"
"hash"
"io"
"net/http"
"regexp"
"strings"
"sync"
log "github.com/sirupsen/logrus"
"go.uber.org/multierr"
)
var (
pathMatcherRegexp = regexp.MustCompile(`(?i)^/hash/(md5|sha1|sha2)(/.*)`)
hashes = map[string]crypto.Hash{
"md5": crypto.MD5,
"sha1": crypto.SHA1,
"sha256": crypto.SHA256,
}
)
type hashResponse struct {
FilePath string
Hash string
}
func NewHashHandler(fs http.FileSystem) http.Handler {
return &hashHandler{
fs: fs,
bufferPool: &sync.Pool{
New: func() interface{} {
return make([]byte, 4096)
},
},
}
}
type hashHandler struct {
bufferPool *sync.Pool
fs http.FileSystem
}
func (h hashHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
components := pathMatcherRegexp.FindStringSubmatch(request.URL.Path)
if len(components) != 3 {
writer.WriteHeader(400)
return
}
filePath := components[2]
var hashFound bool
var hashInstance crypto.Hash
if hashInstance, hashFound = hashes[strings.ToLower(components[1])]; !hashFound {
writer.WriteHeader(404)
return
}
var f http.File
var err error
if f, err = h.fs.Open(filePath); err != nil {
writer.WriteHeader(404)
return
}
var encodedHash string
if encodedHash, err = h.buildHash(hashInstance.New(), f); err != nil {
log.Errorf("Failed to calculate hash %v", err)
writer.WriteHeader(500)
return
}
resp := hashResponse{
FilePath: filePath,
Hash: encodedHash,
}
writer.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(writer)
if err = encoder.Encode(resp); err != nil {
writer.WriteHeader(500)
return
}
}
func (h *hashHandler) buildHash(hasher hash.Hash, src io.ReadCloser) (encodedHash string, err error) {
defer func() {
err = multierr.Append(err, src.Close())
}()
buffer := h.bufferPool.Get().([]byte)
for n, err := src.Read(buffer); n > 0 && err == nil; n, err = src.Read(buffer) {
if _, err := hasher.Write(buffer[:n]); err != nil {
return "", err
}
}
encodedHash = hex.EncodeToString(hasher.Sum(nil))
return
}

View file

@ -1,118 +0,0 @@
package server
import (
"fmt"
"io/fs"
"net"
"net/http"
"os"
"github.com/baez90/goveal/assets"
"github.com/baez90/goveal/internal/app/rendering"
"github.com/baez90/goveal/internal/app/routing"
"github.com/baez90/goveal/internal/encoding"
)
const (
markdownFilePath = "/content.md"
)
type (
Config struct {
Host string
Port uint16
MarkdownPath string
RevealParams *rendering.RevealParams
}
HTTPServer struct {
listener net.Listener
handler http.Handler
}
)
func (srv HTTPServer) Serve() error {
return http.Serve(srv.listener, srv.handler)
}
func (srv HTTPServer) ListenAddress() string {
return srv.listener.Addr().String()
}
func NewHTTPServer(config Config) (srv *HTTPServer, err error) {
noCacheFiles := append(config.RevealParams.FilesToMonitor, markdownFilePath)
if err := detectMarkdownFileEnding(config.MarkdownPath, config.RevealParams); err != nil {
return nil, err
}
router := &routing.RegexpRouter{}
var tmplRenderer rendering.RevealRenderer
if tmplRenderer, err = rendering.NewRevealRenderer(config.RevealParams); err != nil {
err = fmt.Errorf("failed to initialize reveal renderer %w", err)
return
}
// language=regexp
if err = router.AddRule(`^(/(index.html(l)?)?)?$`, tmplRenderer); err != nil {
return
}
var mdFS http.FileSystem
if mdFS, err = routing.NewMarkdownFS(config.MarkdownPath); err != nil {
err = fmt.Errorf("failed to initialize markdown file handler %w", err)
return
}
var revealFS, webFS fs.FS
if revealFS, err = fs.Sub(assets.Assets, "reveal"); err != nil {
return nil, err
}
if webFS, err = fs.Sub(assets.Assets, "web"); err != nil {
return nil, err
}
layeredFS := routing.NewLayeredFileSystem(http.FS(revealFS), http.FS(webFS), http.Dir("."), mdFS)
// language=regexp
if err = router.AddRule(`^(?i)/hash/(md5|sha1|sha2)/.*`, NewHashHandler(layeredFS)); err != nil {
return
}
// language=regexp
if err = router.AddRule("^/.*\\.md$", http.FileServer(mdFS)); err != nil {
return
}
// language=regexp
if err = router.AddRule("/.+", http.FileServer(layeredFS)); err != nil {
return
}
hostPort := fmt.Sprintf("%s:%d", config.Host, config.Port)
srv = &HTTPServer{
handler: routing.NoCache(router, noCacheFiles),
}
if srv.listener, err = net.Listen("tcp", hostPort); err != nil {
return
}
return
}
func detectMarkdownFileEnding(filePath string, params *rendering.RevealParams) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer func() {
_ = f.Close()
}()
if le, err := encoding.Detect(f); err != nil {
return err
} else {
params.LineEnding = le
}
return nil
}

View file

@ -1,44 +0,0 @@
package encoding
import (
"bufio"
"io"
)
const (
LineEndingUnknown LineEnding = ""
LineEndingWindows LineEnding = "\r\n"
LineEndingUnix LineEnding = "\n"
)
type LineEnding string
func (e LineEnding) String() string {
return string(e)
}
func (e LineEnding) Escaped() string {
switch e {
case LineEndingUnix:
return "\\n"
case LineEndingWindows:
return "\\r\\n"
default:
return ""
}
}
func Detect(reader io.Reader) (LineEnding, error) {
bufferedReader := bufio.NewReader(reader)
line, err := bufferedReader.ReadString(byte('\n'))
if err != nil {
return LineEndingUnknown, err
}
lineLength := len(line)
if lineLength <= 1 || line[lineLength-2:] != LineEndingWindows.String() {
return LineEndingUnix, nil
}
return LineEndingWindows, nil
}

View file

@ -1,74 +0,0 @@
package encoding_test
import (
"io"
"strings"
"testing"
"github.com/baez90/goveal/internal/encoding"
)
func TestDetect(t *testing.T) {
type args struct {
reader io.Reader
}
tests := []struct {
name string
args args
want encoding.LineEnding
wantErr bool
}{
{
name: "Empty file expect unknown",
args: args{
reader: strings.NewReader(""),
},
want: encoding.LineEndingUnknown,
wantErr: true,
},
{
name: "File with only Unix line ending",
args: args{
reader: strings.NewReader("\n"),
},
want: encoding.LineEndingUnix,
wantErr: false,
},
{
name: "File with only Windows line ending",
args: args{
reader: strings.NewReader("\r\n"),
},
want: encoding.LineEndingWindows,
wantErr: false,
},
{
name: "File with multiple lines - Unix file ending",
args: args{
reader: strings.NewReader("Hello, World\nThis comes from Unix!\n"),
},
want: encoding.LineEndingUnix,
wantErr: false,
},
{
name: "File with multiple lines - Windows file ending",
args: args{
reader: strings.NewReader("Hello, World\r\nThis comes from Windows!\r\n"),
},
want: encoding.LineEndingWindows,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := encoding.Detect(tt.args.reader)
if (err != nil) != tt.wantErr {
t.Errorf("Detect() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Detect() got = %v, want %v", got, tt.want)
}
})
}
}

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 (
"bytes"
"fmt"
"html/template"
"regexp"
"github.com/gomarkdown/markdown/parser"
"github.com/baez90/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 := templateRenderBufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
templateRenderBufferPool.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]interface{}{
"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]interface{}{
"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]interface{}{
//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"])
}

66
rendering/templates.go Normal file
View file

@ -0,0 +1,66 @@
package rendering
import (
"bytes"
"embed"
"hash/fnv"
"html/template"
"sync"
"github.com/Masterminds/sprig/v3"
"github.com/gomarkdown/markdown"
mdhtml "github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"github.com/baez90/goveal/rendering/emoji"
)
var (
//go:embed templates/*.gohtml
templatesFS embed.FS
templates *template.Template
templateRenderBufferPool = &sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
)
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 interface{}) (output []byte, err error) {
buffer := templateRenderBufferPool.Get().(*bytes.Buffer)
defer func() {
buffer.Reset()
templateRenderBufferPool.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>

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