├── go.mod
├── tmux-wormhole.tmux
├── pkg
├── widgets
│ └── hilite
│ │ └── hilite.go
└── wormflow
│ └── wormflow.go
├── README.md
├── cmd
└── tmux-wormhole
│ └── main.go
├── tmux-wormhole.sh
└── go.sum
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/gcla/tmux-wormhole
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/adrg/xdg v0.4.0
7 | github.com/alessio/shellescape v1.4.1
8 | github.com/gcla/gowid v1.3.0
9 | github.com/gdamore/tcell v1.4.0
10 | github.com/kr/pty v1.1.4 // indirect
11 | github.com/mitchellh/go-homedir v1.1.0
12 | github.com/psanford/wormhole-william v1.0.6
13 | github.com/sirupsen/logrus v1.8.1
14 | )
15 |
--------------------------------------------------------------------------------
/tmux-wormhole.tmux:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 |
5 | if [[ "$TMUX_WORMHOLE_DO_INSTALL" = "1" ]] ; then
6 | cd "$CURRENT_DIR"
7 |
8 | echo Trying to compile tmux-wormhole...
9 | echo
10 | GO111MODULE=on go build -o ./tmux-wormhole cmd/tmux-wormhole/main.go
11 | RES=$?
12 |
13 | echo
14 | if [[ -e ./tmux-wormhole ]] ; then
15 | echo Installed.
16 | read
17 | exit 0
18 | else
19 | echo Could not build tmux-wormhole.
20 | read
21 | exit 1
22 | fi
23 | fi
24 |
25 |
26 | DEFAULT_WORMHOLE_KEY=w
27 |
28 | WORMHOLE_KEY="$(tmux show-option -gqv @wormhole-key)"
29 | WORMHOLE_KEY=${WORMHOLE_KEY:-$DEFAULT_WORMHOLE_KEY}
30 |
31 | tmux bind-key "${WORMHOLE_KEY}" run-shell -b "${CURRENT_DIR}/tmux-wormhole.sh"
32 |
33 | if [[ ! -e "${CURRENT_DIR}/tmux-wormhole" ]] ; then
34 | tmux split-window "TMUX_WORMHOLE_DO_INSTALL=1 ${CURRENT_DIR}/tmux-wormhole.tmux"
35 | fi
36 |
--------------------------------------------------------------------------------
/pkg/widgets/hilite/hilite.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Graham Clark. All rights reserved. Use of this source
2 | // code is governed by the MIT license that can be found in the LICENSE
3 | // file.
4 |
5 | // Package hilite contains a widget that hilights matching sections of text
6 | // by scanning the rendered canvas.
7 | package hilite
8 |
9 | import (
10 | "regexp"
11 |
12 | "github.com/gcla/gowid"
13 | )
14 |
15 | //======================================================================
16 |
17 | type Options struct {
18 | Background gowid.TCellColor
19 | Foreground gowid.TCellColor
20 | }
21 |
22 | type Widget struct {
23 | gowid.IWidget
24 | Match *regexp.Regexp
25 | Opt Options
26 | }
27 |
28 | var _ gowid.IWidget = (*Widget)(nil)
29 |
30 | //======================================================================
31 |
32 | func New(inner gowid.IWidget, re *regexp.Regexp, opts ...Options) *Widget {
33 | var opt Options
34 | if len(opts) > 0 {
35 | opt = opts[0]
36 | } else {
37 | opt = Options{
38 | Background: gowid.ColorLightGreen,
39 | Foreground: gowid.ColorBlack,
40 | }
41 | }
42 |
43 | res := &Widget{
44 | IWidget: inner,
45 | Match: re,
46 | Opt: opt,
47 | }
48 | return res
49 | }
50 |
51 | func (w *Widget) Render(size gowid.IRenderSize, focus gowid.Selector, app gowid.IApp) gowid.ICanvas {
52 | res := w.IWidget.Render(size, focus, app)
53 |
54 | cbytes := canvasToArray(res)
55 |
56 | matches := w.Match.FindAllIndex(cbytes, -1)
57 |
58 | wid := res.BoxColumns()
59 | if wid < 1 {
60 | return res
61 | }
62 |
63 | var x int
64 | var y int
65 | for _, m := range matches {
66 | for j := m[0]; j < m[1]; j++ {
67 | x = j % wid
68 | y = j / wid
69 | res.SetCellAt(x, y, res.CellAt(x, y).WithBackgroundColor(w.Opt.Background).WithForegroundColor(w.Opt.Foreground))
70 | }
71 | }
72 |
73 | return res
74 | }
75 |
76 | func canvasToArray(c gowid.ICanvas) []byte {
77 | res := make([]byte, c.BoxRows()*c.BoxColumns())
78 | var r rune
79 | n := 0
80 | for i := 0; i < c.BoxRows(); i++ {
81 | for j := 0; j < c.BoxColumns(); j++ {
82 | r = c.CellAt(j, i).Rune()
83 | if int(r) < 32 || int(r) > 127 {
84 | r = ' '
85 | }
86 | res[n] = byte(r)
87 | n++
88 | }
89 | }
90 | return res
91 | }
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tmux-wormhole
2 |
3 | Use tmux and magic wormhole to get things from your remote computer to your tmux. If tmux
4 | has DISPLAY set, open the file locally!
5 |
6 | ## Demo
7 |
8 | 
9 |
10 | ## Usage
11 |
12 | - On your remote computer, display the magic wormhole code.
13 | - Press ( prefix + w )
14 | - Hit OK to transfer.
15 |
16 | ## Prerequisites
17 |
18 | `tmux-wormhole` is written in Go. To install `tmux-wormhole` successfully, you'll need Go version 1.13 or higher.
19 |
20 | ## Setup with Tmux Plugin Manager
21 |
22 | Set up this plugin via [TPM](https://github.com/tmux-plugins/tpm) by adding this to your `~/.tmux.conf`:
23 |
24 | ```
25 | set -g @plugin 'gcla/tmux-wormhole'
26 | ```
27 |
28 | Install the plugin by hitting prefix + I.
29 |
30 | ## Setup Manually
31 |
32 | Clone the repo:
33 |
34 | ```
35 | git clone https://github.com/gcla/tmux-wormhole ~/.tmux/plugins/tmux-wormhole
36 | ```
37 |
38 | Compile it:
39 |
40 | ```
41 | cd ~/.tmux/plugins/tmux-wormhole
42 | GO11MODULE=on go build -o tmux-wormhole cmd/tmux-wormhole/main.go
43 | ```
44 |
45 | Source it by adding this to your `~/.tmux.conf`:
46 |
47 | ```
48 | run-shell ~/.tmux/plugins/tmux-wormhole/tmux-wormhole.tmux
49 | ```
50 |
51 | Reload TMUX's config with:
52 |
53 | ```
54 | tmux source-file ~/.tmux.conf
55 | ```
56 |
57 | ## Configuration
58 |
59 | Set these in your `~/.tmux.conf` file.
60 |
61 | - @wormhole-key - how to launch tmux-wormhole (default: `w`)
62 | - @wormhole-save-folder - where to keep transferred files and directories (default: XDG download dir e.g. `~/Downloads/`)
63 | - @wormhole-open-cmd - run this command after a file is transferred (default: `xdg-open` or `open`)
64 | - @wormhole-no-default-open - just transfer, don't run anything afterwards (default: `false`)
65 | - @wormhole-no-ask-to-open - after a file is transferred, ask the user interactively if the file should be opened (default: `false`)
66 | - @wormhole-can-overwrite - allow tmux-wormhole to overwite a file or directory of the same name locally (default: `false`)
67 |
68 | ## How does it work
69 |
70 | The plugin uses sleight of hand to make it look as though its prompts are being displayed over the active pane. When you hit the tmux-wormhole hotkey,
71 | the plugin does the following:
72 |
73 | - saves the contents of the active pane to a temporary file e.g. `/tmp/wormhole`
74 | - launches a new tmux session called `wormhole`, with...
75 | - a pane running `cat /tmp/wormhole ; sleep infinity`
76 |
77 | If you were to attach to the wormhole session, this pane should look like the currently active pane. Next the plugin will:
78 |
79 | - create a new window called `wormhole-ABC` in the active session
80 | - with a single pane running the Go program `tmux-wormhole`
81 | - `tmux-wormhole` is a gowid application that overlays a dialog widget on top of a terminal widget. The terminal widget runs `tmux attach -L wormhole`
82 |
83 | Finally, the plugin swaps the currently active pane with the pane from `wormhole-ABC`.
84 |
85 | The effect is that the terminal now has a yellow dialog overlaid.
86 |
87 | ## Sources
88 |
89 | - [tmux-thumbs](https://github.com/fcsonline/tmux-thumbs) for the project structure which I freely plagiarized!
90 | - [wormhole-william](https://github.com/psanford/wormhole-william) - a GoLang implementation of magic wormhole
91 | - [gowid](https://github.com/gcla/gowid) - my Go TUI framework, which itself heavily depends on...
92 | - [tcell](https://github.com/gdamore/tcell) - like ncurses for GoLang
93 |
94 | # License
95 |
96 | [MIT](https://github.com/fcsonline/tmux-thumbs/blob/master/LICENSE)
97 |
--------------------------------------------------------------------------------
/cmd/tmux-wormhole/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Graham Clark. All rights reserved. Use of this source
2 | // code is governed by the MIT license that can be found in the LICENSE
3 | // file.
4 |
5 | package main
6 |
7 | import (
8 | "fmt"
9 | "io/ioutil"
10 | "os"
11 | "regexp"
12 | "runtime"
13 | "strings"
14 | "time"
15 |
16 | _ "net/http"
17 | _ "net/http/pprof"
18 |
19 | "github.com/adrg/xdg"
20 | "github.com/gcla/gowid"
21 | "github.com/gcla/gowid/widgets/holder"
22 | "github.com/gcla/gowid/widgets/selectable"
23 | "github.com/gcla/gowid/widgets/terminal"
24 | "github.com/gcla/tmux-wormhole/pkg/widgets/hilite"
25 | "github.com/gcla/tmux-wormhole/pkg/wormflow"
26 | "github.com/gdamore/tcell"
27 | "github.com/mitchellh/go-homedir"
28 | "github.com/sirupsen/logrus"
29 | )
30 |
31 | //======================================================================
32 |
33 | var app *gowid.App
34 | var term *terminal.Widget
35 | var code string
36 | var saveDir string
37 | var session string
38 | var shell string
39 | var openCmd string
40 | var willQuit bool
41 |
42 | //======================================================================
43 |
44 | // Go's main() prototype does not provide for returning a value.
45 | func main() {
46 | res := cmain()
47 | os.Exit(res)
48 | }
49 |
50 | //======================================================================
51 |
52 | func envTrue(val string) bool {
53 | switch strings.ToLower(val) {
54 | case "true", "t", "yes", "y", "1":
55 | return true
56 | default:
57 | return false
58 | }
59 | }
60 |
61 | func quit(app gowid.IApp) {
62 | if !willQuit {
63 | willQuit = true
64 | app.Quit()
65 | }
66 | }
67 |
68 | type handler struct {
69 | controller *wormflow.Controller
70 | }
71 |
72 | func (h handler) UnhandledInput(app gowid.IApp, ev interface{}) bool {
73 | handled := false
74 |
75 | if evk, ok := ev.(*tcell.EventKey); ok {
76 | switch evk.Key() {
77 | case tcell.KeyCtrlC, tcell.KeyEsc:
78 | handled = true
79 | quit(app)
80 | }
81 | }
82 | return handled
83 | }
84 |
85 | //======================================================================
86 |
87 | func cmain() int {
88 | var err error
89 |
90 | // Do these before we switch the terminal to graphics
91 | shell = os.Getenv("SHELL")
92 | if shell == "" {
93 | fmt.Printf("This tmux plugin requires a value in the env variable SHELL.\n")
94 | return 1
95 | }
96 |
97 | code = os.Getenv("TMUX_WORMHOLE_CODE")
98 | // If code is empty, it means the bash wrapper didn't find one. Show that error in the UI
99 | // which means we need to launch the UI first. But I do assume that what is provided is
100 | // either empty or will compile to a regexp. If not, it's ok to show a low-tech error.
101 | codeRe, err := regexp.Compile(code)
102 | if err != nil {
103 | fmt.Printf("Wormhole code %s is invalid.\n", code)
104 | return 1
105 | }
106 |
107 | session = os.Getenv("TMUX_WORMHOLE_SESSION")
108 | if session == "" {
109 | fmt.Printf("This tmux plugin requires a value in the env variable TMUX_WORMHOLE_SESSION.\n")
110 | return 1
111 | }
112 |
113 | saveDir = os.Getenv("TMUX_WORMHOLE_SAVE_FOLDER")
114 | if saveDir == "" {
115 | saveDir = xdg.UserDirs.Download
116 | }
117 | if saveDir == "" {
118 | saveDir = "."
119 | }
120 | if saveDir != "" {
121 | saveDir, err = homedir.Expand(saveDir)
122 | if err != nil {
123 | fmt.Printf("Problem expanding save directory %s: %v\n", saveDir, err)
124 | return 1
125 | }
126 | }
127 |
128 | // Takes precedence
129 | openCmd = os.Getenv("TMUX_WORMHOLE_OPEN_CMD")
130 | if openCmd == "" && !envTrue(os.Getenv("TMUX_WORMHOLE_NO_DEFAULT_OPEN")) {
131 | switch runtime.GOOS {
132 | case "darwin":
133 | openCmd = "open"
134 | case "linux", "dragonfly", "freebsd", "netbsd", "openbsd":
135 | openCmd = "xdg-open"
136 | }
137 | }
138 |
139 | // Avoid gowid's dim screen problem with truecolor - need to fix
140 | os.Setenv("COLORTERM", "")
141 |
142 | palette := gowid.Palette{
143 | "dialog": gowid.MakeStyledPaletteEntry(gowid.ColorBlack, gowid.ColorYellow, gowid.StyleNone),
144 | "dialog-button": gowid.MakeStyledPaletteEntry(gowid.ColorYellow, gowid.ColorBlack, gowid.StyleNone),
145 | "button": gowid.MakeForeground(gowid.ColorMagenta),
146 | "button-focus": gowid.MakePaletteEntry(gowid.ColorWhite, gowid.ColorDarkBlue),
147 | "progress-default": gowid.MakeStyledPaletteEntry(gowid.ColorWhite, gowid.ColorBlack, gowid.StyleBold),
148 | "progress-complete": gowid.MakeStyleMod(gowid.MakePaletteRef("progress-default"), gowid.MakeBackground(gowid.ColorMagenta)),
149 | "progress-spinner": gowid.MakePaletteEntry(gowid.ColorMagenta, gowid.ColorBlack),
150 | }
151 |
152 | hkDuration := terminal.HotKeyDuration{time.Second * 3}
153 |
154 | term, err = terminal.NewExt(terminal.Options{
155 | Command: []string{
156 | shell, "-c", fmt.Sprintf("tmux -L wormhole attach-session -t '%s'", session),
157 | },
158 | HotKeyPersistence: &hkDuration,
159 | Scrollback: 100,
160 | })
161 | if err != nil {
162 | fmt.Printf("Unexpected error running gowid terminal widget: %v\n", err)
163 | return 1
164 | }
165 |
166 | term.OnProcessExited(gowid.WidgetCallback{"cb",
167 | func(app gowid.IApp, w gowid.IWidget) {
168 | quit(app)
169 | },
170 | })
171 |
172 | // Don't want any user input going to the pane below the dialog, which is really
173 | // a mock-up of the pane that was being displayed before the plugin ran.
174 | h := holder.New(
175 | selectable.NewUnselectable(
176 | hilite.New(term, codeRe, hilite.Options{
177 | Background: gowid.ColorGreen,
178 | Foreground: gowid.ColorBlack,
179 | }),
180 | ),
181 | )
182 |
183 | log := logrus.New()
184 | log.SetOutput(ioutil.Discard)
185 |
186 | app, err = gowid.NewApp(gowid.AppArgs{
187 | View: h,
188 | Palette: &palette,
189 | Log: log,
190 | })
191 |
192 | if err != nil {
193 | fmt.Printf("Unexpected error launching gowid app: %v\n", err)
194 | return 1
195 | }
196 |
197 | controller := wormflow.New(wormflow.Args{
198 | Code: code,
199 | SaveDir: saveDir,
200 | OpenCmd: openCmd,
201 | NoAskOpen: envTrue(os.Getenv("TMUX_WORMHOLE_NO_ASK_TO_OPEN")),
202 | Overwrite: envTrue(os.Getenv("TMUX_WORMHOLE_CAN_OVERWRITE")),
203 | Shell: shell,
204 | Lower: h,
205 | })
206 |
207 | controller.Start(app)
208 |
209 | app.MainLoop(handler{controller: controller})
210 |
211 | return 0
212 | }
213 |
214 | //======================================================================
215 | // Local Variables:
216 | // mode: Go
217 | // fill-column: 78
218 | // End:
219 |
--------------------------------------------------------------------------------
/tmux-wormhole.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -Eu -o pipefail
4 |
5 | # from tmux-thumbs
6 | function get-opt-value() {
7 | tmux show -vg "@wormhole-${1}" 2> /dev/null
8 | }
9 |
10 | # e.g. join_by , a b c => a,b,c
11 | function join_by {
12 | local IFS="$1"
13 | shift
14 | echo "$*"
15 | }
16 |
17 | # I want a short token because I use this in a tmux window name that might be displayed
18 | # in the status bar. I want it to be relatively unobtrusive.
19 | #
20 | # From https://stackoverflow.com/a/32484733
21 | function random_token() {
22 | chars=abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXYZ2346789
23 | for i in {1..3} ; do
24 | printf "${chars:RANDOM%${#chars}:1}"
25 | done
26 | }
27 |
28 | # From https://stackoverflow.com/a/36490592
29 | function tmp_dir() {
30 | dirname $(mktemp -u -t tmp.XXXXXXXXXX)
31 | }
32 |
33 | # This is run inside the tmux pane that is used in place of the one from
34 | # which the plugin is launched. This just cats the saved pane contents, so
35 | # it looks like the original... macOS doesn't have sleep infinity
36 | function pane_command() {
37 | printf 'bash -c '\''cat "%s" ; sleep 100000'\' "${TMUX_WORMHOLE_TMP_FILE}"
38 | }
39 |
40 | ######################################################################
41 |
42 | TMUX_WORMHOLE_BIN="$HOME/.tmux/plugins/tmux-wormhole/tmux-wormhole"
43 | if [[ ! -e "${TMUX_WORMHOLE_BIN}" ]] ; then
44 | tmux split-window "echo Could not execute tmux-wormhole binary $TMUX_WORMHOLE_BIN. Please check plugin installation. ; read"
45 | exit 1
46 | fi
47 |
48 | set -e
49 |
50 | # Make sure every variable exists
51 | TMUX_WORMHOLE_SAVE_FOLDER="$(get-opt-value save-folder)"
52 | TMUX_WORMHOLE_OPEN_CMD="$(get-opt-value open-cmd)"
53 | TMUX_WORMHOLE_NO_DEFAULT_OPEN="$(get-opt-value no-default-open)"
54 | TMUX_WORMHOLE_NO_ASK_TO_OPEN="$(get-opt-value no-ask-to-open)"
55 | TMUX_WORMHOLE_CAN_OVERWRITE="$(get-opt-value can-overwrite)"
56 |
57 | # e.g. abc
58 | TMUX_WORMHOLE_CURRENT="$(random_token)"
59 |
60 | # e.g. wormhole-abc
61 | TMUX_WORMHOLE_SESSION="wormhole-${TMUX_WORMHOLE_CURRENT}"
62 |
63 | # e.g. /tmp/,tmux-wormhole-abc
64 | TMUX_WORMHOLE_TMP_FILE="$(tmp_dir)/.tmux-wormhole-${TMUX_WORMHOLE_CURRENT}"
65 |
66 | # Used for constructing a regex that matches a wormhole code. The PGP word list.
67 | TMUX_WORMHOLE_PGP_WORD_LIST=(
68 | "aardvark" \
69 | "absurd" \
70 | "accrue" \
71 | "acme" \
72 | "adrift" \
73 | "adult" \
74 | "afflict" \
75 | "ahead" \
76 | "aimless" \
77 | "algol" \
78 | "allow" \
79 | "alone" \
80 | "ammo" \
81 | "ancient" \
82 | "apple" \
83 | "artist" \
84 | "assume" \
85 | "athens" \
86 | "atlas" \
87 | "aztec" \
88 | "baboon" \
89 | "backfield" \
90 | "backward" \
91 | "banjo" \
92 | "beaming" \
93 | "bedlamp" \
94 | "beehive" \
95 | "beeswax" \
96 | "befriend" \
97 | "belfast" \
98 | "berserk" \
99 | "billiard" \
100 | "bison" \
101 | "blackjack" \
102 | "blockade" \
103 | "blowtorch" \
104 | "bluebird" \
105 | "bombast" \
106 | "bookshelf" \
107 | "brackish" \
108 | "breadline" \
109 | "breakup" \
110 | "brickyard" \
111 | "briefcase" \
112 | "burbank" \
113 | "button" \
114 | "buzzard" \
115 | "cement" \
116 | "chairlift" \
117 | "chatter" \
118 | "checkup" \
119 | "chisel" \
120 | "choking" \
121 | "chopper" \
122 | "christmas" \
123 | "clamshell" \
124 | "classic" \
125 | "classroom" \
126 | "cleanup" \
127 | "clockwork" \
128 | "cobra" \
129 | "commence" \
130 | "concert" \
131 | "cowbell" \
132 | "crackdown" \
133 | "cranky" \
134 | "crowfoot" \
135 | "crucial" \
136 | "crumpled" \
137 | "crusade" \
138 | "cubic" \
139 | "dashboard" \
140 | "deadbolt" \
141 | "deckhand" \
142 | "dogsled" \
143 | "dragnet" \
144 | "drainage" \
145 | "dreadful" \
146 | "drifter" \
147 | "dropper" \
148 | "drumbeat" \
149 | "drunken" \
150 | "dupont" \
151 | "dwelling" \
152 | "eating" \
153 | "edict" \
154 | "egghead" \
155 | "eightball" \
156 | "endorse" \
157 | "endow" \
158 | "enlist" \
159 | "erase" \
160 | "escape" \
161 | "exceed" \
162 | "eyeglass" \
163 | "eyetooth" \
164 | "facial" \
165 | "fallout" \
166 | "flagpole" \
167 | "flatfoot" \
168 | "flytrap" \
169 | "fracture" \
170 | "framework" \
171 | "freedom" \
172 | "frighten" \
173 | "gazelle" \
174 | "geiger" \
175 | "glitter" \
176 | "glucose" \
177 | "goggles" \
178 | "goldfish" \
179 | "gremlin" \
180 | "guidance" \
181 | "hamlet" \
182 | "highchair" \
183 | "hockey" \
184 | "indoors" \
185 | "indulge" \
186 | "inverse" \
187 | "involve" \
188 | "island" \
189 | "jawbone" \
190 | "keyboard" \
191 | "kickoff" \
192 | "kiwi" \
193 | "klaxon" \
194 | "locale" \
195 | "lockup" \
196 | "merit" \
197 | "minnow" \
198 | "miser" \
199 | "mohawk" \
200 | "mural" \
201 | "music" \
202 | "necklace" \
203 | "neptune" \
204 | "newborn" \
205 | "nightbird" \
206 | "oakland" \
207 | "obtuse" \
208 | "offload" \
209 | "optic" \
210 | "orca" \
211 | "payday" \
212 | "peachy" \
213 | "pheasant" \
214 | "physique" \
215 | "playhouse" \
216 | "pluto" \
217 | "preclude" \
218 | "prefer" \
219 | "preshrunk" \
220 | "printer" \
221 | "prowler" \
222 | "pupil" \
223 | "puppy" \
224 | "python" \
225 | "quadrant" \
226 | "quiver" \
227 | "quota" \
228 | "ragtime" \
229 | "ratchet" \
230 | "rebirth" \
231 | "reform" \
232 | "regain" \
233 | "reindeer" \
234 | "rematch" \
235 | "repay" \
236 | "retouch" \
237 | "revenge" \
238 | "reward" \
239 | "rhythm" \
240 | "ribcage" \
241 | "ringbolt" \
242 | "robust" \
243 | "rocker" \
244 | "ruffled" \
245 | "sailboat" \
246 | "sawdust" \
247 | "scallion" \
248 | "scenic" \
249 | "scorecard" \
250 | "scotland" \
251 | "seabird" \
252 | "select" \
253 | "sentence" \
254 | "shadow" \
255 | "shamrock" \
256 | "showgirl" \
257 | "skullcap" \
258 | "skydive" \
259 | "slingshot" \
260 | "slowdown" \
261 | "snapline" \
262 | "snapshot" \
263 | "snowcap" \
264 | "snowslide" \
265 | "solo" \
266 | "southward" \
267 | "soybean" \
268 | "spaniel" \
269 | "spearhead" \
270 | "spellbind" \
271 | "spheroid" \
272 | "spigot" \
273 | "spindle" \
274 | "spyglass" \
275 | "stagehand" \
276 | "stagnate" \
277 | "stairway" \
278 | "standard" \
279 | "stapler" \
280 | "steamship" \
281 | "sterling" \
282 | "stockman" \
283 | "stopwatch" \
284 | "stormy" \
285 | "sugar" \
286 | "surmount" \
287 | "suspense" \
288 | "sweatband" \
289 | "swelter" \
290 | "tactics" \
291 | "talon" \
292 | "tapeworm" \
293 | "tempest" \
294 | "tiger" \
295 | "tissue" \
296 | "tonic" \
297 | "topmost" \
298 | "tracker" \
299 | "transit" \
300 | "trauma" \
301 | "treadmill" \
302 | "trojan" \
303 | "trouble" \
304 | "tumor" \
305 | "tunnel" \
306 | "tycoon" \
307 | "uncut" \
308 | "unearth" \
309 | "unwind" \
310 | "uproot" \
311 | "upset" \
312 | "upshot" \
313 | "vapor" \
314 | "village" \
315 | "virus" \
316 | "vulcan" \
317 | "waffle" \
318 | "wallet" \
319 | "watchword" \
320 | "wayside" \
321 | "willow" \
322 | "woodlark" \
323 | "zulu" \
324 | "adroitness" \
325 | "adviser" \
326 | "aftermath" \
327 | "aggregate" \
328 | "alkali" \
329 | "almighty" \
330 | "amulet" \
331 | "amusement" \
332 | "antenna" \
333 | "applicant" \
334 | "apollo" \
335 | "armistice" \
336 | "article" \
337 | "asteroid" \
338 | "atlantic" \
339 | "atmosphere" \
340 | "autopsy" \
341 | "babylon" \
342 | "backwater" \
343 | "barbecue" \
344 | "belowground" \
345 | "bifocals" \
346 | "bodyguard" \
347 | "bookseller" \
348 | "borderline" \
349 | "bottomless" \
350 | "bradbury" \
351 | "bravado" \
352 | "brazilian" \
353 | "breakaway" \
354 | "burlington" \
355 | "businessman" \
356 | "butterfat" \
357 | "camelot" \
358 | "candidate" \
359 | "cannonball" \
360 | "capricorn" \
361 | "caravan" \
362 | "caretaker" \
363 | "celebrate" \
364 | "cellulose" \
365 | "certify" \
366 | "chambermaid" \
367 | "cherokee" \
368 | "chicago" \
369 | "clergyman" \
370 | "coherence" \
371 | "combustion" \
372 | "commando" \
373 | "company" \
374 | "component" \
375 | "concurrent" \
376 | "confidence" \
377 | "conformist" \
378 | "congregate" \
379 | "consensus" \
380 | "consulting" \
381 | "corporate" \
382 | "corrosion" \
383 | "councilman" \
384 | "crossover" \
385 | "crucifix" \
386 | "cumbersome" \
387 | "customer" \
388 | "dakota" \
389 | "decadence" \
390 | "december" \
391 | "decimal" \
392 | "designing" \
393 | "detector" \
394 | "detergent" \
395 | "determine" \
396 | "dictator" \
397 | "dinosaur" \
398 | "direction" \
399 | "disable" \
400 | "disbelief" \
401 | "disruptive" \
402 | "distortion" \
403 | "document" \
404 | "embezzle" \
405 | "enchanting" \
406 | "enrollment" \
407 | "enterprise" \
408 | "equation" \
409 | "equipment" \
410 | "escapade" \
411 | "eskimo" \
412 | "everyday" \
413 | "examine" \
414 | "existence" \
415 | "exodus" \
416 | "fascinate" \
417 | "filament" \
418 | "finicky" \
419 | "forever" \
420 | "fortitude" \
421 | "frequency" \
422 | "gadgetry" \
423 | "galveston" \
424 | "getaway" \
425 | "glossary" \
426 | "gossamer" \
427 | "graduate" \
428 | "gravity" \
429 | "guitarist" \
430 | "hamburger" \
431 | "hamilton" \
432 | "handiwork" \
433 | "hazardous" \
434 | "headwaters" \
435 | "hemisphere" \
436 | "hesitate" \
437 | "hideaway" \
438 | "holiness" \
439 | "hurricane" \
440 | "hydraulic" \
441 | "impartial" \
442 | "impetus" \
443 | "inception" \
444 | "indigo" \
445 | "inertia" \
446 | "infancy" \
447 | "inferno" \
448 | "informant" \
449 | "insincere" \
450 | "insurgent" \
451 | "integrate" \
452 | "intention" \
453 | "inventive" \
454 | "istanbul" \
455 | "jamaica" \
456 | "jupiter" \
457 | "leprosy" \
458 | "letterhead" \
459 | "liberty" \
460 | "maritime" \
461 | "matchmaker" \
462 | "maverick" \
463 | "medusa" \
464 | "megaton" \
465 | "microscope" \
466 | "microwave" \
467 | "midsummer" \
468 | "millionaire" \
469 | "miracle" \
470 | "misnomer" \
471 | "molasses" \
472 | "molecule" \
473 | "montana" \
474 | "monument" \
475 | "mosquito" \
476 | "narrative" \
477 | "nebula" \
478 | "newsletter" \
479 | "norwegian" \
480 | "october" \
481 | "ohio" \
482 | "onlooker" \
483 | "opulent" \
484 | "orlando" \
485 | "outfielder" \
486 | "pacific" \
487 | "pandemic" \
488 | "pandora" \
489 | "paperweight" \
490 | "paragon" \
491 | "paragraph" \
492 | "paramount" \
493 | "passenger" \
494 | "pedigree" \
495 | "pegasus" \
496 | "penetrate" \
497 | "perceptive" \
498 | "performance" \
499 | "pharmacy" \
500 | "phonetic" \
501 | "photograph" \
502 | "pioneer" \
503 | "pocketful" \
504 | "politeness" \
505 | "positive" \
506 | "potato" \
507 | "processor" \
508 | "provincial" \
509 | "proximate" \
510 | "puberty" \
511 | "publisher" \
512 | "pyramid" \
513 | "quantity" \
514 | "racketeer" \
515 | "rebellion" \
516 | "recipe" \
517 | "recover" \
518 | "repellent" \
519 | "replica" \
520 | "reproduce" \
521 | "resistor" \
522 | "responsive" \
523 | "retraction" \
524 | "retrieval" \
525 | "retrospect" \
526 | "revenue" \
527 | "revival" \
528 | "revolver" \
529 | "sandalwood" \
530 | "sardonic" \
531 | "saturday" \
532 | "savagery" \
533 | "scavenger" \
534 | "sensation" \
535 | "sociable" \
536 | "souvenir" \
537 | "specialist" \
538 | "speculate" \
539 | "stethoscope" \
540 | "stupendous" \
541 | "supportive" \
542 | "surrender" \
543 | "suspicious" \
544 | "sympathy" \
545 | "tambourine" \
546 | "telephone" \
547 | "therapist" \
548 | "tobacco" \
549 | "tolerance" \
550 | "tomorrow" \
551 | "torpedo" \
552 | "tradition" \
553 | "travesty" \
554 | "trombonist" \
555 | "truncated" \
556 | "typewriter" \
557 | "ultimate" \
558 | "undaunted" \
559 | "underfoot" \
560 | "unicorn" \
561 | "unify" \
562 | "universe" \
563 | "unravel" \
564 | "upcoming" \
565 | "vacancy" \
566 | "vagabond" \
567 | "vertigo" \
568 | "virginia" \
569 | "visitor" \
570 | "vocalist" \
571 | "voyager" \
572 | "warranty" \
573 | "waterloo" \
574 | "whimsical" \
575 | "wichita" \
576 | "wilmington" \
577 | "wyoming" \
578 | "yesteryear" \
579 | "yucatan" \
580 | )
581 |
582 | TMUX_WORMHOLE_PGP_RE=$(printf '\\b[[:digit:]]{1,3}(-(%s)){2,}' $(join_by '|' "${TMUX_WORMHOLE_PGP_WORD_LIST[@]}"))
583 |
584 | # Capture the width and height so I can set up my fake tmux pane with the same dimensions
585 | IFS=, read TID TWID THEI TZOOM DUMMY \
586 | <<<"$(tmux list-panes -F '#{pane_id},#{pane_width},#{pane_height},#{window_zoomed_flag},#{pane_active}' | grep ',1$')"
587 |
588 | # Save the current pane's contents. I'll scrape this for the wormhole code, and also display
589 | # this inside the gowid terminal which will attach to a dummy tmux session - the terminal
590 | # inside that session will show these contents using cat > /dev/tty ; sleep
591 | tmux capture-pane -e -p -J -t "${TID}" > "${TMUX_WORMHOLE_TMP_FILE}"
592 |
593 | # Strip the last newline so we don't get an extra linefeed when displaying in the gowid terminal.
594 | truncate -s -1 "${TMUX_WORMHOLE_TMP_FILE}"
595 |
596 | # This is passed to the gowid program - so it knows what to show the user.
597 | TMUX_WORMHOLE_CODE=$(grep -E -o -a "${TMUX_WORMHOLE_PGP_RE}" "${TMUX_WORMHOLE_TMP_FILE}" | tail -n 1)
598 |
599 | # This session is used to construct a pane that looks like the current pane, but with the
600 | # wormhole code highlighted. I put it under another socket so I don't have to worry about
601 | # the active session.
602 | TMUX_WORMHOLE_SESSION_ID=$(tmux -L wormhole new-session -s "${TMUX_WORMHOLE_SESSION}" -P -F '#{session_id}' -d)
603 |
604 | # This pane will be displayed inside a gowid terminal inside the current pane. Since the space
605 | # available to the gowid terminal is exactly the same space available to the current pane, I
606 | # need to turn off the status bar in the wormhole session so that when the gowid app runs
607 | # tmux attach, they don't see their main status bar, then the wormhole session status bar too.
608 | # This makes it look seamless.
609 | tmux -L wormhole set -g status off
610 |
611 | # Make sure the window holding the replacement pane is the right size, so there
612 | # are no resize events affecting the layout
613 | if [[ "$TZOOM" != "1" ]] ; then
614 | tmux -L wormhole resize-window -x "${TWID}" -y "${THEI}"
615 | fi
616 |
617 | # cat the pane contents through grep to highlight; then sleep. Now the fake pane
618 | # is set up and ready to be displayed instead of the current one.
619 | tmux -L wormhole respawn-window -k "$(pane_command)"
620 |
621 | # Open a new window without changing focus. I will save the current pane over on that window so
622 | # that I can restore it after the plugin runs.
623 | #
624 | # e.g. @685
625 | TMUX_WORMHOLE_ORIG_WINDOW=$(tmux new-window -P -d -F '#{window_id}' -n ${TMUX_WORMHOLE_SESSION})
626 |
627 | # Replace the current pane - which is a throaway swapped over from the tmp
628 | # window above - with the plugin. The plugin will read the pane output saved
629 | # above and load it in a gowid terminal, so it looks like the original pane...
630 | # When the plugin ends, swap the original pane back to its original location,
631 | # then make sure my wormhole tmux session with pane displaying the highlighted
632 | # terminal contents is cleaned up.
633 | tmux respawn-pane -k -t "${TMUX_WORMHOLE_ORIG_WINDOW}" \
634 | -e TMUX_WORMHOLE_CODE="${TMUX_WORMHOLE_CODE}" \
635 | -e TMUX_WORMHOLE_SESSION="${TMUX_WORMHOLE_SESSION}" \
636 | -e TMUX_WORMHOLE_SAVE_FOLDER="${TMUX_WORMHOLE_SAVE_FOLDER}" \
637 | -e TMUX_WORMHOLE_OPEN_CMD="${TMUX_WORMHOLE_OPEN_CMD}" \
638 | -e TMUX_WORMHOLE_NO_DEFAULT_OPEN="${TMUX_WORMHOLE_NO_DEFAULT_OPEN}" \
639 | -e TMUX_WORMHOLE_NO_ASK_TO_OPEN="${TMUX_WORMHOLE_NO_ASK_TO_OPEN}" \
640 | -e TMUX_WORMHOLE_CAN_OVERWRITE="${TMUX_WORMHOLE_CAN_OVERWRITE}" \
641 | /usr/bin/env bash -c "if ! $TMUX_WORMHOLE_BIN ; then echo Hit enter. ; read ; fi ; \
642 | tmux swap-pane -t \"${TMUX_WORMHOLE_ORIG_WINDOW}\" ; \
643 | [[ "$TZOOM" = "1" ]] && tmux resize-pane -Z ; \
644 | tmux -L wormhole kill-session -t \"${TMUX_WORMHOLE_SESSION}\" ;
645 | rm -f \"${TMUX_WORMHOLE_TMP_FILE}\" "
646 |
647 | # This gives the plugin a little time to startup, launch its own terminal, etc
648 | # and avoids flicker.
649 | # sleep 0.2s
650 |
651 | # Save current pane to the tmp window above, and make the fake the appear
652 | # in its place
653 | if [[ "$TZOOM" = "1" ]] ; then
654 | # the -Z flag to swap-pane only appeared with tmux 3.1
655 | if ! tmux swap-pane -t "${TMUX_WORMHOLE_ORIG_WINDOW}" -Z 2> /dev/null ; then
656 | tmux swap-pane -t "${TMUX_WORMHOLE_ORIG_WINDOW}"
657 | tmux resize-pane -Z
658 | fi
659 | else
660 | tmux swap-pane -t "${TMUX_WORMHOLE_ORIG_WINDOW}"
661 | fi
662 |
663 | exit 0
664 |
--------------------------------------------------------------------------------
/pkg/wormflow/wormflow.go:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Graham Clark. All rights reserved. Use of this source
2 | // code is governed by the MIT license that can be found in the LICENSE
3 | // file.
4 |
5 | // Package wormflow contains code that provides the UI for tmux-wormhole's
6 | // magic-wormhole file-receiving feature.
7 | package wormflow
8 |
9 | import (
10 | "archive/zip"
11 | "context"
12 | "fmt"
13 | "io"
14 | "io/ioutil"
15 | "os"
16 | "os/exec"
17 | "path/filepath"
18 | "strings"
19 | "time"
20 |
21 | "github.com/alessio/shellescape"
22 | "github.com/gcla/gowid"
23 | "github.com/gcla/gowid/gwutil"
24 | "github.com/gcla/gowid/widgets/dialog"
25 | "github.com/gcla/gowid/widgets/divider"
26 | "github.com/gcla/gowid/widgets/framed"
27 | "github.com/gcla/gowid/widgets/hpadding"
28 | "github.com/gcla/gowid/widgets/pile"
29 | "github.com/gcla/gowid/widgets/progress"
30 | "github.com/gcla/gowid/widgets/spinner"
31 | "github.com/gcla/gowid/widgets/text"
32 | "github.com/psanford/wormhole-william/wormhole"
33 | )
34 |
35 | //======================================================================
36 |
37 | type Args struct {
38 | Code string
39 | SaveDir string
40 | OpenCmd string
41 | NoAskOpen bool
42 | Shell string
43 | Overwrite bool
44 | Lower gowid.ISettableComposite
45 | }
46 |
47 | type Controller struct {
48 | Args
49 | }
50 |
51 | // Transfer exists to provide a simple description of the wormhole transfer type
52 | // for displaying to the user.
53 | type Transfer wormhole.TransferType
54 |
55 | func (tt Transfer) String() string {
56 | switch wormhole.TransferType(tt) {
57 | case wormhole.TransferFile:
58 | return "file"
59 | case wormhole.TransferDirectory:
60 | return "directory"
61 | case wormhole.TransferText:
62 | return "message"
63 | default:
64 | return "unknown message"
65 | }
66 | }
67 |
68 | //======================================================================
69 |
70 | func New(args Args) *Controller {
71 | res := &Controller{
72 | Args: args,
73 | }
74 | return res
75 | }
76 |
77 | func (w *Controller) Start(app gowid.IApp) {
78 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
79 | if w.Args.Code == "" {
80 | w.noCode(app)
81 | } else {
82 | w.displayCode(app)
83 | }
84 | }))
85 | }
86 |
87 | //======================================================================
88 |
89 | func makeTxtDialog(txt string, buttons ...dialog.Button) *dialog.Widget {
90 | return makeDialog(text.New(txt), gowid.RenderFixed{}, buttons...)
91 | }
92 |
93 | func makeDialog(w gowid.IWidget, wid gowid.IWidgetDimension, buttons ...dialog.Button) *dialog.Widget {
94 | d := &dialog.Widget{}
95 |
96 | for _, b := range buttons {
97 | b.Action.(iPrevious).SetPrevious(d)
98 | }
99 |
100 | *d = *dialog.New(
101 | framed.NewSpace(
102 | hpadding.New(
103 | w,
104 | gowid.HAlignMiddle{},
105 | wid,
106 | )),
107 | dialog.Options{
108 | Buttons: buttons,
109 | NoEscapeClose: true,
110 | NoShadow: true,
111 | BackgroundStyle: gowid.MakePaletteRef("dialog"),
112 | BorderStyle: gowid.MakePaletteRef("dialog"),
113 | ButtonStyle: gowid.MakePaletteRef("dialog-button"),
114 | },
115 | )
116 | return d
117 | }
118 |
119 | //======================================================================
120 |
121 | type iPrevious interface {
122 | SetPrevious(*dialog.Widget)
123 | }
124 |
125 | type common struct {
126 | previous *dialog.Widget // so it can be closed
127 | }
128 |
129 | func (d common) ID() interface{} {
130 | return "dummy"
131 | }
132 |
133 | func (d *common) SetPrevious(p *dialog.Widget) {
134 | d.previous = p
135 | }
136 |
137 | //======================================================================
138 |
139 | type quit struct {
140 | common
141 | *Controller
142 | }
143 |
144 | // Show the code - hit Cancel button
145 | func (w quit) Changed(app gowid.IApp, widget gowid.IWidget, data ...interface{}) {
146 | app.Quit()
147 | }
148 |
149 | //======================================================================
150 |
151 | type showCodeOk struct {
152 | common
153 | *Controller
154 | }
155 |
156 | // Show the code - hit Ok button
157 | func (w showCodeOk) Changed(app gowid.IApp, widget gowid.IWidget, data ...interface{}) {
158 | var client wormhole.Client
159 |
160 | // goroutine so I don't block ui goroutine
161 | go func() {
162 | ctx := context.Background()
163 | msg, err := client.Receive(ctx, w.Args.Code)
164 | reject := true
165 |
166 | if err != nil {
167 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
168 | w.previous.Close(app)
169 | w.doReceiveError(err, app)
170 | }))
171 | return
172 | }
173 |
174 | defer func() {
175 | //fmt.Fprintf(os.Stderr, "GCLA: will I reject? reject is %v\n", reject)
176 | if reject {
177 | msg.Reject()
178 | }
179 | }()
180 |
181 | switch msg.Type {
182 | case wormhole.TransferText:
183 |
184 | // Wormhole william doesn't allow rejecting text message
185 | // transfers
186 | reject = false
187 |
188 | spin := spinner.New(spinner.Options{
189 | Styler: gowid.MakePaletteRef("progress-spinner"),
190 | })
191 |
192 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
193 | w.previous.Close(app)
194 | w.doSpin(spin, app)
195 | }))
196 |
197 | done := make(chan struct{})
198 | var transferredMessage []byte
199 |
200 | go func() {
201 | transferredMessage, err = ioutil.ReadAll(msg)
202 |
203 | defer close(done)
204 |
205 | if err != nil {
206 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
207 | w.previous.Close(app)
208 | w.doTextTransferError(err, app)
209 | }))
210 | return
211 | }
212 |
213 | // Artificial delay makes a nicer experience
214 | time.Sleep(1000 * time.Millisecond)
215 | }()
216 |
217 | go func() {
218 | c := time.Tick(100 * time.Millisecond)
219 | loop:
220 | for {
221 | select {
222 | case <-done:
223 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
224 | w.previous.Close(app)
225 | w.doMessageThenQuit(string(transferredMessage), "Quit", app)
226 | }))
227 | break loop
228 | case <-c:
229 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
230 | spin.Update()
231 | }))
232 | }
233 | }
234 | }()
235 |
236 | case wormhole.TransferFile:
237 | prog := progress.New(progress.Options{
238 | Normal: gowid.MakePaletteRef("progress-default"),
239 | Complete: gowid.MakePaletteRef("progress-complete"),
240 | })
241 |
242 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
243 | w.previous.Close(app)
244 | w.doProg(msg.Name, Transfer(msg.Type), prog, app)
245 | //w.InTransfer = true
246 | }))
247 |
248 | done := make(chan struct{})
249 | read := 0
250 |
251 | // Only set if dir or file
252 | savedFilename := filepath.Join(w.Args.SaveDir, msg.Name)
253 | if !w.Args.Overwrite && fileExists(savedFilename) {
254 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
255 | w.previous.Close(app)
256 | w.doNoOverwrite(savedFilename, app)
257 | }))
258 | return
259 | }
260 |
261 | f, err := os.Create(savedFilename)
262 | if err != nil {
263 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
264 | w.previous.Close(app)
265 | w.doFileCreateError(savedFilename, err, app)
266 | }))
267 | return
268 | }
269 |
270 | reject = false
271 |
272 | go func() {
273 | _, err = io.Copy(f, &progReader{read: &read, Reader: msg})
274 |
275 | defer func() {
276 | f.Close()
277 | close(done)
278 | }()
279 |
280 | if err != nil {
281 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
282 | w.previous.Close(app)
283 | w.doFileTransferError(savedFilename, err, app)
284 | }))
285 | return
286 | }
287 | }()
288 |
289 | go func() {
290 | c := time.Tick(250 * time.Millisecond)
291 | loop:
292 | for {
293 | select {
294 | case <-done:
295 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
296 | prog.SetTarget(app, int(msg.UncompressedBytes64))
297 | prog.SetProgress(app, int(msg.UncompressedBytes64))
298 |
299 | // Delay at 100% is nice
300 | time.AfterFunc(1*time.Second, func() {
301 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
302 | //w.InTransfer = false
303 | w.previous.Close(app)
304 |
305 | if w.Args.OpenCmd == "" {
306 | w.doSavedAs(savedFilename, app)
307 | } else {
308 | if w.Args.NoAskOpen {
309 | w.doOpen(savedFilename, app)
310 | } else {
311 | w.doAskToOpen(savedFilename, app)
312 | }
313 | }
314 | }))
315 | })
316 |
317 | }))
318 | break loop
319 | case <-c:
320 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
321 | prog.SetTarget(app, int(msg.UncompressedBytes64))
322 | prog.SetProgress(app, read)
323 | }))
324 | }
325 | }
326 | }()
327 |
328 | case wormhole.TransferDirectory:
329 |
330 | dirName := filepath.Join(w.Args.SaveDir, msg.Name)
331 |
332 | err = os.Mkdir(dirName, 0777)
333 | if err != nil {
334 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
335 | w.previous.Close(app)
336 | w.doError(err, app)
337 | }))
338 | return
339 | }
340 |
341 | tmpFile, err := ioutil.TempFile(w.Args.SaveDir, fmt.Sprintf("%s.zip.tmp", msg.Name))
342 | if err != nil {
343 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
344 | w.previous.Close(app)
345 | w.doError(err, app)
346 | }))
347 | return
348 | }
349 |
350 | prog := progress.New(progress.Options{
351 | Normal: gowid.MakePaletteRef("progress-default"),
352 | Complete: gowid.MakePaletteRef("progress-complete"),
353 | })
354 |
355 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
356 | w.previous.Close(app)
357 | w.doProg(msg.Name, Transfer(msg.Type), prog, app)
358 | }))
359 |
360 | done := make(chan struct{})
361 | read := 0
362 |
363 | reject = false
364 |
365 | go func() {
366 |
367 | errme := func(w showCodeOk, err error, app gowid.IApp) {
368 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
369 | w.previous.Close(app)
370 | w.doError(err, app)
371 | }))
372 | }
373 |
374 | defer func() {
375 | tmpFile.Close()
376 | os.Remove(tmpFile.Name())
377 | close(done)
378 | }()
379 |
380 | n, err := io.Copy(tmpFile, &progReader{read: &read, Reader: msg})
381 |
382 | if err != nil {
383 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
384 | w.previous.Close(app)
385 | w.doFileTransferError(msg.Name, err, app)
386 | }))
387 | return
388 | }
389 |
390 | tmpFile.Seek(0, io.SeekStart)
391 | zr, err := zip.NewReader(tmpFile, int64(n))
392 | if err != nil {
393 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
394 | w.previous.Close(app)
395 | w.doError(err, app)
396 | }))
397 | return
398 | }
399 |
400 | for _, zf := range zr.File {
401 | p, err := filepath.Abs(filepath.Join(dirName, zf.Name))
402 | if err != nil {
403 | errme(w, err, app)
404 | return
405 | }
406 |
407 | if !strings.HasPrefix(p, dirName) {
408 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
409 | w.previous.Close(app)
410 | w.doMessageThenQuit(fmt.Sprintf("Dangerous filename found: %s", zf.Name), "Quit", app)
411 | }))
412 | return
413 | }
414 |
415 | rc, err := zf.Open()
416 | if err != nil {
417 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
418 | w.previous.Close(app)
419 | w.doMessageThenQuit(fmt.Sprintf("%s open failed: %v", zf.Name, err), "Quit", app)
420 | }))
421 | return
422 | }
423 |
424 | dir := filepath.Dir(p)
425 | err = os.MkdirAll(dir, 0777)
426 | if err != nil {
427 | errme(w, err, app)
428 | return
429 | }
430 |
431 | f, err := os.Create(p)
432 | if err != nil {
433 | errme(w, err, app)
434 | return
435 | }
436 |
437 | _, err = io.Copy(f, rc)
438 | if err != nil {
439 | errme(w, err, app)
440 | return
441 | }
442 |
443 | err = f.Close()
444 | if err != nil {
445 | errme(w, err, app)
446 | return
447 | }
448 |
449 | rc.Close()
450 | }
451 | }()
452 |
453 | go func() {
454 | c := time.Tick(250 * time.Millisecond)
455 | loop:
456 | for {
457 | select {
458 | case <-done:
459 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
460 | prog.SetTarget(app, int(msg.UncompressedBytes64))
461 | prog.SetProgress(app, int(msg.UncompressedBytes64))
462 |
463 | // Delay at 100% is nice
464 | time.AfterFunc(1*time.Second, func() {
465 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
466 | w.previous.Close(app)
467 | w.doSavedAs(dirName, app)
468 | }))
469 | })
470 |
471 | }))
472 | break loop
473 | case <-c:
474 | app.Run(gowid.RunFunction(func(app gowid.IApp) {
475 | prog.SetTarget(app, int(msg.UncompressedBytes64))
476 | prog.SetProgress(app, read)
477 | }))
478 | }
479 | }
480 | }()
481 |
482 | }
483 |
484 | }()
485 | }
486 |
487 | //======================================================================
488 |
489 | func (w *Controller) openSaveError(savedFilename string, cmd string, err error, app gowid.IApp) {
490 | txt := fmt.Sprintf("Error opening: %s: %v", cmd, err)
491 | d := makeTxtDialog(txt,
492 | dialog.Button{
493 | Msg: "Continue",
494 | Action: &savedAs{savedFilename: savedFilename, Controller: w},
495 | },
496 | )
497 |
498 | dialog.OpenExt(d, w.Lower, gowid.RenderWithUnits{U: len(txt) + 10}, gowid.RenderFlow{}, app)
499 | }
500 |
501 | //======================================================================
502 |
503 | type savedAs struct {
504 | common
505 | savedFilename string
506 | *Controller
507 | }
508 |
509 | // Show the code - hit Cancel button
510 | func (w savedAs) Changed(app gowid.IApp, widget gowid.IWidget, data ...interface{}) {
511 | w.previous.Close(app)
512 | w.doSavedAs(w.savedFilename, app)
513 | }
514 |
515 | func (w *Controller) doSavedAs(savedFilename string, app gowid.IApp) {
516 | w.doMessageThenQuit(fmt.Sprintf("Saved as %s", savedFilename), "Ok", app)
517 | }
518 |
519 | //======================================================================
520 |
521 | func (w *Controller) doProg(name string, trans Transfer, prog *progress.Widget, app gowid.IApp) {
522 | txt := fmt.Sprintf("Transferring %s %s...", trans, name)
523 |
524 | rows := pile.NewFlow(
525 | text.New(txt),
526 | divider.NewBlank(),
527 | prog,
528 | )
529 |
530 | d := makeDialog(rows,
531 | gowid.RenderFlow{},
532 | dialog.Button{
533 | Msg: "Cancel",
534 | // Can't really cancel, can't interrupt receive
535 | Action: &quit{Controller: w},
536 | },
537 | )
538 |
539 | dialog.OpenExt(d, w.Lower, gowid.RenderWithUnits{U: gwutil.Max(32, len(txt)+10)}, gowid.RenderFlow{}, app)
540 | }
541 |
542 | //======================================================================
543 |
544 | func (w *Controller) doSpin(spin *spinner.Widget, app gowid.IApp) {
545 | txt := "Transferring message..."
546 |
547 | rows := pile.NewFlow(
548 | text.New(txt),
549 | divider.NewBlank(),
550 | spin,
551 | )
552 |
553 | d := makeDialog(rows,
554 | gowid.RenderFlow{},
555 | dialog.Button{
556 | Msg: "Cancel",
557 | Action: &quit{Controller: w},
558 | },
559 | )
560 |
561 | dialog.OpenExt(d, w.Lower, gowid.RenderWithUnits{U: gwutil.Min(32, len(txt)+10)}, gowid.RenderFlow{}, app)
562 | }
563 |
564 | //======================================================================
565 |
566 | func (w *Controller) doReceiveError(err error, app gowid.IApp) {
567 | w.doMessageThenQuit(fmt.Sprintf("Error: %v", err), "Quit", app)
568 | }
569 |
570 | //======================================================================
571 |
572 | func (w *Controller) doFileCreateError(filename string, err error, app gowid.IApp) {
573 | w.doMessageThenQuit(fmt.Sprintf("Error creating %s: %v", filename, err), "Quit", app)
574 | }
575 |
576 | //======================================================================
577 |
578 | func (w *Controller) doFileTransferError(filename string, err error, app gowid.IApp) {
579 | w.doMessageThenQuit(fmt.Sprintf("Error transferring %s: %v", filename, err), "Quit", app)
580 | }
581 |
582 | //======================================================================
583 |
584 | func (w *Controller) doTextTransferError(err error, app gowid.IApp) {
585 | w.doMessageThenQuit(fmt.Sprintf("Error transferring message: %v", err), "Quit", app)
586 | }
587 |
588 | //======================================================================
589 |
590 | func (w *Controller) noCode(app gowid.IApp) {
591 | w.doMessageThenQuit("No wormhole code found!", "Quit", app)
592 | }
593 |
594 | //======================================================================
595 |
596 | func (w *Controller) doNoOverwrite(savedFilename string, app gowid.IApp) {
597 | w.doMessageThenQuit(fmt.Sprintf("%s exists. Will not overwrite.", savedFilename), "Quit", app)
598 | }
599 |
600 | //======================================================================
601 |
602 | func (w *Controller) doError(err error, app gowid.IApp) {
603 | w.doMessageThenQuit(fmt.Sprintf("Error: %v", err), "Quit", app)
604 | }
605 |
606 | //======================================================================
607 |
608 | func (w *Controller) doMessageThenQuit(message string, label string, app gowid.IApp) {
609 | txt := fmt.Sprintf("%s", message)
610 | d := makeTxtDialog(txt,
611 | dialog.Button{
612 | Msg: label,
613 | Action: &quit{Controller: w},
614 | },
615 | )
616 |
617 | dialog.OpenExt(d, w.Lower, gowid.RenderWithUnits{U: len(txt) + 10}, gowid.RenderFlow{}, app)
618 | }
619 |
620 | //======================================================================
621 |
622 | func (w *Controller) displayCode(app gowid.IApp) {
623 | txt := fmt.Sprintf("%s. Proceed?", w.Args.Code)
624 |
625 | d := makeTxtDialog(txt,
626 | dialog.Button{
627 | Msg: "Ok",
628 | Action: &showCodeOk{Controller: w},
629 | },
630 | dialog.Button{
631 | Msg: "Cancel",
632 | Action: &quit{Controller: w},
633 | },
634 | )
635 |
636 | dialog.OpenExt(d, w.Lower, gowid.RenderWithUnits{U: len(txt) + 10}, gowid.RenderFlow{}, app)
637 | }
638 |
639 | //======================================================================
640 |
641 | type open struct {
642 | common
643 | savedFilename string
644 | *Controller
645 | }
646 |
647 | func (w open) Changed(app gowid.IApp, widget gowid.IWidget, data ...interface{}) {
648 | w.previous.Close(app)
649 | w.doOpen(w.savedFilename, app)
650 | }
651 |
652 | func (w *Controller) doOpen(savedFilename string, app gowid.IApp) {
653 | var shellCmd string
654 | if strings.Contains(w.Args.OpenCmd, "%s") {
655 | shellCmd = strings.Replace(w.Args.OpenCmd, "%s", shellescape.Quote(savedFilename), -1)
656 | } else {
657 | shellCmd = w.Args.OpenCmd + " " + shellescape.Quote(savedFilename)
658 | }
659 | err := exec.Command(w.Args.Shell, "-c", shellCmd).Run()
660 |
661 | if err == nil {
662 | w.doSavedAs(savedFilename, app)
663 | } else {
664 | w.openSaveError(savedFilename, shellCmd, err, app)
665 | }
666 | }
667 |
668 | //======================================================================
669 |
670 | func (w *Controller) doAskToOpen(savedFilename string, app gowid.IApp) {
671 | txt := fmt.Sprintf("Open %s?", savedFilename)
672 | d := makeTxtDialog(txt,
673 | dialog.Button{
674 | Msg: "Yes",
675 | Action: &open{savedFilename: savedFilename, Controller: w},
676 | },
677 | dialog.Button{
678 | Msg: "No",
679 | Action: &savedAs{savedFilename: savedFilename, Controller: w},
680 | },
681 | )
682 |
683 | dialog.OpenExt(d, w.Lower, gowid.RenderWithUnits{U: len(txt) + 10}, gowid.RenderFlow{}, app)
684 | }
685 |
686 | //======================================================================
687 |
688 | func fileExists(filename string) bool {
689 | _, err := os.Stat(filename)
690 | return !os.IsNotExist(err)
691 | }
692 |
693 | //======================================================================
694 |
695 | type progReader struct {
696 | read *int
697 | io.Reader
698 | }
699 |
700 | func (r *progReader) Read(p []byte) (n int, err error) {
701 | n, err = r.Reader.Read(p)
702 | (*r.read) += n
703 | return
704 | }
705 |
706 | //======================================================================
707 | // Local Variables:
708 | // mode: Go
709 | // fill-column: 110
710 | // End:
711 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
17 | github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
18 | github.com/adrg/xdg v0.3.2 h1:GUSGQ5pHdev83AYhDSS1A/CX+0JIsxbiWtow2DSA+RU=
19 | github.com/adrg/xdg v0.3.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
20 | github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
21 | github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
22 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
23 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
24 | github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
25 | github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
26 | github.com/araddon/dateparse v0.0.0-20210207001429-0eec95c9db7e/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw=
27 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
28 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
29 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
30 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
31 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
32 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
33 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
34 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
35 | github.com/cheggaaa/pb/v3 v3.0.1/go.mod h1:SqqeMF/pMOIu3xgGoxtPYhMNQP258xE4x/XRTYua+KU=
36 | github.com/cheggaaa/pb/v3 v3.0.8/go.mod h1:UICbiLec/XO6Hw6k+BHEtHeQFzzBH4i2/qk/ow1EJTA=
37 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
38 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
39 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
40 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
41 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
42 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
43 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
44 | github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
45 | github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
46 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
47 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
48 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
49 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
50 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
51 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
52 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
53 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
54 | github.com/gcla/gowid v1.2.1-0.20210403174333-badb5d3b9c2e h1:1HY4w4hXm9Q7QlqOQBZD45GC3VRnD6oDcb2lEpoiv/Q=
55 | github.com/gcla/gowid v1.2.1-0.20210403174333-badb5d3b9c2e/go.mod h1:uXYWVvhYMv/0pi2nZeIuogFporMhiivk/PPFmi+lQKw=
56 | github.com/gcla/gowid v1.3.0 h1:mKg/eGCmRn65BqS3PwI5tldBJGb5mD91E6Mb06W9mbo=
57 | github.com/gcla/gowid v1.3.0/go.mod h1:GL9KUFwKHjWS80G7N4hkA++mpOleVQpNvKIHyW9n0i4=
58 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
59 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
60 | github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29 h1:kvzEHvL4/ORuWe6JN6WeaiRYIvVDUVaC2r0gpJIxJ6I=
61 | github.com/gdamore/tcell v1.3.1-0.20200115030318-bff4943f9a29/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
62 | github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
63 | github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
64 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
65 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
66 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
67 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
68 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
69 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
70 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
71 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
72 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
73 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
74 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
75 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
76 | github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
77 | github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
78 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
79 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
80 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
81 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
82 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
83 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
84 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
85 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
86 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
87 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
88 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
89 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
90 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
91 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
92 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
93 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
94 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
95 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
96 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
97 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
98 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
99 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
100 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
101 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
102 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
103 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
104 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
105 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
106 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
107 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
108 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
109 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
110 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
111 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
112 | github.com/guptarohit/asciigraph v0.4.1/go.mod h1:9fYEfE5IGJGxlP1B+w8wHFy7sNZMhPtn59f0RLtpRFM=
113 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
114 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
115 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
116 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
117 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
118 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
119 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
120 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
121 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
122 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
123 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
124 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
125 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
126 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
127 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
128 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
129 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
130 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
131 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
132 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
133 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
134 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
135 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
136 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
137 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
138 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
139 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
140 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
141 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
142 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
143 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
144 | github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU=
145 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
146 | github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
147 | github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
148 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
149 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
150 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
151 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
152 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
153 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
154 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
155 | github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
156 | github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
157 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
158 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
159 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
160 | github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
161 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
162 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
163 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
164 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
165 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
166 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
167 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
168 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
169 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
170 | github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
171 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
172 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
173 | github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
174 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
175 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
176 | github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
177 | github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXlBqXQEwvE0=
178 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
179 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
180 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
181 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
182 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
183 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
184 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
185 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
186 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
187 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
188 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
189 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
190 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
191 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
192 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
193 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
194 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
195 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
196 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
197 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
198 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
199 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
200 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
201 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
202 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
203 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
204 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
205 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
206 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
207 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
208 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
209 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
210 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
211 | github.com/psanford/wormhole-william v1.0.5 h1:+Nb8ChCUpcvW6p4YwJin3Q8TK8SgtQB7JyVcbuVX0gc=
212 | github.com/psanford/wormhole-william v1.0.5/go.mod h1:DSRQm537D3ut3AlCOL2iUrAT+WuYjvHQkWkdynYpR/w=
213 | github.com/psanford/wormhole-william v1.0.6 h1:kph5PiBaSU3Fi/lBlMQTG1pSU6RxSe6fNFndIe9Vg40=
214 | github.com/psanford/wormhole-william v1.0.6/go.mod h1:IkhgE1mq0vzlf/WPdjXYTtTn/ItOfcdY7rhjdqK6k14=
215 | github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
216 | github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
217 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
218 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
219 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
220 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
221 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
222 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
223 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
224 | github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
225 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
226 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
227 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
228 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
229 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
230 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
231 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
232 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
233 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
234 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
235 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
236 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
237 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
238 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
239 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
240 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
241 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
242 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
243 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
244 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
245 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
246 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
247 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
248 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
249 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
250 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
251 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
252 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
253 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
254 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
255 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
256 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
257 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
258 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
259 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
260 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
261 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
262 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
263 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
264 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
265 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
266 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
267 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
268 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
269 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
270 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
271 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
272 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
273 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
274 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
275 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
276 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
277 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
278 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
279 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
280 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
281 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
282 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
283 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
284 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
285 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
286 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
287 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
288 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
289 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
290 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
291 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
292 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
293 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
294 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
295 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
296 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
297 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
298 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
299 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
300 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
301 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
302 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
303 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
304 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
305 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
306 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
307 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
308 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
309 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
310 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
311 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
312 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
313 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
314 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
315 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
316 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
317 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
318 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
319 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
320 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
321 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
322 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
323 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
324 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
325 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
326 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
327 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
328 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
329 | golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
330 | golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
331 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
332 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
333 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
334 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
335 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
336 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
337 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
338 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
339 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
340 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
341 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
342 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
343 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
344 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
345 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
346 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
347 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
348 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
349 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
350 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
351 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
352 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
353 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
354 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
355 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
356 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
357 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
358 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
359 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
360 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
361 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
362 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
363 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
364 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
365 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
366 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
367 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
368 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
369 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
370 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
371 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
372 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
373 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
374 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
375 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
376 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
377 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
378 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
379 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
380 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
381 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
382 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
383 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
384 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
385 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
386 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
387 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
388 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
389 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
390 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
391 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
392 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
393 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
394 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
395 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
396 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
397 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
398 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
399 | nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
400 | nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
401 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
402 | rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
403 | salsa.debian.org/vasudev/gospake2 v0.0.0-20180813171123-adcc69dd31d5 h1:j+F9fFxAFNdrO85XnERJSYS5QGfPUWUy9IP4s9BkV6A=
404 | salsa.debian.org/vasudev/gospake2 v0.0.0-20180813171123-adcc69dd31d5/go.mod h1:soKzqXBAtqHTODjyA0VzH2iERtpzN1w65eZUfetn2cQ=
405 |
--------------------------------------------------------------------------------