├── 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 | ![tmux-wormhole](https://user-images.githubusercontent.com/45680/113491108-37fbf400-949c-11eb-80f5-829b045f1701.gif) 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 | --------------------------------------------------------------------------------