From 0ec6899a4c412e77f18aa74df7f223b46c5206c8 Mon Sep 17 00:00:00 2001 From: Marvin Steadfast Date: Wed, 14 Apr 2021 10:40:57 +0200 Subject: [PATCH] better integration tests and udev rules --- assets/assets.go | 25 + .../assets => assets}/files/ntp.service | 0 .../files/schnutibox.service | 0 .../assets => assets}/files/upmpdcli.conf | 0 assets/templates/50-neuftech.rules.tmpl | 1 + .../templates/fstab.raspbian.tmpl | 0 .../templates/mopidy.conf.tmpl | 3 + assets/templates/schnutibox.yml.tmpl | 6 + cmd/root.go | 5 + go.mod | 1 + go.sum | 16 + integration_test.go | 146 ++- pkg/prepare/prepare.go | 95 +- pkg/prepare/prepare_test.go | 18 - tools/findhidraw/findhidraw.go | 44 + vendor/github.com/chzyer/readline/.gitignore | 1 + vendor/github.com/chzyer/readline/.travis.yml | 8 + .../github.com/chzyer/readline/CHANGELOG.md | 58 ++ vendor/github.com/chzyer/readline/LICENSE | 22 + vendor/github.com/chzyer/readline/README.md | 114 +++ .../chzyer/readline/ansi_windows.go | 249 +++++ vendor/github.com/chzyer/readline/complete.go | 285 ++++++ .../chzyer/readline/complete_helper.go | 165 ++++ .../chzyer/readline/complete_segment.go | 82 ++ vendor/github.com/chzyer/readline/history.go | 330 +++++++ .../github.com/chzyer/readline/operation.go | 531 +++++++++++ vendor/github.com/chzyer/readline/password.go | 33 + .../chzyer/readline/rawreader_windows.go | 125 +++ vendor/github.com/chzyer/readline/readline.go | 326 +++++++ vendor/github.com/chzyer/readline/remote.go | 475 ++++++++++ vendor/github.com/chzyer/readline/runebuf.go | 629 +++++++++++++ vendor/github.com/chzyer/readline/runes.go | 223 +++++ vendor/github.com/chzyer/readline/search.go | 164 ++++ vendor/github.com/chzyer/readline/std.go | 197 ++++ .../github.com/chzyer/readline/std_windows.go | 9 + vendor/github.com/chzyer/readline/term.go | 123 +++ vendor/github.com/chzyer/readline/term_bsd.go | 29 + .../github.com/chzyer/readline/term_linux.go | 33 + .../chzyer/readline/term_solaris.go | 32 + .../github.com/chzyer/readline/term_unix.go | 24 + .../chzyer/readline/term_windows.go | 171 ++++ vendor/github.com/chzyer/readline/terminal.go | 238 +++++ vendor/github.com/chzyer/readline/utils.go | 277 ++++++ .../github.com/chzyer/readline/utils_unix.go | 83 ++ .../chzyer/readline/utils_windows.go | 41 + vendor/github.com/chzyer/readline/vim.go | 176 ++++ .../github.com/chzyer/readline/windows_api.go | 152 +++ vendor/github.com/juju/ansiterm/LICENSE | 191 ++++ vendor/github.com/juju/ansiterm/Makefile | 14 + vendor/github.com/juju/ansiterm/README.md | 323 +++++++ vendor/github.com/juju/ansiterm/attribute.go | 50 + vendor/github.com/juju/ansiterm/color.go | 119 +++ vendor/github.com/juju/ansiterm/context.go | 95 ++ vendor/github.com/juju/ansiterm/doc.go | 6 + vendor/github.com/juju/ansiterm/style.go | 72 ++ vendor/github.com/juju/ansiterm/tabwriter.go | 64 ++ .../juju/ansiterm/tabwriter/LICENSE | 27 + .../juju/ansiterm/tabwriter/tabwriter.go | 587 ++++++++++++ vendor/github.com/juju/ansiterm/terminal.go | 32 + vendor/github.com/juju/ansiterm/writer.go | 74 ++ .../github.com/lunixbochs/vtclean/.travis.yml | 9 + vendor/github.com/lunixbochs/vtclean/LICENSE | 19 + .../github.com/lunixbochs/vtclean/README.md | 46 + vendor/github.com/lunixbochs/vtclean/io.go | 93 ++ vendor/github.com/lunixbochs/vtclean/line.go | 113 +++ .../github.com/lunixbochs/vtclean/vtclean.go | 95 ++ .../github.com/manifoldco/promptui/.gitignore | 3 + .../manifoldco/promptui/.golangci.yml | 26 + .../manifoldco/promptui/.travis.yml | 14 + .../manifoldco/promptui/CHANGELOG.md | 123 +++ .../manifoldco/promptui/CODE_OF_CONDUCT.md | 73 ++ .../github.com/manifoldco/promptui/LICENSE.md | 29 + .../github.com/manifoldco/promptui/Makefile | 49 + .../github.com/manifoldco/promptui/README.md | 107 +++ .../github.com/manifoldco/promptui/codes.go | 120 +++ .../github.com/manifoldco/promptui/cursor.go | 232 +++++ vendor/github.com/manifoldco/promptui/go.mod | 16 + vendor/github.com/manifoldco/promptui/go.sum | 23 + .../manifoldco/promptui/keycodes.go | 29 + .../manifoldco/promptui/keycodes_other.go | 10 + .../manifoldco/promptui/keycodes_windows.go | 10 + .../manifoldco/promptui/list/list.go | 237 +++++ .../github.com/manifoldco/promptui/prompt.go | 341 +++++++ .../manifoldco/promptui/promptui.go | 27 + .../promptui/screenbuf/screenbuf.go | 151 +++ .../github.com/manifoldco/promptui/select.go | 637 +++++++++++++ .../github.com/manifoldco/promptui/styles.go | 23 + .../manifoldco/promptui/styles_windows.go | 21 + .../github.com/mattn/go-colorable/.travis.yml | 9 + vendor/github.com/mattn/go-colorable/LICENSE | 21 + .../github.com/mattn/go-colorable/README.md | 48 + .../mattn/go-colorable/colorable_appengine.go | 29 + .../mattn/go-colorable/colorable_others.go | 30 + .../mattn/go-colorable/colorable_windows.go | 884 ++++++++++++++++++ .../mattn/go-colorable/noncolorable.go | 55 ++ vendor/github.com/mattn/go-isatty/.travis.yml | 13 + vendor/github.com/mattn/go-isatty/LICENSE | 9 + vendor/github.com/mattn/go-isatty/README.md | 50 + vendor/github.com/mattn/go-isatty/doc.go | 2 + .../mattn/go-isatty/isatty_appengine.go | 15 + .../github.com/mattn/go-isatty/isatty_bsd.go | 18 + .../mattn/go-isatty/isatty_linux.go | 18 + .../mattn/go-isatty/isatty_linux_ppc64x.go | 19 + .../mattn/go-isatty/isatty_others.go | 10 + .../mattn/go-isatty/isatty_solaris.go | 16 + .../mattn/go-isatty/isatty_windows.go | 94 ++ vendor/modules.txt | 16 + 107 files changed, 11362 insertions(+), 89 deletions(-) create mode 100644 assets/assets.go rename {pkg/prepare/assets => assets}/files/ntp.service (100%) rename {pkg/prepare/assets => assets}/files/schnutibox.service (100%) rename {pkg/prepare/assets => assets}/files/upmpdcli.conf (100%) create mode 100644 assets/templates/50-neuftech.rules.tmpl rename {pkg/prepare/assets => assets}/templates/fstab.raspbian.tmpl (100%) rename {pkg/prepare/assets => assets}/templates/mopidy.conf.tmpl (90%) create mode 100644 assets/templates/schnutibox.yml.tmpl delete mode 100644 pkg/prepare/prepare_test.go create mode 100644 tools/findhidraw/findhidraw.go create mode 100644 vendor/github.com/chzyer/readline/.gitignore create mode 100644 vendor/github.com/chzyer/readline/.travis.yml create mode 100644 vendor/github.com/chzyer/readline/CHANGELOG.md create mode 100644 vendor/github.com/chzyer/readline/LICENSE create mode 100644 vendor/github.com/chzyer/readline/README.md create mode 100644 vendor/github.com/chzyer/readline/ansi_windows.go create mode 100644 vendor/github.com/chzyer/readline/complete.go create mode 100644 vendor/github.com/chzyer/readline/complete_helper.go create mode 100644 vendor/github.com/chzyer/readline/complete_segment.go create mode 100644 vendor/github.com/chzyer/readline/history.go create mode 100644 vendor/github.com/chzyer/readline/operation.go create mode 100644 vendor/github.com/chzyer/readline/password.go create mode 100644 vendor/github.com/chzyer/readline/rawreader_windows.go create mode 100644 vendor/github.com/chzyer/readline/readline.go create mode 100644 vendor/github.com/chzyer/readline/remote.go create mode 100644 vendor/github.com/chzyer/readline/runebuf.go create mode 100644 vendor/github.com/chzyer/readline/runes.go create mode 100644 vendor/github.com/chzyer/readline/search.go create mode 100644 vendor/github.com/chzyer/readline/std.go create mode 100644 vendor/github.com/chzyer/readline/std_windows.go create mode 100644 vendor/github.com/chzyer/readline/term.go create mode 100644 vendor/github.com/chzyer/readline/term_bsd.go create mode 100644 vendor/github.com/chzyer/readline/term_linux.go create mode 100644 vendor/github.com/chzyer/readline/term_solaris.go create mode 100644 vendor/github.com/chzyer/readline/term_unix.go create mode 100644 vendor/github.com/chzyer/readline/term_windows.go create mode 100644 vendor/github.com/chzyer/readline/terminal.go create mode 100644 vendor/github.com/chzyer/readline/utils.go create mode 100644 vendor/github.com/chzyer/readline/utils_unix.go create mode 100644 vendor/github.com/chzyer/readline/utils_windows.go create mode 100644 vendor/github.com/chzyer/readline/vim.go create mode 100644 vendor/github.com/chzyer/readline/windows_api.go create mode 100644 vendor/github.com/juju/ansiterm/LICENSE create mode 100644 vendor/github.com/juju/ansiterm/Makefile create mode 100644 vendor/github.com/juju/ansiterm/README.md create mode 100644 vendor/github.com/juju/ansiterm/attribute.go create mode 100644 vendor/github.com/juju/ansiterm/color.go create mode 100644 vendor/github.com/juju/ansiterm/context.go create mode 100644 vendor/github.com/juju/ansiterm/doc.go create mode 100644 vendor/github.com/juju/ansiterm/style.go create mode 100644 vendor/github.com/juju/ansiterm/tabwriter.go create mode 100644 vendor/github.com/juju/ansiterm/tabwriter/LICENSE create mode 100644 vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go create mode 100644 vendor/github.com/juju/ansiterm/terminal.go create mode 100644 vendor/github.com/juju/ansiterm/writer.go create mode 100644 vendor/github.com/lunixbochs/vtclean/.travis.yml create mode 100644 vendor/github.com/lunixbochs/vtclean/LICENSE create mode 100644 vendor/github.com/lunixbochs/vtclean/README.md create mode 100644 vendor/github.com/lunixbochs/vtclean/io.go create mode 100644 vendor/github.com/lunixbochs/vtclean/line.go create mode 100644 vendor/github.com/lunixbochs/vtclean/vtclean.go create mode 100644 vendor/github.com/manifoldco/promptui/.gitignore create mode 100644 vendor/github.com/manifoldco/promptui/.golangci.yml create mode 100644 vendor/github.com/manifoldco/promptui/.travis.yml create mode 100644 vendor/github.com/manifoldco/promptui/CHANGELOG.md create mode 100644 vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md create mode 100644 vendor/github.com/manifoldco/promptui/LICENSE.md create mode 100644 vendor/github.com/manifoldco/promptui/Makefile create mode 100644 vendor/github.com/manifoldco/promptui/README.md create mode 100644 vendor/github.com/manifoldco/promptui/codes.go create mode 100644 vendor/github.com/manifoldco/promptui/cursor.go create mode 100644 vendor/github.com/manifoldco/promptui/go.mod create mode 100644 vendor/github.com/manifoldco/promptui/go.sum create mode 100644 vendor/github.com/manifoldco/promptui/keycodes.go create mode 100644 vendor/github.com/manifoldco/promptui/keycodes_other.go create mode 100644 vendor/github.com/manifoldco/promptui/keycodes_windows.go create mode 100644 vendor/github.com/manifoldco/promptui/list/list.go create mode 100644 vendor/github.com/manifoldco/promptui/prompt.go create mode 100644 vendor/github.com/manifoldco/promptui/promptui.go create mode 100644 vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go create mode 100644 vendor/github.com/manifoldco/promptui/select.go create mode 100644 vendor/github.com/manifoldco/promptui/styles.go create mode 100644 vendor/github.com/manifoldco/promptui/styles_windows.go create mode 100644 vendor/github.com/mattn/go-colorable/.travis.yml create mode 100644 vendor/github.com/mattn/go-colorable/LICENSE create mode 100644 vendor/github.com/mattn/go-colorable/README.md create mode 100644 vendor/github.com/mattn/go-colorable/colorable_appengine.go create mode 100644 vendor/github.com/mattn/go-colorable/colorable_others.go create mode 100644 vendor/github.com/mattn/go-colorable/colorable_windows.go create mode 100644 vendor/github.com/mattn/go-colorable/noncolorable.go create mode 100644 vendor/github.com/mattn/go-isatty/.travis.yml create mode 100644 vendor/github.com/mattn/go-isatty/LICENSE create mode 100644 vendor/github.com/mattn/go-isatty/README.md create mode 100644 vendor/github.com/mattn/go-isatty/doc.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_appengine.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_bsd.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_linux.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_others.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_solaris.go create mode 100644 vendor/github.com/mattn/go-isatty/isatty_windows.go diff --git a/assets/assets.go b/assets/assets.go new file mode 100644 index 0000000..a1188a0 --- /dev/null +++ b/assets/assets.go @@ -0,0 +1,25 @@ +//nolint:gochecknoglobals +package assets + +import _ "embed" + +//go:embed templates/schnutibox.yml.tmpl +var SchnutiboxConfig string + +//go:embed files/schnutibox.service +var SchnutiboxService []byte + +//go:embed templates/fstab.raspbian.tmpl +var FstabRaspbianTemplate string + +//go:embed templates/mopidy.conf.tmpl +var MopidyConf string + +//go:embed files/upmpdcli.conf +var UpmpdcliConf []byte + +//go:embed files/ntp.service +var NtpService []byte + +//go:embed templates/50-neuftech.rules.tmpl +var UDEVRules string diff --git a/pkg/prepare/assets/files/ntp.service b/assets/files/ntp.service similarity index 100% rename from pkg/prepare/assets/files/ntp.service rename to assets/files/ntp.service diff --git a/pkg/prepare/assets/files/schnutibox.service b/assets/files/schnutibox.service similarity index 100% rename from pkg/prepare/assets/files/schnutibox.service rename to assets/files/schnutibox.service diff --git a/pkg/prepare/assets/files/upmpdcli.conf b/assets/files/upmpdcli.conf similarity index 100% rename from pkg/prepare/assets/files/upmpdcli.conf rename to assets/files/upmpdcli.conf diff --git a/assets/templates/50-neuftech.rules.tmpl b/assets/templates/50-neuftech.rules.tmpl new file mode 100644 index 0000000..bcb32c0 --- /dev/null +++ b/assets/templates/50-neuftech.rules.tmpl @@ -0,0 +1 @@ +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="27db", GROUP="{{.SchnutiboxGroup}}" diff --git a/pkg/prepare/assets/templates/fstab.raspbian.tmpl b/assets/templates/fstab.raspbian.tmpl similarity index 100% rename from pkg/prepare/assets/templates/fstab.raspbian.tmpl rename to assets/templates/fstab.raspbian.tmpl diff --git a/pkg/prepare/assets/templates/mopidy.conf.tmpl b/assets/templates/mopidy.conf.tmpl similarity index 90% rename from pkg/prepare/assets/templates/mopidy.conf.tmpl rename to assets/templates/mopidy.conf.tmpl index 661a473..ea53ad7 100644 --- a/pkg/prepare/assets/templates/mopidy.conf.tmpl +++ b/assets/templates/mopidy.conf.tmpl @@ -5,16 +5,19 @@ config_dir = /etc/mopidy/ [audio] output = alsasink mixer = alsamixer +mixer_volume = 100 [alsamixer] card = 0 control= Headphone + [mpd] hostname = 0.0.0.0 {{if .Spotify}} [spotify] +enabled = true username = {{.SpotifyUsername}} password = {{.SpotifyPassword}} client_id = {{.SpotifyClientID}} diff --git a/assets/templates/schnutibox.yml.tmpl b/assets/templates/schnutibox.yml.tmpl new file mode 100644 index 0000000..aeb5a38 --- /dev/null +++ b/assets/templates/schnutibox.yml.tmpl @@ -0,0 +1,6 @@ +--- +Reader: + Dev: {{.RFIDReader}} +Meta: + Stop: "{{.StopID}}" +Tracks: diff --git a/cmd/root.go b/cmd/root.go index 380ba25..35e7555 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -44,6 +44,8 @@ func init() { prepareCmd.Flags().StringVar(&prepare.Cfg.SpotifyPassword, "spotify-password", "", "Spotify password") prepareCmd.Flags().StringVar(&prepare.Cfg.SpotifyClientID, "spotify-client-id", "", "Spotify client ID") prepareCmd.Flags().StringVar(&prepare.Cfg.SpotifyClientSecret, "spotify-client-secret", "", "Spotify client secret") + prepareCmd.Flags().StringVar(&prepare.Cfg.RFIDReader, "rfid-reader", "/dev/hidraw0", "dev path of rfid reader") + prepareCmd.Flags().StringVar(&prepare.Cfg.StopID, "stop-id", "", "ID of stop tag") // Version. rootCmd.AddCommand(versionCmd) @@ -65,6 +67,9 @@ func initConfig() { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) viper.AutomaticEnv() + // Flags. + viper.BindPFlag("reader.dev", prepareCmd.Flags().Lookup("rfid-reader")) + // Parse config file. if cfgFile != "" { viper.SetConfigFile(cfgFile) diff --git a/go.mod b/go.mod index b79ddf6..c9a43e4 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/google/go-cmp v0.5.5 // indirect github.com/helloyi/go-sshclient v1.0.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/manifoldco/promptui v0.8.0 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/ory/dockertest/v3 v3.6.3 github.com/prometheus/client_golang v0.9.3 diff --git a/go.sum b/go.sum index c8d68f1..336c332 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,12 @@ github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJm github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e h1:6JKvHHt396/qabvMhnhUZvWaHZzfVfldxE60TK8YLhg= @@ -130,6 +136,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -145,10 +153,17 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= +github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -315,6 +330,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/integration_test.go b/integration_test.go index 46bbdb2..b5b3283 100644 --- a/integration_test.go +++ b/integration_test.go @@ -19,11 +19,18 @@ import ( ) const ( - sdcard = "/home/marv/tmp/2021-03-04-raspios-buster-armhf-lite.img" - sshUser = "pi" - sshPass = "raspberry" - sshHost = "localhost" - bootWait = 3 * time.Minute + sdcard = "/home/marv/tmp/2021-03-04-raspios-buster-armhf-lite.img" + sshUser = "pi" + sshPass = "raspberry" + sshHost = "localhost" + containerTimeout = 5 * time.Minute +) + +// Variables used for accessing stuff in the test functions. +// nolint:gochecknoglobals +var ( + sshConn string + client *sshclient.Client ) // raspbianWorkCopy creates a temp image file. @@ -83,10 +90,23 @@ func copySchnutibox(user, pass, host string) error { return nil } -//nolint:funlen -func TestIntegration(t *testing.T) { - t.Parallel() +// teardown removes some temp test stuff. +func teardown(img string, pool *dockertest.Pool, resource *dockertest.Resource) { + log.Info().Msg("getting rid of container") + if err := pool.Purge(resource); err != nil { + log.Fatal().Err(err).Msg("could not cleanup") + } + + log.Info().Str("img", img).Msg("deleting temp image") + + if err := os.Remove(img); err != nil { + log.Fatal().Err(err).Msg("could not delete temp image") + } +} + +// nolint:funlen +func TestMain(m *testing.M) { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) // Create tmp image. @@ -99,13 +119,11 @@ func TestIntegration(t *testing.T) { pool, err := dockertest.NewPool("") if err != nil { - log.Error().Err(err).Msg("could not connect to docker") - t.Fatal() + log.Fatal().Err(err).Msg("could not connect to docker") } if err != nil { - log.Error().Err(err).Msg("could not get pwd") - t.Fatal() + log.Fatal().Err(err).Msg("could not get pwd") } log.Info().Msg("starting container") @@ -118,54 +136,55 @@ func TestIntegration(t *testing.T) { ExposedPorts: []string{"5022/tcp"}, }) if err != nil { - log.Error().Err(err).Msg("could not start resource") - t.Fatal() + log.Fatal().Err(err).Msg("could not start resource") } - // Register cleanup. - t.Cleanup(func() { - log.Info().Msg("getting rid of container") - if err := pool.Purge(resource); err != nil { - log.Error().Err(err).Msg("could not cleanup") - t.Fatal() - } - - log.Info().Str("img", img).Msg("deleting temp image") - if err := os.Remove(img); err != nil { - log.Error().Err(err).Msg("could not delete temp image") - t.Fatal() - } - }) - // Starting container. log.Info().Msg("waiting to be ready") - sshConn := sshHost + ":" + resource.GetPort("5022/tcp") + sshConn = sshHost + ":" + resource.GetPort("5022/tcp") - if err := pool.Retry(func() error { - time.Sleep(bootWait) - client, err := sshclient.DialWithPasswd(sshConn, sshUser, sshPass) - if err != nil { - return err + // Channels for checking readyness of the container. + sshReady := make(chan struct{}) + sshError := make(chan error) + + // Constant check for readyness of container. + go func() { + for { + client, err := sshclient.DialWithPasswd(sshConn, sshUser, sshPass) + if err == nil { + if err := client.Close(); err != nil { + sshError <- err + } + sshReady <- struct{}{} + } + + log.Debug().Msg("container not ready yet") + time.Sleep(5 * time.Second) } + }() - if err := client.Close(); err != nil { - return err - } - - return nil - }); err != nil { + select { + case err := <-sshError: log.Error().Err(err).Msg("could not connect to container via ssh") - t.Fatal() + teardown(img, pool, resource) + os.Exit(1) + case <-time.After(containerTimeout): + log.Error().Msg("timeout. could not connect to container via ssh") + teardown(img, pool, resource) + os.Exit(1) + case <-sshReady: + log.Info().Msg("container is ready") } // Connect via SSH. log.Info().Msg("connect via ssh") - client, err := sshclient.DialWithPasswd(sshConn, sshUser, sshPass) + client, err = sshclient.DialWithPasswd(sshConn, sshUser, sshPass) if err != nil { log.Error().Err(err).Msg("could not connect via ssh") - t.Fatal() + teardown(img, pool, resource) + os.Exit(1) } // Copy schnutibox binary to container. @@ -177,7 +196,8 @@ func TestIntegration(t *testing.T) { if err := copySchnutibox(sshUser, sshPass, sshConn); err != nil { log.Error().Err(err).Msg("could not copy schnutibox") - t.Fatal() + teardown(img, pool, resource) + os.Exit(1) } // Move schnutibox to /usr/local/bin @@ -188,12 +208,24 @@ func TestIntegration(t *testing.T) { SetStdio(os.Stdout, os.Stderr). Run(); err != nil { log.Error().Err(err).Msg("could not create /usr/local/bin on container") - t.Fatal() + teardown(img, pool, resource) + os.Exit(1) } // Doing the testing. log.Info().Msg("doing to testing") + // Running the tests. + code := m.Run() + + // Removing container. + teardown(img, pool, resource) + + os.Exit(code) +} + +// nolint:paralleltest +func TestPrepare(t *testing.T) { if err := client. Cmd("sudo schnutibox prepare --read-only"). SetStdio(os.Stdout, os.Stderr). @@ -202,3 +234,25 @@ func TestIntegration(t *testing.T) { t.Fatal() } } + +// nolint:paralleltest +func TestBoxService(t *testing.T) { + if err := client. + Cmd("file /etc/systemd/system/schnutibox.service"). + SetStdio(os.Stdout, os.Stderr). + Run(); err != nil { + log.Error().Err(err).Msg("could not find schnutibox service file") + t.Fatal() + } +} + +// nolint:paralleltest +func TestUdevRules(t *testing.T) { + if err := client. + Cmd("file /etc/udev/rules.d/50-neuftech.rules"). + SetStdio(os.Stdout, os.Stderr). + Run(); err != nil { + log.Error().Err(err).Msg("could not find udev rules file") + t.Fatal() + } +} diff --git a/pkg/prepare/prepare.go b/pkg/prepare/prepare.go index 2f5f4eb..6d91f50 100644 --- a/pkg/prepare/prepare.go +++ b/pkg/prepare/prepare.go @@ -3,7 +3,6 @@ package prepare //nolint:golint import ( - _ "embed" "fmt" "io/ioutil" "os" @@ -15,6 +14,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "go.xsfx.dev/schnutibox/assets" ) const ( @@ -30,30 +30,17 @@ const ( upmpdcliGroup = "nogroup" ) -//go:embed assets/files/schnutibox.service -var serviceFile []byte - -//go:embed assets/templates/fstab.raspbian.tmpl -var fstabRaspbianTemplate string - -//go:embed assets/templates/mopidy.conf.tmpl -var mopidyConf string - -//go:embed assets/files/upmpdcli.conf -var upmpdcliConf []byte - -//go:embed assets/files/ntp.service -var ntpService []byte - // Config. var Cfg = struct { + RFIDReader string ReadOnly bool - System string Spotify bool - SpotifyUsername string - SpotifyPassword string SpotifyClientID string SpotifyClientSecret string + SpotifyPassword string + SpotifyUsername string + StopID string + System string }{} // BoxService creates a systemd service for schnutibox. @@ -70,7 +57,7 @@ func BoxService(filename string, enable bool) error { } //nolint:gosec - if err := ioutil.WriteFile(filename, serviceFile, 0o644); err != nil { + if err := ioutil.WriteFile(filename, assets.SchnutiboxService, 0o644); err != nil { return fmt.Errorf("could not write service file: %w", err) } @@ -106,7 +93,7 @@ func NTP() error { return fmt.Errorf("could not install ntp: %w", err) } - if err := ioutil.WriteFile("/etc/systemd/system/ntp.service", ntpService, 0o644); err != nil { + if err := ioutil.WriteFile("/etc/systemd/system/ntp.service", assets.NtpService, 0o644); err != nil { return fmt.Errorf("could not copy ntp service file: %w", err) } @@ -169,7 +156,7 @@ func Fstab(system string) error { // Chose the right template. // In future it should be a switch statement. - tmpl := fstabRaspbianTemplate + tmpl := assets.FstabRaspbianTemplate // Parse template. t := template.Must(template.New("fstab").Parse(tmpl)) @@ -241,6 +228,32 @@ func RemovePkgs(system string) error { return nil } +func CreateUDEVrules() error { + logger := log.With().Str("stage", "CreateUDEVrules").Logger() + logger.Info().Msg("writing udev rule file") + + // Parse template. + t := template.Must(template.New("udev").Parse(assets.UDEVRules)) + + // Open file. + f, err := os.Create("/etc/udev/rules.d/50-neuftech.rules") + if err != nil { + return fmt.Errorf("could not create file to write: %w", err) + } + defer f.Close() + + // Create and write. + if err := t.Execute(f, struct { + SchnutiboxGroup string + }{ + schnutiboxUser, + }); err != nil { + return fmt.Errorf("could not write templated udev rules: %w", err) + } + + return nil +} + // CreateUser creates schnutibox system user and group. func CreateUser() error { logger := log.With().Str("stage", "CreateUser").Logger() @@ -432,7 +445,7 @@ func Mopidy() error { Cfg.Spotify = true } - t := template.Must(template.New("mopidyConf").Parse(mopidyConf)) + t := template.Must(template.New("mopidyConf").Parse(assets.MopidyConf)) f, err := os.Create("/etc/mopidy/mopidy.conf") if err != nil { @@ -519,21 +532,53 @@ func Upmpdcli() error { } // Create config. - if err := ioutil.WriteFile("/etc/upmpdcli.conf", upmpdcliConf, 0o644); err != nil { + if err := ioutil.WriteFile("/etc/upmpdcli.conf", assets.UpmpdcliConf, 0o644); err != nil { return fmt.Errorf("could not copy upmpdcli config: %w", err) } return nil } +func SchnutiboxConfig() error { + logger := log.With().Str("stage", "Upmpdcli").Logger() + logger.Info().Msg("writing schnutibox config") + + // Parse template. + t := template.Must(template.New("config").Parse(assets.SchnutiboxConfig)) + + // Open file. + f, err := os.Create("/etc/schnutibox/schnutibox.yml") + if err != nil { + return fmt.Errorf("could not create file to write: %w", err) + } + defer f.Close() + + // Create and write. + if err := t.Execute(f, Cfg); err != nil { + return fmt.Errorf("could not write templated udev rules: %w", err) + } + + return nil +} + func Run(cmd *cobra.Command, args []string) { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) - // Running the preparation. + // Install schnutibox service. if err := BoxService(serviceLocation+"/"+serviceFileName, true); err != nil { log.Fatal().Err(err).Msg("could not create schnutibox service") } + // Create schnutibox config. + if err := SchnutiboxConfig(); err != nil { + log.Fatal().Err(err).Msg("could not create schnutibox config.") + } + + // Install udev file. + if err := CreateUDEVrules(); err != nil { + log.Fatal().Err(err).Msg("could not install udev rules") + } + // Setup NTP. if err := NTP(); err != nil { log.Fatal().Err(err).Msg("could not setup ntp") diff --git a/pkg/prepare/prepare_test.go b/pkg/prepare/prepare_test.go deleted file mode 100644 index 51086bf..0000000 --- a/pkg/prepare/prepare_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package prepare_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "go.xsfx.dev/schnutibox/pkg/prepare" -) - -func TestBoxService(t *testing.T) { - t.Parallel() - assert := assert.New(t) - tmpDir := t.TempDir() - serviceFile := tmpDir + "schnutibox.service" - err := prepare.BoxService(serviceFile, false) - assert.NoError(err) - assert.FileExists(serviceFile) -} diff --git a/tools/findhidraw/findhidraw.go b/tools/findhidraw/findhidraw.go new file mode 100644 index 0000000..3c64dd1 --- /dev/null +++ b/tools/findhidraw/findhidraw.go @@ -0,0 +1,44 @@ +package findhidraw + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/manifoldco/promptui" +) + +func hidraws(path string) ([]string, error) { + hs := []string{} + + files, err := ioutil.ReadDir(path) + if err != nil { + return []string{}, fmt.Errorf("could not dir files: %w", err) + } + + for _, f := range files { + if strings.Contains(f.Name(), "hidraw") { + hs = append(hs, f.Name()) + } + } + + return hs, nil +} + +func FindHidraw(path string) (string, error) { + prompt := promptui.Prompt{ + Label: "please unplug reader", + IsConfirm: true, + } + + ok, err := prompt.Run() + if err != nil { + return "", fmt.Errorf("could not read input: %w", err) + } + + if ok != "y" { + return "", fmt.Errorf("did not unplug the reader") + } + + return "", nil +} diff --git a/vendor/github.com/chzyer/readline/.gitignore b/vendor/github.com/chzyer/readline/.gitignore new file mode 100644 index 0000000..a3062be --- /dev/null +++ b/vendor/github.com/chzyer/readline/.gitignore @@ -0,0 +1 @@ +.vscode/* diff --git a/vendor/github.com/chzyer/readline/.travis.yml b/vendor/github.com/chzyer/readline/.travis.yml new file mode 100644 index 0000000..9c35955 --- /dev/null +++ b/vendor/github.com/chzyer/readline/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.x +script: + - GOOS=windows go install github.com/chzyer/readline/example/... + - GOOS=linux go install github.com/chzyer/readline/example/... + - GOOS=darwin go install github.com/chzyer/readline/example/... + - go test -race -v diff --git a/vendor/github.com/chzyer/readline/CHANGELOG.md b/vendor/github.com/chzyer/readline/CHANGELOG.md new file mode 100644 index 0000000..14ff5be --- /dev/null +++ b/vendor/github.com/chzyer/readline/CHANGELOG.md @@ -0,0 +1,58 @@ +# ChangeLog + +### 1.4 - 2016-07-25 + +* [#60][60] Support dynamic autocompletion +* Fix ANSI parser on Windows +* Fix wrong column width in complete mode on Windows +* Remove dependent package "golang.org/x/crypto/ssh/terminal" + +### 1.3 - 2016-05-09 + +* [#38][38] add SetChildren for prefix completer interface +* [#42][42] improve multiple lines compatibility +* [#43][43] remove sub-package(runes) for gopkg compatibility +* [#46][46] Auto complete with space prefixed line +* [#48][48] support suspend process (ctrl+Z) +* [#49][49] fix bug that check equals with previous command +* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty + +### 1.2 - 2016-03-05 + +* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib) +* [#23][23], support stdin remapping +* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM. +* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines. +* Supports performs even stdin/stdout is not a tty. +* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api. +* [#28][28], fixes the history is not working as expected. +* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)` + +### 1.1 - 2015-11-20 + +* [#12][12] Add support for key ``/``/`` +* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`. +* Bugs fixed for `PrefixCompleter` +* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience. +* Customable Interrupt/EOF prompt in `Config` +* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices +* Provides a new password user experience(`readline.ReadPasswordEx()`). + +### 1.0 - 2015-10-14 + +* Initial public release. + +[12]: https://github.com/chzyer/readline/pull/12 +[17]: https://github.com/chzyer/readline/pull/17 +[23]: https://github.com/chzyer/readline/pull/23 +[27]: https://github.com/chzyer/readline/pull/27 +[28]: https://github.com/chzyer/readline/pull/28 +[33]: https://github.com/chzyer/readline/pull/33 +[38]: https://github.com/chzyer/readline/pull/38 +[42]: https://github.com/chzyer/readline/pull/42 +[43]: https://github.com/chzyer/readline/pull/43 +[46]: https://github.com/chzyer/readline/pull/46 +[48]: https://github.com/chzyer/readline/pull/48 +[49]: https://github.com/chzyer/readline/pull/49 +[53]: https://github.com/chzyer/readline/pull/53 +[60]: https://github.com/chzyer/readline/pull/60 diff --git a/vendor/github.com/chzyer/readline/LICENSE b/vendor/github.com/chzyer/readline/LICENSE new file mode 100644 index 0000000..c9afab3 --- /dev/null +++ b/vendor/github.com/chzyer/readline/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Chzyer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/chzyer/readline/README.md b/vendor/github.com/chzyer/readline/README.md new file mode 100644 index 0000000..fab974b --- /dev/null +++ b/vendor/github.com/chzyer/readline/README.md @@ -0,0 +1,114 @@ +[![Build Status](https://travis-ci.org/chzyer/readline.svg?branch=master)](https://travis-ci.org/chzyer/readline) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) +[![Version](https://img.shields.io/github/tag/chzyer/readline.svg)](https://github.com/chzyer/readline/releases) +[![GoDoc](https://godoc.org/github.com/chzyer/readline?status.svg)](https://godoc.org/github.com/chzyer/readline) +[![OpenCollective](https://opencollective.com/readline/badge/backers.svg)](#backers) +[![OpenCollective](https://opencollective.com/readline/badge/sponsors.svg)](#sponsors) + +

+ + + +

+ +A powerful readline library in `Linux` `macOS` `Windows` `Solaris` + +## Guide + +* [Demo](example/readline-demo/readline-demo.go) +* [Shortcut](doc/shortcut.md) + +## Repos using readline + +[![cockroachdb](https://img.shields.io/github/stars/cockroachdb/cockroach.svg?label=cockroachdb/cockroach)](https://github.com/cockroachdb/cockroach) +[![robertkrimen/otto](https://img.shields.io/github/stars/robertkrimen/otto.svg?label=robertkrimen/otto)](https://github.com/robertkrimen/otto) +[![empire](https://img.shields.io/github/stars/remind101/empire.svg?label=remind101/empire)](https://github.com/remind101/empire) +[![mehrdadrad/mylg](https://img.shields.io/github/stars/mehrdadrad/mylg.svg?label=mehrdadrad/mylg)](https://github.com/mehrdadrad/mylg) +[![knq/usql](https://img.shields.io/github/stars/knq/usql.svg?label=knq/usql)](https://github.com/knq/usql) +[![youtube/doorman](https://img.shields.io/github/stars/youtube/doorman.svg?label=youtube/doorman)](https://github.com/youtube/doorman) +[![bom-d-van/harp](https://img.shields.io/github/stars/bom-d-van/harp.svg?label=bom-d-van/harp)](https://github.com/bom-d-van/harp) +[![abiosoft/ishell](https://img.shields.io/github/stars/abiosoft/ishell.svg?label=abiosoft/ishell)](https://github.com/abiosoft/ishell) +[![Netflix/hal-9001](https://img.shields.io/github/stars/Netflix/hal-9001.svg?label=Netflix/hal-9001)](https://github.com/Netflix/hal-9001) +[![docker/go-p9p](https://img.shields.io/github/stars/docker/go-p9p.svg?label=docker/go-p9p)](https://github.com/docker/go-p9p) + + +## Feedback + +If you have any questions, please submit a github issue and any pull requests is welcomed :) + +* [https://twitter.com/chzyer](https://twitter.com/chzyer) +* [http://weibo.com/2145262190](http://weibo.com/2145262190) + + +## Backers + +Love Readline? Help me keep it alive by donating funds to cover project expenses!
+[[Become a backer](https://opencollective.com/readline#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Sponsors + +Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/github.com/chzyer/readline/ansi_windows.go b/vendor/github.com/chzyer/readline/ansi_windows.go new file mode 100644 index 0000000..63b908c --- /dev/null +++ b/vendor/github.com/chzyer/readline/ansi_windows.go @@ -0,0 +1,249 @@ +// +build windows + +package readline + +import ( + "bufio" + "io" + "strconv" + "strings" + "sync" + "unicode/utf8" + "unsafe" +) + +const ( + _ = uint16(0) + COLOR_FBLUE = 0x0001 + COLOR_FGREEN = 0x0002 + COLOR_FRED = 0x0004 + COLOR_FINTENSITY = 0x0008 + + COLOR_BBLUE = 0x0010 + COLOR_BGREEN = 0x0020 + COLOR_BRED = 0x0040 + COLOR_BINTENSITY = 0x0080 + + COMMON_LVB_UNDERSCORE = 0x8000 + COMMON_LVB_BOLD = 0x0007 +) + +var ColorTableFg = []word{ + 0, // 30: Black + COLOR_FRED, // 31: Red + COLOR_FGREEN, // 32: Green + COLOR_FRED | COLOR_FGREEN, // 33: Yellow + COLOR_FBLUE, // 34: Blue + COLOR_FRED | COLOR_FBLUE, // 35: Magenta + COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan + COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White +} + +var ColorTableBg = []word{ + 0, // 40: Black + COLOR_BRED, // 41: Red + COLOR_BGREEN, // 42: Green + COLOR_BRED | COLOR_BGREEN, // 43: Yellow + COLOR_BBLUE, // 44: Blue + COLOR_BRED | COLOR_BBLUE, // 45: Magenta + COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan + COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White +} + +type ANSIWriter struct { + target io.Writer + wg sync.WaitGroup + ctx *ANSIWriterCtx + sync.Mutex +} + +func NewANSIWriter(w io.Writer) *ANSIWriter { + a := &ANSIWriter{ + target: w, + ctx: NewANSIWriterCtx(w), + } + return a +} + +func (a *ANSIWriter) Close() error { + a.wg.Wait() + return nil +} + +type ANSIWriterCtx struct { + isEsc bool + isEscSeq bool + arg []string + target *bufio.Writer + wantFlush bool +} + +func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { + return &ANSIWriterCtx{ + target: bufio.NewWriter(target), + } +} + +func (a *ANSIWriterCtx) Flush() { + a.target.Flush() +} + +func (a *ANSIWriterCtx) process(r rune) bool { + if a.wantFlush { + if r == 0 || r == CharEsc { + a.wantFlush = false + a.target.Flush() + } + } + if a.isEscSeq { + a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) + return true + } + + switch r { + case CharEsc: + a.isEsc = true + case '[': + if a.isEsc { + a.arg = nil + a.isEscSeq = true + a.isEsc = false + break + } + fallthrough + default: + a.target.WriteRune(r) + a.wantFlush = true + } + return true +} + +func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { + arg := *argptr + var err error + + if r >= 'A' && r <= 'D' { + count := short(GetInt(arg, 1)) + info, err := GetConsoleScreenBufferInfo() + if err != nil { + return false + } + switch r { + case 'A': // up + info.dwCursorPosition.y -= count + case 'B': // down + info.dwCursorPosition.y += count + case 'C': // right + info.dwCursorPosition.x += count + case 'D': // left + info.dwCursorPosition.x -= count + } + SetConsoleCursorPosition(&info.dwCursorPosition) + return false + } + + switch r { + case 'J': + killLines() + case 'K': + eraseLine() + case 'm': + color := word(0) + for _, item := range arg { + var c int + c, err = strconv.Atoi(item) + if err != nil { + w.WriteString("[" + strings.Join(arg, ";") + "m") + break + } + if c >= 30 && c < 40 { + color ^= COLOR_FINTENSITY + color |= ColorTableFg[c-30] + } else if c >= 40 && c < 50 { + color ^= COLOR_BINTENSITY + color |= ColorTableBg[c-40] + } else if c == 4 { + color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7] + } else if c == 1 { + color |= COMMON_LVB_BOLD | COLOR_FINTENSITY + } else { // unknown code treat as reset + color = ColorTableFg[7] + } + } + if err != nil { + break + } + kernel.SetConsoleTextAttribute(stdout, uintptr(color)) + case '\007': // set title + case ';': + if len(arg) == 0 || arg[len(arg)-1] != "" { + arg = append(arg, "") + *argptr = arg + } + return true + default: + if len(arg) == 0 { + arg = append(arg, "") + } + arg[len(arg)-1] += string(r) + *argptr = arg + return true + } + *argptr = nil + return false +} + +func (a *ANSIWriter) Write(b []byte) (int, error) { + a.Lock() + defer a.Unlock() + + off := 0 + for len(b) > off { + r, size := utf8.DecodeRune(b[off:]) + if size == 0 { + return off, io.ErrShortWrite + } + off += size + a.ctx.process(r) + } + a.ctx.Flush() + return off, nil +} + +func killLines() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x + size += sbi.dwCursorPosition.x + + var written int + kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} + +func eraseLine() error { + sbi, err := GetConsoleScreenBufferInfo() + if err != nil { + return err + } + + size := sbi.dwSize.x + sbi.dwCursorPosition.x = 0 + var written int + return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), + uintptr(size), + sbi.dwCursorPosition.ptr(), + uintptr(unsafe.Pointer(&written)), + ) +} diff --git a/vendor/github.com/chzyer/readline/complete.go b/vendor/github.com/chzyer/readline/complete.go new file mode 100644 index 0000000..c08c994 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete.go @@ -0,0 +1,285 @@ +package readline + +import ( + "bufio" + "bytes" + "fmt" + "io" +) + +type AutoCompleter interface { + // Readline will pass the whole line and current offset to it + // Completer need to pass all the candidates, and how long they shared the same characters in line + // Example: + // [go, git, git-shell, grep] + // Do("g", 1) => ["o", "it", "it-shell", "rep"], 1 + // Do("gi", 2) => ["t", "t-shell"], 2 + // Do("git", 3) => ["", "-shell"], 3 + Do(line []rune, pos int) (newLine [][]rune, length int) +} + +type TabCompleter struct{} + +func (t *TabCompleter) Do([]rune, int) ([][]rune, int) { + return [][]rune{[]rune("\t")}, 0 +} + +type opCompleter struct { + w io.Writer + op *Operation + width int + + inCompleteMode bool + inSelectMode bool + candidate [][]rune + candidateSource []rune + candidateOff int + candidateChoise int + candidateColNum int +} + +func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter { + return &opCompleter{ + w: w, + op: op, + width: width, + } +} + +func (o *opCompleter) doSelect() { + if len(o.candidate) == 1 { + o.op.buf.WriteRunes(o.candidate[0]) + o.ExitCompleteMode(false) + return + } + o.nextCandidate(1) + o.CompleteRefresh() +} + +func (o *opCompleter) nextCandidate(i int) { + o.candidateChoise += i + o.candidateChoise = o.candidateChoise % len(o.candidate) + if o.candidateChoise < 0 { + o.candidateChoise = len(o.candidate) + o.candidateChoise + } +} + +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } + if o.IsInCompleteSelectMode() { + o.doSelect() + return true + } + + buf := o.op.buf + rs := buf.Runes() + + if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { + o.EnterCompleteSelectMode() + o.doSelect() + return true + } + + o.ExitCompleteSelectMode() + o.candidateSource = rs + newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) + if len(newLines) == 0 { + o.ExitCompleteMode(false) + return true + } + + // only Aggregate candidates in non-complete mode + if !o.IsInCompleteMode() { + if len(newLines) == 1 { + buf.WriteRunes(newLines[0]) + o.ExitCompleteMode(false) + return true + } + + same, size := runes.Aggregate(newLines) + if size > 0 { + buf.WriteRunes(same) + o.ExitCompleteMode(false) + return true + } + } + + o.EnterCompleteMode(offset, newLines) + return true +} + +func (o *opCompleter) IsInCompleteSelectMode() bool { + return o.inSelectMode +} + +func (o *opCompleter) IsInCompleteMode() bool { + return o.inCompleteMode +} + +func (o *opCompleter) HandleCompleteSelect(r rune) bool { + next := true + switch r { + case CharEnter, CharCtrlJ: + next = false + o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise]) + o.ExitCompleteMode(false) + case CharLineStart: + num := o.candidateChoise % o.candidateColNum + o.nextCandidate(-num) + case CharLineEnd: + num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1 + o.candidateChoise += num + if o.candidateChoise >= len(o.candidate) { + o.candidateChoise = len(o.candidate) - 1 + } + case CharBackspace: + o.ExitCompleteSelectMode() + next = false + case CharTab, CharForward: + o.doSelect() + case CharBell, CharInterrupt: + o.ExitCompleteMode(true) + next = false + case CharNext: + tmpChoise := o.candidateChoise + o.candidateColNum + if tmpChoise >= o.getMatrixSize() { + tmpChoise -= o.getMatrixSize() + } else if tmpChoise >= len(o.candidate) { + tmpChoise += o.candidateColNum + tmpChoise -= o.getMatrixSize() + } + o.candidateChoise = tmpChoise + case CharBackward: + o.nextCandidate(-1) + case CharPrev: + tmpChoise := o.candidateChoise - o.candidateColNum + if tmpChoise < 0 { + tmpChoise += o.getMatrixSize() + if tmpChoise >= len(o.candidate) { + tmpChoise -= o.candidateColNum + } + } + o.candidateChoise = tmpChoise + default: + next = false + o.ExitCompleteSelectMode() + } + if next { + o.CompleteRefresh() + return true + } + return false +} + +func (o *opCompleter) getMatrixSize() int { + line := len(o.candidate) / o.candidateColNum + if len(o.candidate)%o.candidateColNum != 0 { + line++ + } + return line * o.candidateColNum +} + +func (o *opCompleter) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opCompleter) CompleteRefresh() { + if !o.inCompleteMode { + return + } + lineCnt := o.op.buf.CursorLineCount() + colWidth := 0 + for _, c := range o.candidate { + w := runes.WidthAll(c) + if w > colWidth { + colWidth = w + } + } + colWidth += o.candidateOff + 1 + same := o.op.buf.RuneSlice(-o.candidateOff) + + // -1 to avoid reach the end of line + width := o.width - 1 + colNum := width / colWidth + if colNum != 0 { + colWidth += (width - (colWidth * colNum)) / colNum + } + + o.candidateColNum = colNum + buf := bufio.NewWriter(o.w) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + + colIdx := 0 + lines := 1 + buf.WriteString("\033[J") + for idx, c := range o.candidate { + inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode() + if inSelect { + buf.WriteString("\033[30;47m") + } + buf.WriteString(string(same)) + buf.WriteString(string(c)) + buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same))) + + if inSelect { + buf.WriteString("\033[0m") + } + + colIdx++ + if colIdx == colNum { + buf.WriteString("\n") + lines++ + colIdx = 0 + } + } + + // move back + fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines) + fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen()) + buf.Flush() +} + +func (o *opCompleter) aggCandidate(candidate [][]rune) int { + offset := 0 + for i := 0; i < len(candidate[0]); i++ { + for j := 0; j < len(candidate)-1; j++ { + if i > len(candidate[j]) { + goto aggregate + } + if candidate[j][i] != candidate[j+1][i] { + goto aggregate + } + } + offset = i + } +aggregate: + return offset +} + +func (o *opCompleter) EnterCompleteSelectMode() { + o.inSelectMode = true + o.candidateChoise = -1 + o.CompleteRefresh() +} + +func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) { + o.inCompleteMode = true + o.candidate = candidate + o.candidateOff = offset + o.CompleteRefresh() +} + +func (o *opCompleter) ExitCompleteSelectMode() { + o.inSelectMode = false + o.candidate = nil + o.candidateChoise = -1 + o.candidateOff = -1 + o.candidateSource = nil +} + +func (o *opCompleter) ExitCompleteMode(revent bool) { + o.inCompleteMode = false + o.ExitCompleteSelectMode() +} diff --git a/vendor/github.com/chzyer/readline/complete_helper.go b/vendor/github.com/chzyer/readline/complete_helper.go new file mode 100644 index 0000000..58d7248 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_helper.go @@ -0,0 +1,165 @@ +package readline + +import ( + "bytes" + "strings" +) + +// Caller type for dynamic completion +type DynamicCompleteFunc func(string) []string + +type PrefixCompleterInterface interface { + Print(prefix string, level int, buf *bytes.Buffer) + Do(line []rune, pos int) (newLine [][]rune, length int) + GetName() []rune + GetChildren() []PrefixCompleterInterface + SetChildren(children []PrefixCompleterInterface) +} + +type DynamicPrefixCompleterInterface interface { + PrefixCompleterInterface + IsDynamic() bool + GetDynamicNames(line []rune) [][]rune +} + +type PrefixCompleter struct { + Name []rune + Dynamic bool + Callback DynamicCompleteFunc + Children []PrefixCompleterInterface +} + +func (p *PrefixCompleter) Tree(prefix string) string { + buf := bytes.NewBuffer(nil) + p.Print(prefix, 0, buf) + return buf.String() +} + +func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) { + if strings.TrimSpace(string(p.GetName())) != "" { + buf.WriteString(prefix) + if level > 0 { + buf.WriteString("├") + buf.WriteString(strings.Repeat("─", (level*4)-2)) + buf.WriteString(" ") + } + buf.WriteString(string(p.GetName()) + "\n") + level++ + } + for _, ch := range p.GetChildren() { + ch.Print(prefix, level, buf) + } +} + +func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) { + Print(p, prefix, level, buf) +} + +func (p *PrefixCompleter) IsDynamic() bool { + return p.Dynamic +} + +func (p *PrefixCompleter) GetName() []rune { + return p.Name +} + +func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune { + var names = [][]rune{} + for _, name := range p.Callback(string(line)) { + names = append(names, []rune(name+" ")) + } + return names +} + +func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface { + return p.Children +} + +func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) { + p.Children = children +} + +func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter { + return PcItem("", pc...) +} + +func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter { + name += " " + return &PrefixCompleter{ + Name: []rune(name), + Dynamic: false, + Children: pc, + } +} + +func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter { + return &PrefixCompleter{ + Callback: callback, + Dynamic: true, + Children: pc, + } +} + +func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) { + return doInternal(p, line, pos, line) +} + +func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) { + line = runes.TrimSpaceLeft(line[:pos]) + goNext := false + var lineCompleter PrefixCompleterInterface + for _, child := range p.GetChildren() { + childNames := make([][]rune, 1) + + childDynamic, ok := child.(DynamicPrefixCompleterInterface) + if ok && childDynamic.IsDynamic() { + childNames = childDynamic.GetDynamicNames(origLine) + } else { + childNames[0] = child.GetName() + } + + for _, childName := range childNames { + if len(line) >= len(childName) { + if runes.HasPrefix(line, childName) { + if len(line) == len(childName) { + newLine = append(newLine, []rune{' '}) + } else { + newLine = append(newLine, childName) + } + offset = len(childName) + lineCompleter = child + goNext = true + } + } else { + if runes.HasPrefix(childName, line) { + newLine = append(newLine, childName[len(line):]) + offset = len(line) + lineCompleter = child + } + } + } + } + + if len(newLine) != 1 { + return + } + + tmpLine := make([]rune, 0, len(line)) + for i := offset; i < len(line); i++ { + if line[i] == ' ' { + continue + } + + tmpLine = append(tmpLine, line[i:]...) + return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine) + } + + if goNext { + return doInternal(lineCompleter, nil, 0, origLine) + } + return +} diff --git a/vendor/github.com/chzyer/readline/complete_segment.go b/vendor/github.com/chzyer/readline/complete_segment.go new file mode 100644 index 0000000..5ceadd8 --- /dev/null +++ b/vendor/github.com/chzyer/readline/complete_segment.go @@ -0,0 +1,82 @@ +package readline + +type SegmentCompleter interface { + // a + // |- a1 + // |--- a11 + // |- a2 + // b + // input: + // DoTree([], 0) [a, b] + // DoTree([a], 1) [a] + // DoTree([a, ], 0) [a1, a2] + // DoTree([a, a], 1) [a1, a2] + // DoTree([a, a1], 2) [a1] + // DoTree([a, a1, ], 0) [a11] + // DoTree([a, a1, a], 1) [a11] + DoSegment([][]rune, int) [][]rune +} + +type dumpSegmentCompleter struct { + f func([][]rune, int) [][]rune +} + +func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune { + return d.f(segment, n) +} + +func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter { + return &SegmentComplete{&dumpSegmentCompleter{f}} +} + +func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete { + return &SegmentComplete{ + SegmentCompleter: completer, + } +} + +type SegmentComplete struct { + SegmentCompleter +} + +func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) { + ret := make([][]rune, 0, len(cands)) + lastSegment := segments[len(segments)-1] + for _, cand := range cands { + if !runes.HasPrefix(cand, lastSegment) { + continue + } + ret = append(ret, cand[len(lastSegment):]) + } + return ret, idx +} + +func SplitSegment(line []rune, pos int) ([][]rune, int) { + segs := [][]rune{} + lastIdx := -1 + line = line[:pos] + pos = 0 + for idx, l := range line { + if l == ' ' { + pos = 0 + segs = append(segs, line[lastIdx+1:idx]) + lastIdx = idx + } else { + pos++ + } + } + segs = append(segs, line[lastIdx+1:]) + return segs, pos +} + +func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) { + + segment, idx := SplitSegment(line, pos) + + cands := c.DoSegment(segment, idx) + newLine, offset = RetSegment(segment, cands, idx) + for idx := range newLine { + newLine[idx] = append(newLine[idx], ' ') + } + return newLine, offset +} diff --git a/vendor/github.com/chzyer/readline/history.go b/vendor/github.com/chzyer/readline/history.go new file mode 100644 index 0000000..6b17c46 --- /dev/null +++ b/vendor/github.com/chzyer/readline/history.go @@ -0,0 +1,330 @@ +package readline + +import ( + "bufio" + "container/list" + "fmt" + "os" + "strings" + "sync" +) + +type hisItem struct { + Source []rune + Version int64 + Tmp []rune +} + +func (h *hisItem) Clean() { + h.Source = nil + h.Tmp = nil +} + +type opHistory struct { + cfg *Config + history *list.List + historyVer int64 + current *list.Element + fd *os.File + fdLock sync.Mutex + enable bool +} + +func newOpHistory(cfg *Config) (o *opHistory) { + o = &opHistory{ + cfg: cfg, + history: list.New(), + enable: true, + } + return o +} + +func (o *opHistory) Reset() { + o.history = list.New() + o.current = nil +} + +func (o *opHistory) IsHistoryClosed() bool { + o.fdLock.Lock() + defer o.fdLock.Unlock() + return o.fd.Fd() == ^(uintptr(0)) +} + +func (o *opHistory) Init() { + if o.IsHistoryClosed() { + o.initHistory() + } +} + +func (o *opHistory) initHistory() { + if o.cfg.HistoryFile != "" { + o.historyUpdatePath(o.cfg.HistoryFile) + } +} + +// only called by newOpHistory +func (o *opHistory) historyUpdatePath(path string) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) + if err != nil { + return + } + o.fd = f + r := bufio.NewReader(o.fd) + total := 0 + for ; ; total++ { + line, err := r.ReadString('\n') + if err != nil { + break + } + // ignore the empty line + line = strings.TrimSpace(line) + if len(line) == 0 { + continue + } + o.Push([]rune(line)) + o.Compact() + } + if total > o.cfg.HistoryLimit { + o.rewriteLocked() + } + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Compact() { + for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { + o.history.Remove(o.history.Front()) + } +} + +func (o *opHistory) Rewrite() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + o.rewriteLocked() +} + +func (o *opHistory) rewriteLocked() { + if o.cfg.HistoryFile == "" { + return + } + + tmpFile := o.cfg.HistoryFile + ".tmp" + fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) + if err != nil { + return + } + + buf := bufio.NewWriter(fd) + for elem := o.history.Front(); elem != nil; elem = elem.Next() { + buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") + } + buf.Flush() + + // replace history file + if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { + fd.Close() + return + } + + if o.fd != nil { + o.fd.Close() + } + // fd is write only, just satisfy what we need. + o.fd = fd +} + +func (o *opHistory) Close() { + o.fdLock.Lock() + defer o.fdLock.Unlock() + if o.fd != nil { + o.fd.Close() + } +} + +func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Prev() { + item := o.showItem(elem.Value) + if isNewSearch { + start += len(rs) + } + if elem == o.current { + if len(item) >= start { + item = item[:start] + } + } + idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { + for elem := o.current; elem != nil; elem = elem.Next() { + item := o.showItem(elem.Value) + if isNewSearch { + start -= len(rs) + if start < 0 { + start = 0 + } + } + if elem == o.current { + if len(item)-1 >= start { + item = item[start:] + } else { + continue + } + } + idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) + if idx < 0 { + continue + } + if elem == o.current { + idx += start + } + return idx, elem + } + return -1, nil +} + +func (o *opHistory) showItem(obj interface{}) []rune { + item := obj.(*hisItem) + if item.Version == o.historyVer { + return item.Tmp + } + return item.Source +} + +func (o *opHistory) Prev() []rune { + if o.current == nil { + return nil + } + current := o.current.Prev() + if current == nil { + return nil + } + o.current = current + return runes.Copy(o.showItem(current.Value)) +} + +func (o *opHistory) Next() ([]rune, bool) { + if o.current == nil { + return nil, false + } + current := o.current.Next() + if current == nil { + return nil, false + } + + o.current = current + return runes.Copy(o.showItem(current.Value)), true +} + +// Disable the current history +func (o *opHistory) Disable() { + o.enable = false +} + +// Enable the current history +func (o *opHistory) Enable() { + o.enable = true +} + +func (o *opHistory) debug() { + Debug("-------") + for item := o.history.Front(); item != nil; item = item.Next() { + Debug(fmt.Sprintf("%+v", item.Value)) + } +} + +// save history +func (o *opHistory) New(current []rune) (err error) { + + // history deactivated + if !o.enable { + return nil + } + + current = runes.Copy(current) + + // if just use last command without modify + // just clean lastest history + if back := o.history.Back(); back != nil { + prev := back.Prev() + if prev != nil { + if runes.Equal(current, prev.Value.(*hisItem).Source) { + o.current = o.history.Back() + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + } + + if len(current) == 0 { + o.current = o.history.Back() + if o.current != nil { + o.current.Value.(*hisItem).Clean() + o.historyVer++ + return nil + } + } + + if o.current != o.history.Back() { + // move history item to current command + currentItem := o.current.Value.(*hisItem) + // set current to last item + o.current = o.history.Back() + + current = runes.Copy(currentItem.Tmp) + } + + // err only can be a IO error, just report + err = o.Update(current, true) + + // push a new one to commit current command + o.historyVer++ + o.Push(nil) + return +} + +func (o *opHistory) Revert() { + o.historyVer++ + o.current = o.history.Back() +} + +func (o *opHistory) Update(s []rune, commit bool) (err error) { + o.fdLock.Lock() + defer o.fdLock.Unlock() + s = runes.Copy(s) + if o.current == nil { + o.Push(s) + o.Compact() + return + } + r := o.current.Value.(*hisItem) + r.Version = o.historyVer + if commit { + r.Source = s + if o.fd != nil { + // just report the error + _, err = o.fd.Write([]byte(string(r.Source) + "\n")) + } + } else { + r.Tmp = append(r.Tmp[:0], s...) + } + o.current.Value = r + o.Compact() + return +} + +func (o *opHistory) Push(s []rune) { + s = runes.Copy(s) + elem := o.history.PushBack(&hisItem{Source: s}) + o.current = elem +} diff --git a/vendor/github.com/chzyer/readline/operation.go b/vendor/github.com/chzyer/readline/operation.go new file mode 100644 index 0000000..4c31624 --- /dev/null +++ b/vendor/github.com/chzyer/readline/operation.go @@ -0,0 +1,531 @@ +package readline + +import ( + "errors" + "io" + "sync" +) + +var ( + ErrInterrupt = errors.New("Interrupt") +) + +type InterruptError struct { + Line []rune +} + +func (*InterruptError) Error() string { + return "Interrupted" +} + +type Operation struct { + m sync.Mutex + cfg *Config + t *Terminal + buf *RuneBuffer + outchan chan []rune + errchan chan error + w io.Writer + + history *opHistory + *opSearch + *opCompleter + *opPassword + *opVim +} + +func (o *Operation) SetBuffer(what string) { + o.buf.Set([]rune(what)) +} + +type wrapWriter struct { + r *Operation + t *Terminal + target io.Writer +} + +func (w *wrapWriter) Write(b []byte) (int, error) { + if !w.t.IsReading() { + return w.target.Write(b) + } + + var ( + n int + err error + ) + w.r.buf.Refresh(func() { + n, err = w.target.Write(b) + }) + + if w.r.IsSearchMode() { + w.r.SearchRefresh(-1) + } + if w.r.IsInCompleteMode() { + w.r.CompleteRefresh() + } + return n, err +} + +func NewOperation(t *Terminal, cfg *Config) *Operation { + width := cfg.FuncGetWidth() + op := &Operation{ + t: t, + buf: NewRuneBuffer(t, cfg.Prompt, cfg, width), + outchan: make(chan []rune), + errchan: make(chan error, 1), + } + op.w = op.buf.w + op.SetConfig(cfg) + op.opVim = newVimMode(op) + op.opCompleter = newOpCompleter(op.buf.w, op, width) + op.opPassword = newOpPassword(op) + op.cfg.FuncOnWidthChanged(func() { + newWidth := cfg.FuncGetWidth() + op.opCompleter.OnWidthChange(newWidth) + op.opSearch.OnWidthChange(newWidth) + op.buf.OnWidthChange(newWidth) + }) + go op.ioloop() + return op +} + +func (o *Operation) SetPrompt(s string) { + o.buf.SetPrompt(s) +} + +func (o *Operation) SetMaskRune(r rune) { + o.buf.SetMask(r) +} + +func (o *Operation) GetConfig() *Config { + o.m.Lock() + cfg := *o.cfg + o.m.Unlock() + return &cfg +} + +func (o *Operation) ioloop() { + for { + keepInSearchMode := false + keepInCompleteMode := false + r := o.t.ReadRune() + if o.GetConfig().FuncFilterInputRune != nil { + var process bool + r, process = o.GetConfig().FuncFilterInputRune(r) + if !process { + o.buf.Refresh(nil) // to refresh the line + continue // ignore this rune + } + } + + if r == 0 { // io.EOF + if o.buf.Len() == 0 { + o.buf.Clean() + select { + case o.errchan <- io.EOF: + } + break + } else { + // if stdin got io.EOF and there is something left in buffer, + // let's flush them by sending CharEnter. + // And we will got io.EOF int next loop. + r = CharEnter + } + } + isUpdateHistory := true + + if o.IsInCompleteSelectMode() { + keepInCompleteMode = o.HandleCompleteSelect(r) + if keepInCompleteMode { + continue + } + + o.buf.Refresh(nil) + switch r { + case CharEnter, CharCtrlJ: + o.history.Update(o.buf.Runes(), false) + fallthrough + case CharInterrupt: + o.t.KickRead() + fallthrough + case CharBell: + continue + } + } + + if o.IsEnableVimMode() { + r = o.HandleVim(r, o.t.ReadRune) + if r == 0 { + continue + } + } + + switch r { + case CharBell: + if o.IsSearchMode() { + o.ExitSearchMode(true) + o.buf.Refresh(nil) + } + if o.IsInCompleteMode() { + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + } + case CharTab: + if o.GetConfig().AutoComplete == nil { + o.t.Bell() + break + } + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + + case CharBckSearch: + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharCtrlU: + o.buf.KillFront() + case CharFwdSearch: + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } + keepInSearchMode = true + case CharKill: + o.buf.Kill() + keepInCompleteMode = true + case MetaForward: + o.buf.MoveToNextWord() + case CharTranspose: + o.buf.Transpose() + case MetaBackward: + o.buf.MoveToPrevWord() + case MetaDelete: + o.buf.DeleteWord() + case CharLineStart: + o.buf.MoveToLineStart() + case CharLineEnd: + o.buf.MoveToLineEnd() + case CharBackspace, CharCtrlH: + if o.IsSearchMode() { + o.SearchBackspace() + keepInSearchMode = true + break + } + + if o.buf.Len() == 0 { + o.t.Bell() + break + } + o.buf.Backspace() + if o.IsInCompleteMode() { + o.OnComplete() + } + case CharCtrlZ: + o.buf.Clean() + o.t.SleepToResume() + o.Refresh() + case CharCtrlL: + ClearScreen(o.w) + o.Refresh() + case MetaBackspace, CharCtrlW: + o.buf.BackEscapeWord() + case CharCtrlY: + o.buf.Yank() + case CharEnter, CharCtrlJ: + if o.IsSearchMode() { + o.ExitSearchMode(false) + } + o.buf.MoveToLineEnd() + var data []rune + if !o.GetConfig().UniqueEditLine { + o.buf.WriteRune('\n') + data = o.buf.Reset() + data = data[:len(data)-1] // trim \n + } else { + o.buf.Clean() + data = o.buf.Reset() + } + o.outchan <- data + if !o.GetConfig().DisableAutoSaveHistory { + // ignore IO error + _ = o.history.New(data) + } else { + isUpdateHistory = false + } + case CharBackward: + o.buf.MoveBackward() + case CharForward: + o.buf.MoveForward() + case CharPrev: + buf := o.history.Prev() + if buf != nil { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharNext: + buf, ok := o.history.Next() + if ok { + o.buf.Set(buf) + } else { + o.t.Bell() + } + case CharDelete: + if o.buf.Len() > 0 || !o.IsNormalMode() { + o.t.KickRead() + if !o.buf.Delete() { + o.t.Bell() + } + break + } + + // treat as EOF + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(o.GetConfig().EOFPrompt + "\n") + } + o.buf.Reset() + isUpdateHistory = false + o.history.Revert() + o.errchan <- io.EOF + if o.GetConfig().UniqueEditLine { + o.buf.Clean() + } + case CharInterrupt: + if o.IsSearchMode() { + o.t.KickRead() + o.ExitSearchMode(true) + break + } + if o.IsInCompleteMode() { + o.t.KickRead() + o.ExitCompleteMode(true) + o.buf.Refresh(nil) + break + } + o.buf.MoveToLineEnd() + o.buf.Refresh(nil) + hint := o.GetConfig().InterruptPrompt + "\n" + if !o.GetConfig().UniqueEditLine { + o.buf.WriteString(hint) + } + remain := o.buf.Reset() + if !o.GetConfig().UniqueEditLine { + remain = remain[:len(remain)-len([]rune(hint))] + } + isUpdateHistory = false + o.history.Revert() + o.errchan <- &InterruptError{remain} + default: + if o.IsSearchMode() { + o.SearchChar(r) + keepInSearchMode = true + break + } + o.buf.WriteRune(r) + if o.IsInCompleteMode() { + o.OnComplete() + keepInCompleteMode = true + } + } + + listener := o.GetConfig().Listener + if listener != nil { + newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r) + if ok { + o.buf.SetWithIdx(newPos, newLine) + } + } + + o.m.Lock() + if !keepInSearchMode && o.IsSearchMode() { + o.ExitSearchMode(false) + o.buf.Refresh(nil) + } else if o.IsInCompleteMode() { + if !keepInCompleteMode { + o.ExitCompleteMode(false) + o.Refresh() + } else { + o.buf.Refresh(nil) + o.CompleteRefresh() + } + } + if isUpdateHistory && !o.IsSearchMode() { + // it will cause null history + o.history.Update(o.buf.Runes(), false) + } + o.m.Unlock() + } +} + +func (o *Operation) Stderr() io.Writer { + return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t} +} + +func (o *Operation) Stdout() io.Writer { + return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t} +} + +func (o *Operation) String() (string, error) { + r, err := o.Runes() + return string(r), err +} + +func (o *Operation) Runes() ([]rune, error) { + o.t.EnterRawMode() + defer o.t.ExitRawMode() + + listener := o.GetConfig().Listener + if listener != nil { + listener.OnChange(nil, 0, 0) + } + + o.buf.Refresh(nil) // print prompt + o.t.KickRead() + select { + case r := <-o.outchan: + return r, nil + case err := <-o.errchan: + if e, ok := err.(*InterruptError); ok { + return e.Line, ErrInterrupt + } + return nil, err + } +} + +func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) { + cfg := o.GenPasswordConfig() + cfg.Prompt = prompt + cfg.Listener = l + return o.PasswordWithConfig(cfg) +} + +func (o *Operation) GenPasswordConfig() *Config { + return o.opPassword.PasswordConfig() +} + +func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) { + if err := o.opPassword.EnterPasswordMode(cfg); err != nil { + return nil, err + } + defer o.opPassword.ExitPasswordMode() + return o.Slice() +} + +func (o *Operation) Password(prompt string) ([]byte, error) { + return o.PasswordEx(prompt, nil) +} + +func (o *Operation) SetTitle(t string) { + o.w.Write([]byte("\033[2;" + t + "\007")) +} + +func (o *Operation) Slice() ([]byte, error) { + r, err := o.Runes() + if err != nil { + return nil, err + } + return []byte(string(r)), nil +} + +func (o *Operation) Close() { + o.history.Close() +} + +func (o *Operation) SetHistoryPath(path string) { + if o.history != nil { + o.history.Close() + } + o.cfg.HistoryFile = path + o.history = newOpHistory(o.cfg) +} + +func (o *Operation) IsNormalMode() bool { + return !o.IsInCompleteMode() && !o.IsSearchMode() +} + +func (op *Operation) SetConfig(cfg *Config) (*Config, error) { + op.m.Lock() + defer op.m.Unlock() + if op.cfg == cfg { + return op.cfg, nil + } + if err := cfg.Init(); err != nil { + return op.cfg, err + } + old := op.cfg + op.cfg = cfg + op.SetPrompt(cfg.Prompt) + op.SetMaskRune(cfg.MaskRune) + op.buf.SetConfig(cfg) + width := op.cfg.FuncGetWidth() + + if cfg.opHistory == nil { + op.SetHistoryPath(cfg.HistoryFile) + cfg.opHistory = op.history + cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width) + } + op.history = cfg.opHistory + + // SetHistoryPath will close opHistory which already exists + // so if we use it next time, we need to reopen it by `InitHistory()` + op.history.Init() + + if op.cfg.AutoComplete != nil { + op.opCompleter = newOpCompleter(op.buf.w, op, width) + } + + op.opSearch = cfg.opSearch + return old, nil +} + +func (o *Operation) ResetHistory() { + o.history.Reset() +} + +// if err is not nil, it just mean it fail to write to file +// other things goes fine. +func (o *Operation) SaveHistory(content string) error { + return o.history.New([]rune(content)) +} + +func (o *Operation) Refresh() { + if o.t.IsReading() { + o.buf.Refresh(nil) + } +} + +func (o *Operation) Clean() { + o.buf.Clean() +} + +func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener { + return &DumpListener{f: f} +} + +type DumpListener struct { + f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { + return d.f(line, pos, key) +} + +type Listener interface { + OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) +} + +type Painter interface { + Paint(line []rune, pos int) []rune +} + +type defaultPainter struct{} + +func (p *defaultPainter) Paint(line []rune, _ int) []rune { + return line +} diff --git a/vendor/github.com/chzyer/readline/password.go b/vendor/github.com/chzyer/readline/password.go new file mode 100644 index 0000000..414288c --- /dev/null +++ b/vendor/github.com/chzyer/readline/password.go @@ -0,0 +1,33 @@ +package readline + +type opPassword struct { + o *Operation + backupCfg *Config +} + +func newOpPassword(o *Operation) *opPassword { + return &opPassword{o: o} +} + +func (o *opPassword) ExitPasswordMode() { + o.o.SetConfig(o.backupCfg) + o.backupCfg = nil +} + +func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) { + o.backupCfg, err = o.o.SetConfig(cfg) + return +} + +func (o *opPassword) PasswordConfig() *Config { + return &Config{ + EnableMask: true, + InterruptPrompt: "\n", + EOFPrompt: "\n", + HistoryLimit: -1, + Painter: &defaultPainter{}, + + Stdout: o.o.cfg.Stdout, + Stderr: o.o.cfg.Stderr, + } +} diff --git a/vendor/github.com/chzyer/readline/rawreader_windows.go b/vendor/github.com/chzyer/readline/rawreader_windows.go new file mode 100644 index 0000000..073ef15 --- /dev/null +++ b/vendor/github.com/chzyer/readline/rawreader_windows.go @@ -0,0 +1,125 @@ +// +build windows + +package readline + +import "unsafe" + +const ( + VK_CANCEL = 0x03 + VK_BACK = 0x08 + VK_TAB = 0x09 + VK_RETURN = 0x0D + VK_SHIFT = 0x10 + VK_CONTROL = 0x11 + VK_MENU = 0x12 + VK_ESCAPE = 0x1B + VK_LEFT = 0x25 + VK_UP = 0x26 + VK_RIGHT = 0x27 + VK_DOWN = 0x28 + VK_DELETE = 0x2E + VK_LSHIFT = 0xA0 + VK_RSHIFT = 0xA1 + VK_LCONTROL = 0xA2 + VK_RCONTROL = 0xA3 +) + +// RawReader translate input record to ANSI escape sequence. +// To provides same behavior as unix terminal. +type RawReader struct { + ctrlKey bool + altKey bool +} + +func NewRawReader() *RawReader { + r := new(RawReader) + return r +} + +// only process one action in one read +func (r *RawReader) Read(buf []byte) (int, error) { + ir := new(_INPUT_RECORD) + var read int + var err error +next: + err = kernel.ReadConsoleInputW(stdin, + uintptr(unsafe.Pointer(ir)), + 1, + uintptr(unsafe.Pointer(&read)), + ) + if err != nil { + return 0, err + } + if ir.EventType != EVENT_KEY { + goto next + } + ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0])) + if ker.bKeyDown == 0 { // keyup + if r.ctrlKey || r.altKey { + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = false + case VK_MENU: //alt + r.altKey = false + } + } + goto next + } + + if ker.unicodeChar == 0 { + var target rune + switch ker.wVirtualKeyCode { + case VK_RCONTROL, VK_LCONTROL: + r.ctrlKey = true + case VK_MENU: //alt + r.altKey = true + case VK_LEFT: + target = CharBackward + case VK_RIGHT: + target = CharForward + case VK_UP: + target = CharPrev + case VK_DOWN: + target = CharNext + } + if target != 0 { + return r.write(buf, target) + } + goto next + } + char := rune(ker.unicodeChar) + if r.ctrlKey { + switch char { + case 'A': + char = CharLineStart + case 'E': + char = CharLineEnd + case 'R': + char = CharBckSearch + case 'S': + char = CharFwdSearch + } + } else if r.altKey { + switch char { + case VK_BACK: + char = CharBackspace + } + return r.writeEsc(buf, char) + } + return r.write(buf, char) +} + +func (r *RawReader) writeEsc(b []byte, char rune) (int, error) { + b[0] = '\033' + n := copy(b[1:], []byte(string(char))) + return n + 1, nil +} + +func (r *RawReader) write(b []byte, char rune) (int, error) { + n := copy(b, []byte(string(char))) + return n, nil +} + +func (r *RawReader) Close() error { + return nil +} diff --git a/vendor/github.com/chzyer/readline/readline.go b/vendor/github.com/chzyer/readline/readline.go new file mode 100644 index 0000000..0e7aca0 --- /dev/null +++ b/vendor/github.com/chzyer/readline/readline.go @@ -0,0 +1,326 @@ +// Readline is a pure go implementation for GNU-Readline kind library. +// +// example: +// rl, err := readline.New("> ") +// if err != nil { +// panic(err) +// } +// defer rl.Close() +// +// for { +// line, err := rl.Readline() +// if err != nil { // io.EOF +// break +// } +// println(line) +// } +// +package readline + +import "io" + +type Instance struct { + Config *Config + Terminal *Terminal + Operation *Operation +} + +type Config struct { + // prompt supports ANSI escape sequence, so we can color some characters even in windows + Prompt string + + // readline will persist historys to file where HistoryFile specified + HistoryFile string + // specify the max length of historys, it's 500 by default, set it to -1 to disable history + HistoryLimit int + DisableAutoSaveHistory bool + // enable case-insensitive history searching + HistorySearchFold bool + + // AutoCompleter will called once user press TAB + AutoComplete AutoCompleter + + // Any key press will pass to Listener + // NOTE: Listener will be triggered by (nil, 0, 0) immediately + Listener Listener + + Painter Painter + + // If VimMode is true, readline will in vim.insert mode by default + VimMode bool + + InterruptPrompt string + EOFPrompt string + + FuncGetWidth func() int + + Stdin io.ReadCloser + StdinWriter io.Writer + Stdout io.Writer + Stderr io.Writer + + EnableMask bool + MaskRune rune + + // erase the editing line after user submited it + // it use in IM usually. + UniqueEditLine bool + + // filter input runes (may be used to disable CtrlZ or for translating some keys to different actions) + // -> output = new (translated) rune and true/false if continue with processing this one + FuncFilterInputRune func(rune) (rune, bool) + + // force use interactive even stdout is not a tty + FuncIsTerminal func() bool + FuncMakeRaw func() error + FuncExitRaw func() error + FuncOnWidthChanged func(func()) + ForceUseInteractive bool + + // private fields + inited bool + opHistory *opHistory + opSearch *opSearch +} + +func (c *Config) useInteractive() bool { + if c.ForceUseInteractive { + return true + } + return c.FuncIsTerminal() +} + +func (c *Config) Init() error { + if c.inited { + return nil + } + c.inited = true + if c.Stdin == nil { + c.Stdin = NewCancelableStdin(Stdin) + } + + c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin) + + if c.Stdout == nil { + c.Stdout = Stdout + } + if c.Stderr == nil { + c.Stderr = Stderr + } + if c.HistoryLimit == 0 { + c.HistoryLimit = 500 + } + + if c.InterruptPrompt == "" { + c.InterruptPrompt = "^C" + } else if c.InterruptPrompt == "\n" { + c.InterruptPrompt = "" + } + if c.EOFPrompt == "" { + c.EOFPrompt = "^D" + } else if c.EOFPrompt == "\n" { + c.EOFPrompt = "" + } + + if c.AutoComplete == nil { + c.AutoComplete = &TabCompleter{} + } + if c.FuncGetWidth == nil { + c.FuncGetWidth = GetScreenWidth + } + if c.FuncIsTerminal == nil { + c.FuncIsTerminal = DefaultIsTerminal + } + rm := new(RawMode) + if c.FuncMakeRaw == nil { + c.FuncMakeRaw = rm.Enter + } + if c.FuncExitRaw == nil { + c.FuncExitRaw = rm.Exit + } + if c.FuncOnWidthChanged == nil { + c.FuncOnWidthChanged = DefaultOnWidthChanged + } + + return nil +} + +func (c Config) Clone() *Config { + c.opHistory = nil + c.opSearch = nil + return &c +} + +func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) { + c.Listener = FuncListener(f) +} + +func (c *Config) SetPainter(p Painter) { + c.Painter = p +} + +func NewEx(cfg *Config) (*Instance, error) { + t, err := NewTerminal(cfg) + if err != nil { + return nil, err + } + rl := t.Readline() + if cfg.Painter == nil { + cfg.Painter = &defaultPainter{} + } + return &Instance{ + Config: cfg, + Terminal: t, + Operation: rl, + }, nil +} + +func New(prompt string) (*Instance, error) { + return NewEx(&Config{Prompt: prompt}) +} + +func (i *Instance) ResetHistory() { + i.Operation.ResetHistory() +} + +func (i *Instance) SetPrompt(s string) { + i.Operation.SetPrompt(s) +} + +func (i *Instance) SetMaskRune(r rune) { + i.Operation.SetMaskRune(r) +} + +// change history persistence in runtime +func (i *Instance) SetHistoryPath(p string) { + i.Operation.SetHistoryPath(p) +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stdout() io.Writer { + return i.Operation.Stdout() +} + +// readline will refresh automatic when write through Stdout() +func (i *Instance) Stderr() io.Writer { + return i.Operation.Stderr() +} + +// switch VimMode in runtime +func (i *Instance) SetVimMode(on bool) { + i.Operation.SetVimMode(on) +} + +func (i *Instance) IsVimMode() bool { + return i.Operation.IsEnableVimMode() +} + +func (i *Instance) GenPasswordConfig() *Config { + return i.Operation.GenPasswordConfig() +} + +// we can generate a config by `i.GenPasswordConfig()` +func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) { + return i.Operation.PasswordWithConfig(cfg) +} + +func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) { + return i.Operation.PasswordEx(prompt, l) +} + +func (i *Instance) ReadPassword(prompt string) ([]byte, error) { + return i.Operation.Password(prompt) +} + +type Result struct { + Line string + Error error +} + +func (l *Result) CanContinue() bool { + return len(l.Line) != 0 && l.Error == ErrInterrupt +} + +func (l *Result) CanBreak() bool { + return !l.CanContinue() && l.Error != nil +} + +func (i *Instance) Line() *Result { + ret, err := i.Readline() + return &Result{ret, err} +} + +// err is one of (nil, io.EOF, readline.ErrInterrupt) +func (i *Instance) Readline() (string, error) { + return i.Operation.String() +} + +func (i *Instance) ReadlineWithDefault(what string) (string, error) { + i.Operation.SetBuffer(what) + return i.Operation.String() +} + +func (i *Instance) SaveHistory(content string) error { + return i.Operation.SaveHistory(content) +} + +// same as readline +func (i *Instance) ReadSlice() ([]byte, error) { + return i.Operation.Slice() +} + +// we must make sure that call Close() before process exit. +func (i *Instance) Close() error { + if err := i.Terminal.Close(); err != nil { + return err + } + i.Config.Stdin.Close() + i.Operation.Close() + return nil +} +func (i *Instance) Clean() { + i.Operation.Clean() +} + +func (i *Instance) Write(b []byte) (int, error) { + return i.Stdout().Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +// ie : +// i := readline.New() +// i.WriteStdin([]byte("test")) +// _, _= i.Readline() +// +// gives +// +// > test[cursor] +func (i *Instance) WriteStdin(val []byte) (int, error) { + return i.Terminal.WriteStdin(val) +} + +func (i *Instance) SetConfig(cfg *Config) *Config { + if i.Config == cfg { + return cfg + } + old := i.Config + i.Config = cfg + i.Operation.SetConfig(cfg) + i.Terminal.SetConfig(cfg) + return old +} + +func (i *Instance) Refresh() { + i.Operation.Refresh() +} + +// HistoryDisable the save of the commands into the history +func (i *Instance) HistoryDisable() { + i.Operation.history.Disable() +} + +// HistoryEnable the save of the commands into the history (default on) +func (i *Instance) HistoryEnable() { + i.Operation.history.Enable() +} diff --git a/vendor/github.com/chzyer/readline/remote.go b/vendor/github.com/chzyer/readline/remote.go new file mode 100644 index 0000000..74dbf56 --- /dev/null +++ b/vendor/github.com/chzyer/readline/remote.go @@ -0,0 +1,475 @@ +package readline + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "os" + "sync" + "sync/atomic" +) + +type MsgType int16 + +const ( + T_DATA = MsgType(iota) + T_WIDTH + T_WIDTH_REPORT + T_ISTTY_REPORT + T_RAW + T_ERAW // exit raw + T_EOF +) + +type RemoteSvr struct { + eof int32 + closed int32 + width int32 + reciveChan chan struct{} + writeChan chan *writeCtx + conn net.Conn + isTerminal bool + funcWidthChan func() + stopChan chan struct{} + + dataBufM sync.Mutex + dataBuf bytes.Buffer +} + +type writeReply struct { + n int + err error +} + +type writeCtx struct { + msg *Message + reply chan *writeReply +} + +func newWriteCtx(msg *Message) *writeCtx { + return &writeCtx{ + msg: msg, + reply: make(chan *writeReply), + } +} + +func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) { + rs := &RemoteSvr{ + width: -1, + conn: conn, + writeChan: make(chan *writeCtx), + reciveChan: make(chan struct{}), + stopChan: make(chan struct{}), + } + buf := bufio.NewReader(rs.conn) + + if err := rs.init(buf); err != nil { + return nil, err + } + + go rs.readLoop(buf) + go rs.writeLoop() + return rs, nil +} + +func (r *RemoteSvr) init(buf *bufio.Reader) error { + m, err := ReadMessage(buf) + if err != nil { + return err + } + // receive isTerminal + if m.Type != T_ISTTY_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotIsTerminal(m.Data) + + // receive width + m, err = ReadMessage(buf) + if err != nil { + return err + } + if m.Type != T_WIDTH_REPORT { + return fmt.Errorf("unexpected init message") + } + r.GotReportWidth(m.Data) + + return nil +} + +func (r *RemoteSvr) HandleConfig(cfg *Config) { + cfg.Stderr = r + cfg.Stdout = r + cfg.Stdin = r + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncIsTerminal = r.IsTerminal + cfg.FuncMakeRaw = r.EnterRawMode + cfg.FuncExitRaw = r.ExitRawMode + cfg.FuncGetWidth = r.GetWidth + cfg.FuncOnWidthChanged = func(f func()) { + r.funcWidthChan = f + } +} + +func (r *RemoteSvr) IsTerminal() bool { + return r.isTerminal +} + +func (r *RemoteSvr) checkEOF() error { + if atomic.LoadInt32(&r.eof) == 1 { + return io.EOF + } + return nil +} + +func (r *RemoteSvr) Read(b []byte) (int, error) { + r.dataBufM.Lock() + n, err := r.dataBuf.Read(b) + r.dataBufM.Unlock() + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + if n == 0 && err == io.EOF { + <-r.reciveChan + r.dataBufM.Lock() + n, err = r.dataBuf.Read(b) + r.dataBufM.Unlock() + } + if n == 0 { + if err := r.checkEOF(); err != nil { + return 0, err + } + } + + return n, err +} + +func (r *RemoteSvr) writeMsg(m *Message) error { + ctx := newWriteCtx(m) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.err +} + +func (r *RemoteSvr) Write(b []byte) (int, error) { + ctx := newWriteCtx(NewMessage(T_DATA, b)) + r.writeChan <- ctx + reply := <-ctx.reply + return reply.n, reply.err +} + +func (r *RemoteSvr) EnterRawMode() error { + return r.writeMsg(NewMessage(T_RAW, nil)) +} + +func (r *RemoteSvr) ExitRawMode() error { + return r.writeMsg(NewMessage(T_ERAW, nil)) +} + +func (r *RemoteSvr) writeLoop() { + defer r.Close() + +loop: + for { + select { + case ctx, ok := <-r.writeChan: + if !ok { + break + } + n, err := ctx.msg.WriteTo(r.conn) + ctx.reply <- &writeReply{n, err} + case <-r.stopChan: + break loop + } + } +} + +func (r *RemoteSvr) Close() error { + if atomic.CompareAndSwapInt32(&r.closed, 0, 1) { + close(r.stopChan) + r.conn.Close() + } + return nil +} + +func (r *RemoteSvr) readLoop(buf *bufio.Reader) { + defer r.Close() + for { + m, err := ReadMessage(buf) + if err != nil { + break + } + switch m.Type { + case T_EOF: + atomic.StoreInt32(&r.eof, 1) + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_DATA: + r.dataBufM.Lock() + r.dataBuf.Write(m.Data) + r.dataBufM.Unlock() + select { + case r.reciveChan <- struct{}{}: + default: + } + case T_WIDTH_REPORT: + r.GotReportWidth(m.Data) + case T_ISTTY_REPORT: + r.GotIsTerminal(m.Data) + } + } +} + +func (r *RemoteSvr) GotIsTerminal(data []byte) { + if binary.BigEndian.Uint16(data) == 0 { + r.isTerminal = false + } else { + r.isTerminal = true + } +} + +func (r *RemoteSvr) GotReportWidth(data []byte) { + atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data))) + if r.funcWidthChan != nil { + r.funcWidthChan() + } +} + +func (r *RemoteSvr) GetWidth() int { + return int(atomic.LoadInt32(&r.width)) +} + +// ----------------------------------------------------------------------------- + +type Message struct { + Type MsgType + Data []byte +} + +func ReadMessage(r io.Reader) (*Message, error) { + m := new(Message) + var length int32 + if err := binary.Read(r, binary.BigEndian, &length); err != nil { + return nil, err + } + if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil { + return nil, err + } + m.Data = make([]byte, int(length)-2) + if _, err := io.ReadFull(r, m.Data); err != nil { + return nil, err + } + return m, nil +} + +func NewMessage(t MsgType, data []byte) *Message { + return &Message{t, data} +} + +func (m *Message) WriteTo(w io.Writer) (int, error) { + buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4)) + binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2)) + binary.Write(buf, binary.BigEndian, m.Type) + buf.Write(m.Data) + n, err := buf.WriteTo(w) + return int(n), err +} + +// ----------------------------------------------------------------------------- + +type RemoteCli struct { + conn net.Conn + raw RawMode + receiveChan chan struct{} + inited int32 + isTerminal *bool + + data bytes.Buffer + dataM sync.Mutex +} + +func NewRemoteCli(conn net.Conn) (*RemoteCli, error) { + r := &RemoteCli{ + conn: conn, + receiveChan: make(chan struct{}), + } + return r, nil +} + +func (r *RemoteCli) MarkIsTerminal(is bool) { + r.isTerminal = &is +} + +func (r *RemoteCli) init() error { + if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) { + return nil + } + + if err := r.reportIsTerminal(); err != nil { + return err + } + + if err := r.reportWidth(); err != nil { + return err + } + + // register sig for width changed + DefaultOnWidthChanged(func() { + r.reportWidth() + }) + return nil +} + +func (r *RemoteCli) writeMsg(m *Message) error { + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return err +} + +func (r *RemoteCli) Write(b []byte) (int, error) { + m := NewMessage(T_DATA, b) + r.dataM.Lock() + _, err := m.WriteTo(r.conn) + r.dataM.Unlock() + return len(b), err +} + +func (r *RemoteCli) reportWidth() error { + screenWidth := GetScreenWidth() + data := make([]byte, 2) + binary.BigEndian.PutUint16(data, uint16(screenWidth)) + msg := NewMessage(T_WIDTH_REPORT, data) + + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) reportIsTerminal() error { + var isTerminal bool + if r.isTerminal != nil { + isTerminal = *r.isTerminal + } else { + isTerminal = DefaultIsTerminal() + } + data := make([]byte, 2) + if isTerminal { + binary.BigEndian.PutUint16(data, 1) + } else { + binary.BigEndian.PutUint16(data, 0) + } + msg := NewMessage(T_ISTTY_REPORT, data) + if err := r.writeMsg(msg); err != nil { + return err + } + return nil +} + +func (r *RemoteCli) readLoop() { + buf := bufio.NewReader(r.conn) + for { + msg, err := ReadMessage(buf) + if err != nil { + break + } + switch msg.Type { + case T_ERAW: + r.raw.Exit() + case T_RAW: + r.raw.Enter() + case T_DATA: + os.Stdout.Write(msg.Data) + } + } +} + +func (r *RemoteCli) ServeBy(source io.Reader) error { + if err := r.init(); err != nil { + return err + } + + go func() { + defer r.Close() + for { + n, _ := io.Copy(r, source) + if n == 0 { + break + } + } + }() + defer r.raw.Exit() + r.readLoop() + return nil +} + +func (r *RemoteCli) Close() { + r.writeMsg(NewMessage(T_EOF, nil)) +} + +func (r *RemoteCli) Serve() error { + return r.ServeBy(os.Stdin) +} + +func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error { + ln, err := net.Listen(n, addr) + if err != nil { + return err + } + if len(onListen) > 0 { + if err := onListen[0](ln); err != nil { + return err + } + } + for { + conn, err := ln.Accept() + if err != nil { + break + } + go func() { + defer conn.Close() + rl, err := HandleConn(*cfg, conn) + if err != nil { + return + } + h(rl) + }() + } + return nil +} + +func HandleConn(cfg Config, conn net.Conn) (*Instance, error) { + r, err := NewRemoteSvr(conn) + if err != nil { + return nil, err + } + r.HandleConfig(&cfg) + + rl, err := NewEx(&cfg) + if err != nil { + return nil, err + } + return rl, nil +} + +func DialRemote(n, addr string) error { + conn, err := net.Dial(n, addr) + if err != nil { + return err + } + defer conn.Close() + + cli, err := NewRemoteCli(conn) + if err != nil { + return err + } + return cli.Serve() +} diff --git a/vendor/github.com/chzyer/readline/runebuf.go b/vendor/github.com/chzyer/readline/runebuf.go new file mode 100644 index 0000000..81d2da5 --- /dev/null +++ b/vendor/github.com/chzyer/readline/runebuf.go @@ -0,0 +1,629 @@ +package readline + +import ( + "bufio" + "bytes" + "io" + "strconv" + "strings" + "sync" +) + +type runeBufferBck struct { + buf []rune + idx int +} + +type RuneBuffer struct { + buf []rune + idx int + prompt []rune + w io.Writer + + hadClean bool + interactive bool + cfg *Config + + width int + + bck *runeBufferBck + + offset string + + lastKill []rune + + sync.Mutex +} + +func (r* RuneBuffer) pushKill(text []rune) { + r.lastKill = append([]rune{}, text...) +} + +func (r *RuneBuffer) OnWidthChange(newWidth int) { + r.Lock() + r.width = newWidth + r.Unlock() +} + +func (r *RuneBuffer) Backup() { + r.Lock() + r.bck = &runeBufferBck{r.buf, r.idx} + r.Unlock() +} + +func (r *RuneBuffer) Restore() { + r.Refresh(func() { + if r.bck == nil { + return + } + r.buf = r.bck.buf + r.idx = r.bck.idx + }) +} + +func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer { + rb := &RuneBuffer{ + w: w, + interactive: cfg.useInteractive(), + cfg: cfg, + width: width, + } + rb.SetPrompt(prompt) + return rb +} + +func (r *RuneBuffer) SetConfig(cfg *Config) { + r.Lock() + r.cfg = cfg + r.interactive = cfg.useInteractive() + r.Unlock() +} + +func (r *RuneBuffer) SetMask(m rune) { + r.Lock() + r.cfg.MaskRune = m + r.Unlock() +} + +func (r *RuneBuffer) CurrentWidth(x int) int { + r.Lock() + defer r.Unlock() + return runes.WidthAll(r.buf[:x]) +} + +func (r *RuneBuffer) PromptLen() int { + r.Lock() + width := r.promptLen() + r.Unlock() + return width +} + +func (r *RuneBuffer) promptLen() int { + return runes.WidthAll(runes.ColorFilter(r.prompt)) +} + +func (r *RuneBuffer) RuneSlice(i int) []rune { + r.Lock() + defer r.Unlock() + + if i > 0 { + rs := make([]rune, i) + copy(rs, r.buf[r.idx:r.idx+i]) + return rs + } + rs := make([]rune, -i) + copy(rs, r.buf[r.idx+i:r.idx]) + return rs +} + +func (r *RuneBuffer) Runes() []rune { + r.Lock() + newr := make([]rune, len(r.buf)) + copy(newr, r.buf) + r.Unlock() + return newr +} + +func (r *RuneBuffer) Pos() int { + r.Lock() + defer r.Unlock() + return r.idx +} + +func (r *RuneBuffer) Len() int { + r.Lock() + defer r.Unlock() + return len(r.buf) +} + +func (r *RuneBuffer) MoveToLineStart() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx = 0 + }) +} + +func (r *RuneBuffer) MoveBackward() { + r.Refresh(func() { + if r.idx == 0 { + return + } + r.idx-- + }) +} + +func (r *RuneBuffer) WriteString(s string) { + r.WriteRunes([]rune(s)) +} + +func (r *RuneBuffer) WriteRune(s rune) { + r.WriteRunes([]rune{s}) +} + +func (r *RuneBuffer) WriteRunes(s []rune) { + r.Refresh(func() { + tail := append(s, r.buf[r.idx:]...) + r.buf = append(r.buf[:r.idx], tail...) + r.idx += len(s) + }) +} + +func (r *RuneBuffer) MoveForward() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.idx++ + }) +} + +func (r *RuneBuffer) IsCursorInEnd() bool { + r.Lock() + defer r.Unlock() + return r.idx == len(r.buf) +} + +func (r *RuneBuffer) Replace(ch rune) { + r.Refresh(func() { + r.buf[r.idx] = ch + }) +} + +func (r *RuneBuffer) Erase() { + r.Refresh(func() { + r.idx = 0 + r.pushKill(r.buf[:]) + r.buf = r.buf[:0] + }) +} + +func (r *RuneBuffer) Delete() (success bool) { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + r.pushKill(r.buf[r.idx : r.idx+1]) + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + success = true + }) + return +} + +func (r *RuneBuffer) DeleteWord() { + if r.idx == len(r.buf) { + return + } + init := r.idx + for init < len(r.buf) && IsWordBreak(r.buf[init]) { + init++ + } + for i := init + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[r.idx:i-1]) + r.Refresh(func() { + r.buf = append(r.buf[:r.idx], r.buf[i-1:]...) + }) + return + } + } + r.Kill() +} + +func (r *RuneBuffer) MoveToPrevWord() (success bool) { + r.Refresh(func() { + if r.idx == 0 { + return + } + + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + success = true + return + } + } + r.idx = 0 + success = true + }) + return +} + +func (r *RuneBuffer) KillFront() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + length := len(r.buf) - r.idx + r.pushKill(r.buf[:r.idx]) + copy(r.buf[:length], r.buf[r.idx:]) + r.idx = 0 + r.buf = r.buf[:length] + }) +} + +func (r *RuneBuffer) Kill() { + r.Refresh(func() { + r.pushKill(r.buf[r.idx:]) + r.buf = r.buf[:r.idx] + }) +} + +func (r *RuneBuffer) Transpose() { + r.Refresh(func() { + if len(r.buf) == 1 { + r.idx++ + } + + if len(r.buf) < 2 { + return + } + + if r.idx == 0 { + r.idx = 1 + } else if r.idx >= len(r.buf) { + r.idx = len(r.buf) - 1 + } + r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx] + r.idx++ + }) +} + +func (r *RuneBuffer) MoveToNextWord() { + r.Refresh(func() { + for i := r.idx + 1; i < len(r.buf); i++ { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.idx = i + return + } + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) MoveToEndWord() { + r.Refresh(func() { + // already at the end, so do nothing + if r.idx == len(r.buf) { + return + } + // if we are at the end of a word already, go to next + if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) { + r.idx++ + } + + // keep going until at the end of a word + for i := r.idx + 1; i < len(r.buf); i++ { + if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) { + r.idx = i - 1 + return + } + } + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) BackEscapeWord() { + r.Refresh(func() { + if r.idx == 0 { + return + } + for i := r.idx - 1; i > 0; i-- { + if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) { + r.pushKill(r.buf[i:r.idx]) + r.buf = append(r.buf[:i], r.buf[r.idx:]...) + r.idx = i + return + } + } + + r.buf = r.buf[:0] + r.idx = 0 + }) +} + +func (r *RuneBuffer) Yank() { + if len(r.lastKill) == 0 { + return + } + r.Refresh(func() { + buf := make([]rune, 0, len(r.buf) + len(r.lastKill)) + buf = append(buf, r.buf[:r.idx]...) + buf = append(buf, r.lastKill...) + buf = append(buf, r.buf[r.idx:]...) + r.buf = buf + r.idx += len(r.lastKill) + }) +} + +func (r *RuneBuffer) Backspace() { + r.Refresh(func() { + if r.idx == 0 { + return + } + + r.idx-- + r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...) + }) +} + +func (r *RuneBuffer) MoveToLineEnd() { + r.Refresh(func() { + if r.idx == len(r.buf) { + return + } + + r.idx = len(r.buf) + }) +} + +func (r *RuneBuffer) LineCount(width int) int { + if width == -1 { + width = r.width + } + return LineCount(width, + runes.WidthAll(r.buf)+r.PromptLen()) +} + +func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) { + r.Refresh(func() { + if reverse { + for i := r.idx - 1; i >= 0; i-- { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx++ + } + success = true + return + } + } + return + } + for i := r.idx + 1; i < len(r.buf); i++ { + if r.buf[i] == ch { + r.idx = i + if prevChar { + r.idx-- + } + success = true + return + } + } + }) + return +} + +func (r *RuneBuffer) isInLineEdge() bool { + if isWindows { + return false + } + sp := r.getSplitByLine(r.buf) + return len(sp[len(sp)-1]) == 0 +} + +func (r *RuneBuffer) getSplitByLine(rs []rune) []string { + return SplitByLine(r.promptLen(), r.width, rs) +} + +func (r *RuneBuffer) IdxLine(width int) int { + r.Lock() + defer r.Unlock() + return r.idxLine(width) +} + +func (r *RuneBuffer) idxLine(width int) int { + if width == 0 { + return 0 + } + sp := r.getSplitByLine(r.buf[:r.idx]) + return len(sp) - 1 +} + +func (r *RuneBuffer) CursorLineCount() int { + return r.LineCount(r.width) - r.IdxLine(r.width) +} + +func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + + if !r.interactive { + if f != nil { + f() + } + return + } + + r.clean() + if f != nil { + f() + } + r.print() +} + +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + +func (r *RuneBuffer) print() { + r.w.Write(r.output()) + r.hadClean = false +} + +func (r *RuneBuffer) output() []byte { + buf := bytes.NewBuffer(nil) + buf.WriteString(string(r.prompt)) + if r.cfg.EnableMask && len(r.buf) > 0 { + buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1))) + if r.buf[len(r.buf)-1] == '\n' { + buf.Write([]byte{'\n'}) + } else { + buf.Write([]byte(string(r.cfg.MaskRune))) + } + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + + } else { + for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) { + if e == '\t' { + buf.WriteString(strings.Repeat(" ", TabWidth)) + } else { + buf.WriteRune(e) + } + } + if r.isInLineEdge() { + buf.Write([]byte(" \b")) + } + } + // cursor position + if len(r.buf) > r.idx { + buf.Write(r.getBackspaceSequence()) + } + return buf.Bytes() +} + +func (r *RuneBuffer) getBackspaceSequence() []byte { + var sep = map[int]bool{} + + var i int + for { + if i >= runes.WidthAll(r.buf) { + break + } + + if i == 0 { + i -= r.promptLen() + } + i += r.width + + sep[i] = true + } + var buf []byte + for i := len(r.buf); i > r.idx; i-- { + // move input to the left of one + buf = append(buf, '\b') + if sep[i] { + // up one line, go to the start of the line and move cursor right to the end (r.width) + buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...) + } + } + + return buf + +} + +func (r *RuneBuffer) Reset() []rune { + ret := runes.Copy(r.buf) + r.buf = r.buf[:0] + r.idx = 0 + return ret +} + +func (r *RuneBuffer) calWidth(m int) int { + if m > 0 { + return runes.WidthAll(r.buf[r.idx : r.idx+m]) + } + return runes.WidthAll(r.buf[r.idx+m : r.idx]) +} + +func (r *RuneBuffer) SetStyle(start, end int, style string) { + if end < start { + panic("end < start") + } + + // goto start + move := start - r.idx + if move > 0 { + r.w.Write([]byte(string(r.buf[r.idx : r.idx+move]))) + } else { + r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move))) + } + r.w.Write([]byte("\033[" + style + "m")) + r.w.Write([]byte(string(r.buf[start:end]))) + r.w.Write([]byte("\033[0m")) + // TODO: move back +} + +func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) { + r.Refresh(func() { + r.buf = buf + r.idx = idx + }) +} + +func (r *RuneBuffer) Set(buf []rune) { + r.SetWithIdx(len(buf), buf) +} + +func (r *RuneBuffer) SetPrompt(prompt string) { + r.Lock() + r.prompt = []rune(prompt) + r.Unlock() +} + +func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { + buf := bufio.NewWriter(w) + + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen())) + buf.Write([]byte("\033[J")) + } else { + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") + } + } + buf.Flush() + return +} + +func (r *RuneBuffer) Clean() { + r.Lock() + r.clean() + r.Unlock() +} + +func (r *RuneBuffer) clean() { + r.cleanWithIdxLine(r.idxLine(r.width)) +} + +func (r *RuneBuffer) cleanWithIdxLine(idxLine int) { + if r.hadClean || !r.interactive { + return + } + r.hadClean = true + r.cleanOutput(r.w, idxLine) +} diff --git a/vendor/github.com/chzyer/readline/runes.go b/vendor/github.com/chzyer/readline/runes.go new file mode 100644 index 0000000..a669bc4 --- /dev/null +++ b/vendor/github.com/chzyer/readline/runes.go @@ -0,0 +1,223 @@ +package readline + +import ( + "bytes" + "unicode" + "unicode/utf8" +) + +var runes = Runes{} +var TabWidth = 4 + +type Runes struct{} + +func (Runes) EqualRune(a, b rune, fold bool) bool { + if a == b { + return true + } + if !fold { + return false + } + if a > b { + a, b = b, a + } + if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' { + if b == a+'a'-'A' { + return true + } + } + return false +} + +func (r Runes) EqualRuneFold(a, b rune) bool { + return r.EqualRune(a, b, true) +} + +func (r Runes) EqualFold(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if r.EqualRuneFold(a[i], b[i]) { + continue + } + return false + } + + return true +} + +func (Runes) Equal(a, b []rune) bool { + if len(a) != len(b) { + return false + } + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + return true +} + +func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int { + for i := len(r) - len(sub); i >= 0; i-- { + found := true + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +// Search in runes from end to front +func (rs Runes) IndexAllBck(r, sub []rune) int { + return rs.IndexAllBckEx(r, sub, false) +} + +// Search in runes from front to end +func (rs Runes) IndexAll(r, sub []rune) int { + return rs.IndexAllEx(r, sub, false) +} + +func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int { + for i := 0; i < len(r); i++ { + found := true + if len(r[i:]) < len(sub) { + return -1 + } + for j := 0; j < len(sub); j++ { + if !rs.EqualRune(r[i+j], sub[j], fold) { + found = false + break + } + } + if found { + return i + } + } + return -1 +} + +func (Runes) Index(r rune, rs []rune) int { + for i := 0; i < len(rs); i++ { + if rs[i] == r { + return i + } + } + return -1 +} + +func (Runes) ColorFilter(r []rune) []rune { + newr := make([]rune, 0, len(r)) + for pos := 0; pos < len(r); pos++ { + if r[pos] == '\033' && r[pos+1] == '[' { + idx := runes.Index('m', r[pos+2:]) + if idx == -1 { + continue + } + pos += idx + 2 + continue + } + newr = append(newr, r[pos]) + } + return newr +} + +var zeroWidth = []*unicode.RangeTable{ + unicode.Mn, + unicode.Me, + unicode.Cc, + unicode.Cf, +} + +var doubleWidth = []*unicode.RangeTable{ + unicode.Han, + unicode.Hangul, + unicode.Hiragana, + unicode.Katakana, +} + +func (Runes) Width(r rune) int { + if r == '\t' { + return TabWidth + } + if unicode.IsOneOf(zeroWidth, r) { + return 0 + } + if unicode.IsOneOf(doubleWidth, r) { + return 2 + } + return 1 +} + +func (Runes) WidthAll(r []rune) (length int) { + for i := 0; i < len(r); i++ { + length += runes.Width(r[i]) + } + return +} + +func (Runes) Backspace(r []rune) []byte { + return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r)) +} + +func (Runes) Copy(r []rune) []rune { + n := make([]rune, len(r)) + copy(n, r) + return n +} + +func (Runes) HasPrefixFold(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.EqualFold(r[:len(prefix)], prefix) +} + +func (Runes) HasPrefix(r, prefix []rune) bool { + if len(r) < len(prefix) { + return false + } + return runes.Equal(r[:len(prefix)], prefix) +} + +func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) { + for i := 0; i < len(candicate[0]); i++ { + for j := 0; j < len(candicate)-1; j++ { + if i >= len(candicate[j]) || i >= len(candicate[j+1]) { + goto aggregate + } + if candicate[j][i] != candicate[j+1][i] { + goto aggregate + } + } + size = i + 1 + } +aggregate: + if size > 0 { + same = runes.Copy(candicate[0][:size]) + for i := 0; i < len(candicate); i++ { + n := runes.Copy(candicate[i]) + copy(n, n[size:]) + candicate[i] = n[:len(n)-size] + } + } + return +} + +func (Runes) TrimSpaceLeft(in []rune) []rune { + firstIndex := len(in) + for i, r := range in { + if unicode.IsSpace(r) == false { + firstIndex = i + break + } + } + return in[firstIndex:] +} diff --git a/vendor/github.com/chzyer/readline/search.go b/vendor/github.com/chzyer/readline/search.go new file mode 100644 index 0000000..52e8ff0 --- /dev/null +++ b/vendor/github.com/chzyer/readline/search.go @@ -0,0 +1,164 @@ +package readline + +import ( + "bytes" + "container/list" + "fmt" + "io" +) + +const ( + S_STATE_FOUND = iota + S_STATE_FAILING +) + +const ( + S_DIR_BCK = iota + S_DIR_FWD +) + +type opSearch struct { + inMode bool + state int + dir int + source *list.Element + w io.Writer + buf *RuneBuffer + data []rune + history *opHistory + cfg *Config + markStart int + markEnd int + width int +} + +func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { + return &opSearch{ + w: w, + buf: buf, + cfg: cfg, + history: history, + width: width, + } +} + +func (o *opSearch) OnWidthChange(newWidth int) { + o.width = newWidth +} + +func (o *opSearch) IsSearchMode() bool { + return o.inMode +} + +func (o *opSearch) SearchBackspace() { + if len(o.data) > 0 { + o.data = o.data[:len(o.data)-1] + o.search(true) + } +} + +func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { + if o.dir == S_DIR_BCK { + return o.history.FindBck(isNewSearch, o.data, o.buf.idx) + } + return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) +} + +func (o *opSearch) search(isChange bool) bool { + if len(o.data) == 0 { + o.state = S_STATE_FOUND + o.SearchRefresh(-1) + return true + } + idx, elem := o.findHistoryBy(isChange) + if elem == nil { + o.SearchRefresh(-2) + return false + } + o.history.current = elem + + item := o.history.showItem(o.history.current.Value) + start, end := 0, 0 + if o.dir == S_DIR_BCK { + start, end = idx, idx+len(o.data) + } else { + start, end = idx, idx+len(o.data) + idx += len(o.data) + } + o.buf.SetWithIdx(idx, item) + o.markStart, o.markEnd = start, end + o.SearchRefresh(idx) + return true +} + +func (o *opSearch) SearchChar(r rune) { + o.data = append(o.data, r) + o.search(true) +} + +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } + alreadyInMode := o.inMode + o.inMode = true + o.dir = dir + o.source = o.history.current + if alreadyInMode { + o.search(false) + } else { + o.SearchRefresh(-1) + } + return true +} + +func (o *opSearch) ExitSearchMode(revert bool) { + if revert { + o.history.current = o.source + o.buf.Set(o.history.showItem(o.history.current.Value)) + } + o.markStart, o.markEnd = 0, 0 + o.state = S_STATE_FOUND + o.inMode = false + o.source = nil + o.data = nil +} + +func (o *opSearch) SearchRefresh(x int) { + if x == -2 { + o.state = S_STATE_FAILING + } else if x >= 0 { + o.state = S_STATE_FOUND + } + if x < 0 { + x = o.buf.idx + } + x = o.buf.CurrentWidth(x) + x += o.buf.PromptLen() + x = x % o.width + + if o.markStart > 0 { + o.buf.SetStyle(o.markStart, o.markEnd, "4") + } + + lineCnt := o.buf.CursorLineCount() + buf := bytes.NewBuffer(nil) + buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) + buf.WriteString("\033[J") + if o.state == S_STATE_FAILING { + buf.WriteString("failing ") + } + if o.dir == S_DIR_BCK { + buf.WriteString("bck") + } else if o.dir == S_DIR_FWD { + buf.WriteString("fwd") + } + buf.WriteString("-i-search: ") + buf.WriteString(string(o.data)) // keyword + buf.WriteString("\033[4m \033[0m") // _ + fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev + if x > 0 { + fmt.Fprintf(buf, "\033[%dC", x) // move forward + } + o.w.Write(buf.Bytes()) +} diff --git a/vendor/github.com/chzyer/readline/std.go b/vendor/github.com/chzyer/readline/std.go new file mode 100644 index 0000000..61d44b7 --- /dev/null +++ b/vendor/github.com/chzyer/readline/std.go @@ -0,0 +1,197 @@ +package readline + +import ( + "io" + "os" + "sync" + "sync/atomic" +) + +var ( + Stdin io.ReadCloser = os.Stdin + Stdout io.WriteCloser = os.Stdout + Stderr io.WriteCloser = os.Stderr +) + +var ( + std *Instance + stdOnce sync.Once +) + +// global instance will not submit history automatic +func getInstance() *Instance { + stdOnce.Do(func() { + std, _ = NewEx(&Config{ + DisableAutoSaveHistory: true, + }) + }) + return std +} + +// let readline load history from filepath +// and try to persist history into disk +// set fp to "" to prevent readline persisting history to disk +// so the `AddHistory` will return nil error forever. +func SetHistoryPath(fp string) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.HistoryFile = fp + ins.SetConfig(cfg) +} + +// set auto completer to global instance +func SetAutoComplete(completer AutoCompleter) { + ins := getInstance() + cfg := ins.Config.Clone() + cfg.AutoComplete = completer + ins.SetConfig(cfg) +} + +// add history to global instance manually +// raise error only if `SetHistoryPath` is set with a non-empty path +func AddHistory(content string) error { + ins := getInstance() + return ins.SaveHistory(content) +} + +func Password(prompt string) ([]byte, error) { + ins := getInstance() + return ins.ReadPassword(prompt) +} + +// readline with global configs +func Line(prompt string) (string, error) { + ins := getInstance() + ins.SetPrompt(prompt) + return ins.Readline() +} + +type CancelableStdin struct { + r io.Reader + mutex sync.Mutex + stop chan struct{} + closed int32 + notify chan struct{} + data []byte + read int + err error +} + +func NewCancelableStdin(r io.Reader) *CancelableStdin { + c := &CancelableStdin{ + r: r, + notify: make(chan struct{}), + stop: make(chan struct{}), + } + go c.ioloop() + return c +} + +func (c *CancelableStdin) ioloop() { +loop: + for { + select { + case <-c.notify: + c.read, c.err = c.r.Read(c.data) + select { + case c.notify <- struct{}{}: + case <-c.stop: + break loop + } + case <-c.stop: + break loop + } + } +} + +func (c *CancelableStdin) Read(b []byte) (n int, err error) { + c.mutex.Lock() + defer c.mutex.Unlock() + if atomic.LoadInt32(&c.closed) == 1 { + return 0, io.EOF + } + + c.data = b + select { + case c.notify <- struct{}{}: + case <-c.stop: + return 0, io.EOF + } + select { + case <-c.notify: + return c.read, c.err + case <-c.stop: + return 0, io.EOF + } +} + +func (c *CancelableStdin) Close() error { + if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { + close(c.stop) + } + return nil +} + +// FillableStdin is a stdin reader which can prepend some data before +// reading into the real stdin +type FillableStdin struct { + sync.Mutex + stdin io.Reader + stdinBuffer io.ReadCloser + buf []byte + bufErr error +} + +// NewFillableStdin gives you FillableStdin +func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) { + r, w := io.Pipe() + s := &FillableStdin{ + stdinBuffer: r, + stdin: stdin, + } + s.ioloop() + return s, w +} + +func (s *FillableStdin) ioloop() { + go func() { + for { + bufR := make([]byte, 100) + var n int + n, s.bufErr = s.stdinBuffer.Read(bufR) + if s.bufErr != nil { + if s.bufErr == io.ErrClosedPipe { + break + } + } + s.Lock() + s.buf = append(s.buf, bufR[:n]...) + s.Unlock() + } + }() +} + +// Read will read from the local buffer and if no data, read from stdin +func (s *FillableStdin) Read(p []byte) (n int, err error) { + s.Lock() + i := len(s.buf) + if len(p) < i { + i = len(p) + } + if i > 0 { + n := copy(p, s.buf) + s.buf = s.buf[:0] + cerr := s.bufErr + s.bufErr = nil + s.Unlock() + return n, cerr + } + s.Unlock() + n, err = s.stdin.Read(p) + return n, err +} + +func (s *FillableStdin) Close() error { + s.stdinBuffer.Close() + return nil +} diff --git a/vendor/github.com/chzyer/readline/std_windows.go b/vendor/github.com/chzyer/readline/std_windows.go new file mode 100644 index 0000000..b10f91b --- /dev/null +++ b/vendor/github.com/chzyer/readline/std_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package readline + +func init() { + Stdin = NewRawReader() + Stdout = NewANSIWriter(Stdout) + Stderr = NewANSIWriter(Stderr) +} diff --git a/vendor/github.com/chzyer/readline/term.go b/vendor/github.com/chzyer/readline/term.go new file mode 100644 index 0000000..133993c --- /dev/null +++ b/vendor/github.com/chzyer/readline/term.go @@ -0,0 +1,123 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" +) + +// State contains the state of a terminal. +type State struct { + termios Termios +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + _, err := getTermios(fd) + return err == nil +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var oldState State + + if termios, err := getTermios(fd); err != nil { + return nil, err + } else { + oldState.termios = *termios + } + + newState := oldState.termios + // This attempts to replicate the behaviour documented for cfmakeraw in + // the termios(3) manpage. + newState.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON + // newState.Oflag &^= syscall.OPOST + newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN + newState.Cflag &^= syscall.CSIZE | syscall.PARENB + newState.Cflag |= syscall.CS8 + + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + return &oldState, setTermios(fd, &newState) +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + termios, err := getTermios(fd) + if err != nil { + return nil, err + } + + return &State{termios: *termios}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + return setTermios(fd, &state.termios) +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + oldState, err := getTermios(fd) + if err != nil { + return nil, err + } + + newState := oldState + newState.Lflag &^= syscall.ECHO + newState.Lflag |= syscall.ICANON | syscall.ISIG + newState.Iflag |= syscall.ICRNL + if err := setTermios(fd, newState); err != nil { + return nil, err + } + + defer func() { + setTermios(fd, oldState) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(fd, buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/term_bsd.go b/vendor/github.com/chzyer/readline/term_bsd.go new file mode 100644 index 0000000..68b56ea --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_bsd.go @@ -0,0 +1,29 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCGETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), syscall.TIOCSETA, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_linux.go b/vendor/github.com/chzyer/readline/term_linux.go new file mode 100644 index 0000000..e3392b4 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_linux.go @@ -0,0 +1,33 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package readline + +import ( + "syscall" + "unsafe" +) + +// These constants are declared here, rather than importing +// them from the syscall package as some syscall packages, even +// on linux, for example gccgo, do not declare them. +const ioctlReadTermios = 0x5401 // syscall.TCGETS +const ioctlWriteTermios = 0x5402 // syscall.TCSETS + +func getTermios(fd int) (*Termios, error) { + termios := new(Termios) + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return nil, err + } + return termios, nil +} + +func setTermios(fd int, termios *Termios) error { + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(termios)), 0, 0, 0) + if err != 0 { + return err + } + return nil +} diff --git a/vendor/github.com/chzyer/readline/term_solaris.go b/vendor/github.com/chzyer/readline/term_solaris.go new file mode 100644 index 0000000..4c27273 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_solaris.go @@ -0,0 +1,32 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build solaris + +package readline + +import "golang.org/x/sys/unix" + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) + if err != nil { + return 0, 0, err + } + return int(ws.Col), int(ws.Row), nil +} + +type Termios unix.Termios + +func getTermios(fd int) (*Termios, error) { + termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) + if err != nil { + return nil, err + } + return (*Termios)(termios), nil +} + +func setTermios(fd int, termios *Termios) error { + return unix.IoctlSetTermios(fd, unix.TCSETSF, (*unix.Termios)(termios)) +} diff --git a/vendor/github.com/chzyer/readline/term_unix.go b/vendor/github.com/chzyer/readline/term_unix.go new file mode 100644 index 0000000..d3ea242 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_unix.go @@ -0,0 +1,24 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd + +package readline + +import ( + "syscall" + "unsafe" +) + +type Termios syscall.Termios + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (int, int, error) { + var dimensions [4]uint16 + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0) + if err != 0 { + return 0, 0, err + } + return int(dimensions[1]), int(dimensions[0]), nil +} diff --git a/vendor/github.com/chzyer/readline/term_windows.go b/vendor/github.com/chzyer/readline/term_windows.go new file mode 100644 index 0000000..1290e00 --- /dev/null +++ b/vendor/github.com/chzyer/readline/term_windows.go @@ -0,0 +1,171 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package terminal provides support functions for dealing with terminals, as +// commonly found on UNIX systems. +// +// Putting a terminal into raw mode is the most common requirement: +// +// oldState, err := terminal.MakeRaw(0) +// if err != nil { +// panic(err) +// } +// defer terminal.Restore(0, oldState) +package readline + +import ( + "io" + "syscall" + "unsafe" +) + +const ( + enableLineInput = 2 + enableEchoInput = 4 + enableProcessedInput = 1 + enableWindowInput = 8 + enableMouseInput = 16 + enableInsertMode = 32 + enableQuickEditMode = 64 + enableExtendedFlags = 128 + enableAutoPosition = 256 + enableProcessedOutput = 1 + enableWrapAtEolOutput = 2 +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") +) + +type ( + coord struct { + x short + y short + } + smallRect struct { + left short + top short + right short + bottom short + } + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord + } +) + +type State struct { + mode uint32 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// GetState returns the current state of a terminal which may be useful to +// restore the terminal after a signal. +func GetState(fd int) (*State, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + return &State{st}, nil +} + +// Restore restores the terminal connected to the given file descriptor to a +// previous state. +func restoreTerm(fd int, state *State) error { + _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) + return err +} + +// GetSize returns the dimensions of the given terminal. +func GetSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) + if e != 0 { + return 0, 0, error(e) + } + return int(info.size.x), int(info.size.y), nil +} + +// ReadPassword reads a line of input from a terminal without local echo. This +// is commonly used for inputting passwords and other sensitive data. The slice +// returned does not include the \n. +func ReadPassword(fd int) ([]byte, error) { + var st uint32 + _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + if e != 0 { + return nil, error(e) + } + old := st + + st &^= (enableEchoInput) + st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) + _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) + if e != 0 { + return nil, error(e) + } + + defer func() { + syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) + }() + + var buf [16]byte + var ret []byte + for { + n, err := syscall.Read(syscall.Handle(fd), buf[:]) + if err != nil { + return nil, err + } + if n == 0 { + if len(ret) == 0 { + return nil, io.EOF + } + break + } + if buf[n-1] == '\n' { + n-- + } + if n > 0 && buf[n-1] == '\r' { + n-- + } + ret = append(ret, buf[:n]...) + if n < len(buf) { + break + } + } + + return ret, nil +} diff --git a/vendor/github.com/chzyer/readline/terminal.go b/vendor/github.com/chzyer/readline/terminal.go new file mode 100644 index 0000000..1078631 --- /dev/null +++ b/vendor/github.com/chzyer/readline/terminal.go @@ -0,0 +1,238 @@ +package readline + +import ( + "bufio" + "fmt" + "io" + "strings" + "sync" + "sync/atomic" +) + +type Terminal struct { + m sync.Mutex + cfg *Config + outchan chan rune + closed int32 + stopChan chan struct{} + kickChan chan struct{} + wg sync.WaitGroup + isReading int32 + sleeping int32 + + sizeChan chan string +} + +func NewTerminal(cfg *Config) (*Terminal, error) { + if err := cfg.Init(); err != nil { + return nil, err + } + t := &Terminal{ + cfg: cfg, + kickChan: make(chan struct{}, 1), + outchan: make(chan rune), + stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), + } + + go t.ioloop() + return t, nil +} + +// SleepToResume will sleep myself, and return only if I'm resumed. +func (t *Terminal) SleepToResume() { + if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) { + return + } + defer atomic.StoreInt32(&t.sleeping, 0) + + t.ExitRawMode() + ch := WaitForResume() + SuspendMe() + <-ch + t.EnterRawMode() +} + +func (t *Terminal) EnterRawMode() (err error) { + return t.cfg.FuncMakeRaw() +} + +func (t *Terminal) ExitRawMode() (err error) { + return t.cfg.FuncExitRaw() +} + +func (t *Terminal) Write(b []byte) (int, error) { + return t.cfg.Stdout.Write(b) +} + +// WriteStdin prefill the next Stdin fetch +// Next time you call ReadLine() this value will be writen before the user input +func (t *Terminal) WriteStdin(b []byte) (int, error) { + return t.cfg.StdinWriter.Write(b) +} + +type termSize struct { + left int + top int +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + +func (t *Terminal) Print(s string) { + fmt.Fprintf(t.cfg.Stdout, "%s", s) +} + +func (t *Terminal) PrintRune(r rune) { + fmt.Fprintf(t.cfg.Stdout, "%c", r) +} + +func (t *Terminal) Readline() *Operation { + return NewOperation(t, t.cfg) +} + +// return rune(0) if meet EOF +func (t *Terminal) ReadRune() rune { + ch, ok := <-t.outchan + if !ok { + return rune(0) + } + return ch +} + +func (t *Terminal) IsReading() bool { + return atomic.LoadInt32(&t.isReading) == 1 +} + +func (t *Terminal) KickRead() { + select { + case t.kickChan <- struct{}{}: + default: + } +} + +func (t *Terminal) ioloop() { + t.wg.Add(1) + defer func() { + t.wg.Done() + close(t.outchan) + }() + + var ( + isEscape bool + isEscapeEx bool + expectNextChar bool + ) + + buf := bufio.NewReader(t.getStdin()) + for { + if !expectNextChar { + atomic.StoreInt32(&t.isReading, 0) + select { + case <-t.kickChan: + atomic.StoreInt32(&t.isReading, 1) + case <-t.stopChan: + return + } + } + expectNextChar = false + r, _, err := buf.ReadRune() + if err != nil { + if strings.Contains(err.Error(), "interrupted system call") { + expectNextChar = true + continue + } + break + } + + if isEscape { + isEscape = false + if r == CharEscapeEx { + expectNextChar = true + isEscapeEx = true + continue + } + r = escapeKey(r, buf) + } else if isEscapeEx { + isEscapeEx = false + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } + } + + expectNextChar = true + switch r { + case CharEsc: + if t.cfg.VimMode { + t.outchan <- r + break + } + isEscape = true + case CharInterrupt, CharEnter, CharCtrlJ, CharDelete: + expectNextChar = false + fallthrough + default: + t.outchan <- r + } + } + +} + +func (t *Terminal) Bell() { + fmt.Fprintf(t, "%c", CharBell) +} + +func (t *Terminal) Close() error { + if atomic.SwapInt32(&t.closed, 1) != 0 { + return nil + } + if closer, ok := t.cfg.Stdin.(io.Closer); ok { + closer.Close() + } + close(t.stopChan) + t.wg.Wait() + return t.ExitRawMode() +} + +func (t *Terminal) GetConfig() *Config { + t.m.Lock() + cfg := *t.cfg + t.m.Unlock() + return &cfg +} + +func (t *Terminal) getStdin() io.Reader { + t.m.Lock() + r := t.cfg.Stdin + t.m.Unlock() + return r +} + +func (t *Terminal) SetConfig(c *Config) error { + if err := c.Init(); err != nil { + return err + } + t.m.Lock() + t.cfg = c + t.m.Unlock() + return nil +} diff --git a/vendor/github.com/chzyer/readline/utils.go b/vendor/github.com/chzyer/readline/utils.go new file mode 100644 index 0000000..af4e005 --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils.go @@ -0,0 +1,277 @@ +package readline + +import ( + "bufio" + "bytes" + "container/list" + "fmt" + "os" + "strconv" + "strings" + "sync" + "time" + "unicode" +) + +var ( + isWindows = false +) + +const ( + CharLineStart = 1 + CharBackward = 2 + CharInterrupt = 3 + CharDelete = 4 + CharLineEnd = 5 + CharForward = 6 + CharBell = 7 + CharCtrlH = 8 + CharTab = 9 + CharCtrlJ = 10 + CharKill = 11 + CharCtrlL = 12 + CharEnter = 13 + CharNext = 14 + CharPrev = 16 + CharBckSearch = 18 + CharFwdSearch = 19 + CharTranspose = 20 + CharCtrlU = 21 + CharCtrlW = 23 + CharCtrlY = 25 + CharCtrlZ = 26 + CharEsc = 27 + CharEscapeEx = 91 + CharBackspace = 127 +) + +const ( + MetaBackward rune = -iota - 1 + MetaForward + MetaDelete + MetaBackspace + MetaTranspose +) + +// WaitForResume need to call before current process got suspend. +// It will run a ticker until a long duration is occurs, +// which means this process is resumed. +func WaitForResume() chan struct{} { + ch := make(chan struct{}) + var wg sync.WaitGroup + wg.Add(1) + go func() { + ticker := time.NewTicker(10 * time.Millisecond) + t := time.Now() + wg.Done() + for { + now := <-ticker.C + if now.Sub(t) > 100*time.Millisecond { + break + } + t = now + } + ticker.Stop() + ch <- struct{}{} + }() + wg.Wait() + return ch +} + +func Restore(fd int, state *State) error { + err := restoreTerm(fd, state) + if err != nil { + // errno 0 means everything is ok :) + if err.Error() == "errno 0" { + return nil + } else { + return err + } + } + return nil +} + +func IsPrintable(key rune) bool { + isInSurrogateArea := key >= 0xd800 && key <= 0xdbff + return key >= 32 && !isInSurrogateArea +} + +// translate Esc[X +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { + case 'D': + r = CharBackward + case 'C': + r = CharForward + case 'A': + r = CharPrev + case 'B': + r = CharNext + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + case '~': + if key.attr == "3" { + r = CharDelete + } + default: + } + return r +} + +type escapeKeyPair struct { + attr string + typ rune +} + +func (e *escapeKeyPair) Get2() (int, int, bool) { + sp := strings.Split(e.attr, ";") + if len(sp) < 2 { + return -1, -1, false + } + s1, err := strconv.Atoi(sp[0]) + if err != nil { + return -1, -1, false + } + s2, err := strconv.Atoi(sp[1]) + if err != nil { + return -1, -1, false + } + return s1, s2, true +} + +func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { + p := escapeKeyPair{} + buf := bytes.NewBuffer(nil) + for { + if r == ';' { + } else if unicode.IsNumber(r) { + } else { + p.typ = r + break + } + buf.WriteRune(r) + r, _, _ = reader.ReadRune() + } + p.attr = buf.String() + return &p +} + +// translate EscX to Meta+X +func escapeKey(r rune, reader *bufio.Reader) rune { + switch r { + case 'b': + r = MetaBackward + case 'f': + r = MetaForward + case 'd': + r = MetaDelete + case CharTranspose: + r = MetaTranspose + case CharBackspace: + r = MetaBackspace + case 'O': + d, _, _ := reader.ReadRune() + switch d { + case 'H': + r = CharLineStart + case 'F': + r = CharLineEnd + default: + reader.UnreadRune() + } + case CharEsc: + + } + return r +} + +func SplitByLine(start, screenWidth int, rs []rune) []string { + var ret []string + buf := bytes.NewBuffer(nil) + currentWidth := start + for _, r := range rs { + w := runes.Width(r) + currentWidth += w + buf.WriteRune(r) + if currentWidth >= screenWidth { + ret = append(ret, buf.String()) + buf.Reset() + currentWidth = 0 + } + } + ret = append(ret, buf.String()) + return ret +} + +// calculate how many lines for N character +func LineCount(screenWidth, w int) int { + r := w / screenWidth + if w%screenWidth != 0 { + r++ + } + return r +} + +func IsWordBreak(i rune) bool { + switch { + case i >= 'a' && i <= 'z': + case i >= 'A' && i <= 'Z': + case i >= '0' && i <= '9': + default: + return true + } + return false +} + +func GetInt(s []string, def int) int { + if len(s) == 0 { + return def + } + c, err := strconv.Atoi(s[0]) + if err != nil { + return def + } + return c +} + +type RawMode struct { + state *State +} + +func (r *RawMode) Enter() (err error) { + r.state, err = MakeRaw(GetStdin()) + return err +} + +func (r *RawMode) Exit() error { + if r.state == nil { + return nil + } + return Restore(GetStdin(), r.state) +} + +// ----------------------------------------------------------------------------- + +func sleep(n int) { + Debug(n) + time.Sleep(2000 * time.Millisecond) +} + +// print a linked list to Debug() +func debugList(l *list.List) { + idx := 0 + for e := l.Front(); e != nil; e = e.Next() { + Debug(idx, fmt.Sprintf("%+v", e.Value)) + idx++ + } +} + +// append log info to another file +func Debug(o ...interface{}) { + f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + fmt.Fprintln(f, o...) + f.Close() +} diff --git a/vendor/github.com/chzyer/readline/utils_unix.go b/vendor/github.com/chzyer/readline/utils_unix.go new file mode 100644 index 0000000..f88dac9 --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_unix.go @@ -0,0 +1,83 @@ +// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd solaris + +package readline + +import ( + "io" + "os" + "os/signal" + "sync" + "syscall" +) + +type winsize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 +} + +// SuspendMe use to send suspend signal to myself, when we in the raw mode. +// For OSX it need to send to parent's pid +// For Linux it need to send to myself +func SuspendMe() { + p, _ := os.FindProcess(os.Getppid()) + p.Signal(syscall.SIGTSTP) + p, _ = os.FindProcess(os.Getpid()) + p.Signal(syscall.SIGTSTP) +} + +// get width of the terminal +func getWidth(stdoutFd int) int { + cols, _, err := GetSize(stdoutFd) + if err != nil { + return -1 + } + return cols +} + +func GetScreenWidth() int { + w := getWidth(syscall.Stdout) + if w < 0 { + w = getWidth(syscall.Stderr) + } + return w +} + +// ClearScreen clears the console screen +func ClearScreen(w io.Writer) (int, error) { + return w.Write([]byte("\033[H")) +} + +func DefaultIsTerminal() bool { + return IsTerminal(syscall.Stdin) && (IsTerminal(syscall.Stdout) || IsTerminal(syscall.Stderr)) +} + +func GetStdin() int { + return syscall.Stdin +} + +// ----------------------------------------------------------------------------- + +var ( + widthChange sync.Once + widthChangeCallback func() +) + +func DefaultOnWidthChanged(f func()) { + widthChangeCallback = f + widthChange.Do(func() { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGWINCH) + + go func() { + for { + _, ok := <-ch + if !ok { + break + } + widthChangeCallback() + } + }() + }) +} diff --git a/vendor/github.com/chzyer/readline/utils_windows.go b/vendor/github.com/chzyer/readline/utils_windows.go new file mode 100644 index 0000000..5bfa55d --- /dev/null +++ b/vendor/github.com/chzyer/readline/utils_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package readline + +import ( + "io" + "syscall" +) + +func SuspendMe() { +} + +func GetStdin() int { + return int(syscall.Stdin) +} + +func init() { + isWindows = true +} + +// get width of the terminal +func GetScreenWidth() int { + info, _ := GetConsoleScreenBufferInfo() + if info == nil { + return -1 + } + return int(info.dwSize.x) +} + +// ClearScreen clears the console screen +func ClearScreen(_ io.Writer) error { + return SetConsoleCursorPosition(&_COORD{0, 0}) +} + +func DefaultIsTerminal() bool { + return true +} + +func DefaultOnWidthChanged(func()) { + +} diff --git a/vendor/github.com/chzyer/readline/vim.go b/vendor/github.com/chzyer/readline/vim.go new file mode 100644 index 0000000..bedf2c1 --- /dev/null +++ b/vendor/github.com/chzyer/readline/vim.go @@ -0,0 +1,176 @@ +package readline + +const ( + VIM_NORMAL = iota + VIM_INSERT + VIM_VISUAL +) + +type opVim struct { + cfg *Config + op *Operation + vimMode int +} + +func newVimMode(op *Operation) *opVim { + ov := &opVim{ + cfg: op.cfg, + op: op, + } + ov.SetVimMode(ov.cfg.VimMode) + return ov +} + +func (o *opVim) SetVimMode(on bool) { + if o.cfg.VimMode && !on { // turn off + o.ExitVimMode() + } + o.cfg.VimMode = on + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) IsEnableVimMode() bool { + return o.cfg.VimMode +} + +func (o *opVim) handleVimNormalMovement(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'h': + t = CharBackward + case 'j': + t = CharNext + case 'k': + t = CharPrev + case 'l': + t = CharForward + case '0', '^': + rb.MoveToLineStart() + case '$': + rb.MoveToLineEnd() + case 'x': + rb.Delete() + if rb.IsCursorInEnd() { + rb.MoveBackward() + } + case 'r': + rb.Replace(readNext()) + case 'd': + next := readNext() + switch next { + case 'd': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + case 'p': + rb.Yank() + case 'b', 'B': + rb.MoveToPrevWord() + case 'w', 'W': + rb.MoveToNextWord() + case 'e', 'E': + rb.MoveToEndWord() + case 'f', 'F', 't', 'T': + next := readNext() + prevChar := r == 't' || r == 'T' + reverse := r == 'F' || r == 'T' + switch next { + case CharEsc: + default: + rb.MoveTo(next, prevChar, reverse) + } + default: + return r, false + } + return t, true +} + +func (o *opVim) handleVimNormalEnterInsert(r rune, readNext func() rune) (t rune, handled bool) { + rb := o.op.buf + handled = true + switch r { + case 'i': + case 'I': + rb.MoveToLineStart() + case 'a': + rb.MoveForward() + case 'A': + rb.MoveToLineEnd() + case 's': + rb.Delete() + case 'S': + rb.Erase() + case 'c': + next := readNext() + switch next { + case 'c': + rb.Erase() + case 'w': + rb.DeleteWord() + case 'h': + rb.Backspace() + case 'l': + rb.Delete() + } + default: + return r, false + } + + o.EnterVimInsertMode() + return +} + +func (o *opVim) HandleVimNormal(r rune, readNext func() rune) (t rune) { + switch r { + case CharEnter, CharInterrupt: + o.ExitVimMode() + return r + } + + if r, handled := o.handleVimNormalMovement(r, readNext); handled { + return r + } + + if r, handled := o.handleVimNormalEnterInsert(r, readNext); handled { + return r + } + + // invalid operation + o.op.t.Bell() + return 0 +} + +func (o *opVim) EnterVimInsertMode() { + o.vimMode = VIM_INSERT +} + +func (o *opVim) ExitVimInsertMode() { + o.vimMode = VIM_NORMAL +} + +func (o *opVim) HandleVim(r rune, readNext func() rune) rune { + if o.vimMode == VIM_NORMAL { + return o.HandleVimNormal(r, readNext) + } + if r == CharEsc { + o.ExitVimInsertMode() + return 0 + } + + switch o.vimMode { + case VIM_INSERT: + return r + case VIM_VISUAL: + } + return r +} diff --git a/vendor/github.com/chzyer/readline/windows_api.go b/vendor/github.com/chzyer/readline/windows_api.go new file mode 100644 index 0000000..63f4f7b --- /dev/null +++ b/vendor/github.com/chzyer/readline/windows_api.go @@ -0,0 +1,152 @@ +// +build windows + +package readline + +import ( + "reflect" + "syscall" + "unsafe" +) + +var ( + kernel = NewKernel() + stdout = uintptr(syscall.Stdout) + stdin = uintptr(syscall.Stdin) +) + +type Kernel struct { + SetConsoleCursorPosition, + SetConsoleTextAttribute, + FillConsoleOutputCharacterW, + FillConsoleOutputAttribute, + ReadConsoleInputW, + GetConsoleScreenBufferInfo, + GetConsoleCursorInfo, + GetStdHandle CallFunc +} + +type short int16 +type word uint16 +type dword uint32 +type wchar uint16 + +type _COORD struct { + x short + y short +} + +func (c *_COORD) ptr() uintptr { + return uintptr(*(*int32)(unsafe.Pointer(c))) +} + +const ( + EVENT_KEY = 0x0001 + EVENT_MOUSE = 0x0002 + EVENT_WINDOW_BUFFER_SIZE = 0x0004 + EVENT_MENU = 0x0008 + EVENT_FOCUS = 0x0010 +) + +type _KEY_EVENT_RECORD struct { + bKeyDown int32 + wRepeatCount word + wVirtualKeyCode word + wVirtualScanCode word + unicodeChar wchar + dwControlKeyState dword +} + +// KEY_EVENT_RECORD KeyEvent; +// MOUSE_EVENT_RECORD MouseEvent; +// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; +// MENU_EVENT_RECORD MenuEvent; +// FOCUS_EVENT_RECORD FocusEvent; +type _INPUT_RECORD struct { + EventType word + Padding uint16 + Event [16]byte +} + +type _CONSOLE_SCREEN_BUFFER_INFO struct { + dwSize _COORD + dwCursorPosition _COORD + wAttributes word + srWindow _SMALL_RECT + dwMaximumWindowSize _COORD +} + +type _SMALL_RECT struct { + left short + top short + right short + bottom short +} + +type _CONSOLE_CURSOR_INFO struct { + dwSize dword + bVisible bool +} + +type CallFunc func(u ...uintptr) error + +func NewKernel() *Kernel { + k := &Kernel{} + kernel32 := syscall.NewLazyDLL("kernel32.dll") + v := reflect.ValueOf(k).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + name := t.Field(i).Name + f := kernel32.NewProc(name) + v.Field(i).Set(reflect.ValueOf(k.Wrap(f))) + } + return k +} + +func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc { + return func(args ...uintptr) error { + var r0 uintptr + var e1 syscall.Errno + size := uintptr(len(args)) + if len(args) <= 3 { + buf := make([]uintptr, 3) + copy(buf, args) + r0, _, e1 = syscall.Syscall(p.Addr(), size, + buf[0], buf[1], buf[2]) + } else { + buf := make([]uintptr, 6) + copy(buf, args) + r0, _, e1 = syscall.Syscall6(p.Addr(), size, + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], + ) + } + + if int(r0) == 0 { + if e1 != 0 { + return error(e1) + } else { + return syscall.EINVAL + } + } + return nil + } + +} + +func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { + t := new(_CONSOLE_SCREEN_BUFFER_INFO) + err := kernel.GetConsoleScreenBufferInfo( + stdout, + uintptr(unsafe.Pointer(t)), + ) + return t, err +} + +func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { + t := new(_CONSOLE_CURSOR_INFO) + err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) + return t, err +} + +func SetConsoleCursorPosition(c *_COORD) error { + return kernel.SetConsoleCursorPosition(stdout, c.ptr()) +} diff --git a/vendor/github.com/juju/ansiterm/LICENSE b/vendor/github.com/juju/ansiterm/LICENSE new file mode 100644 index 0000000..ade9307 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/LICENSE @@ -0,0 +1,191 @@ +All files in this repository are licensed as follows. If you contribute +to this repository, it is assumed that you license your contribution +under the same license unless you state otherwise. + +All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file. + +This software is licensed under the LGPLv3, included below. + +As a special exception to the GNU Lesser General Public License version 3 +("LGPL3"), the copyright holders of this Library give you permission to +convey to a third party a Combined Work that links statically or dynamically +to this Library without providing any Minimal Corresponding Source or +Minimal Application Code as set out in 4d or providing the installation +information set out in section 4e, provided that you comply with the other +provisions of LGPL3 and provided that you meet, for the Application the +terms and conditions of the license(s) which apply to the Application. + +Except as stated in this special exception, the provisions of LGPL3 will +continue to comply in full to this Library. If you modify this Library, you +may apply this exception to your version of this Library, but you are not +obliged to do so. If you do not wish to do so, delete this exception +statement from your version. This exception does not (and cannot) modify any +license terms which apply to the Application, with which you must still +comply. + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/github.com/juju/ansiterm/Makefile b/vendor/github.com/juju/ansiterm/Makefile new file mode 100644 index 0000000..212fdcb --- /dev/null +++ b/vendor/github.com/juju/ansiterm/Makefile @@ -0,0 +1,14 @@ +# Copyright 2016 Canonical Ltd. +# Licensed under the LGPLv3, see LICENCE file for details. + +default: check + +check: + go test + +docs: + godoc2md github.com/juju/ansiterm > README.md + sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/ansiterm?status.svg)](https://godoc.org/github.com/juju/ansiterm)|' README.md + + +.PHONY: default check docs diff --git a/vendor/github.com/juju/ansiterm/README.md b/vendor/github.com/juju/ansiterm/README.md new file mode 100644 index 0000000..5674387 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/README.md @@ -0,0 +1,323 @@ + +# ansiterm + import "github.com/juju/ansiterm" + +Package ansiterm provides a Writer that writes out the ANSI escape +codes for color and styles. + + + + + + + +## type Color +``` go +type Color int +``` +Color represents one of the standard 16 ANSI colors. + + + +``` go +const ( + Default Color + Black + Red + Green + Yellow + Blue + Magenta + Cyan + Gray + DarkGray + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + White +) +``` + + + + + + + + +### func (Color) String +``` go +func (c Color) String() string +``` +String returns the name of the color. + + + +## type Context +``` go +type Context struct { + Foreground Color + Background Color + Styles []Style +} +``` +Context provides a way to specify both foreground and background colors +along with other styles and write text to a Writer with those colors and +styles. + + + + + + + + + +### func Background +``` go +func Background(color Color) *Context +``` +Background is a convenience function that creates a Context with the +specified color as the background color. + + +### func Foreground +``` go +func Foreground(color Color) *Context +``` +Foreground is a convenience function that creates a Context with the +specified color as the foreground color. + + +### func Styles +``` go +func Styles(styles ...Style) *Context +``` +Styles is a convenience function that creates a Context with the +specified styles set. + + + + +### func (\*Context) Fprint +``` go +func (c *Context) Fprint(w sgrWriter, args ...interface{}) +``` +Fprint will set the sgr values of the writer to the specified foreground, +background and styles, then formats using the default formats for its +operands and writes to w. Spaces are added between operands when neither is +a string. It returns the number of bytes written and any write error +encountered. + + + +### func (\*Context) Fprintf +``` go +func (c *Context) Fprintf(w sgrWriter, format string, args ...interface{}) +``` +Fprintf will set the sgr values of the writer to the specified +foreground, background and styles, then write the formatted string, +then reset the writer. + + + +### func (\*Context) SetBackground +``` go +func (c *Context) SetBackground(color Color) *Context +``` +SetBackground sets the background to the specified color. + + + +### func (\*Context) SetForeground +``` go +func (c *Context) SetForeground(color Color) *Context +``` +SetForeground sets the foreground to the specified color. + + + +### func (\*Context) SetStyle +``` go +func (c *Context) SetStyle(styles ...Style) *Context +``` +SetStyle replaces the styles with the new values. + + + +## type Style +``` go +type Style int +``` + + +``` go +const ( + Bold Style + Faint + Italic + Underline + Blink + Reverse + Strikethrough + Conceal +) +``` + + + + + + + + +### func (Style) String +``` go +func (s Style) String() string +``` + + +## type TabWriter +``` go +type TabWriter struct { + Writer + // contains filtered or unexported fields +} +``` +TabWriter is a filter that inserts padding around tab-delimited +columns in its input to align them in the output. + +It also setting of colors and styles over and above the standard +tabwriter package. + + + + + + + + + +### func NewTabWriter +``` go +func NewTabWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter +``` +NewTabWriter returns a writer that is able to set colors and styels. +The ansi escape codes are stripped for width calculations. + + + + +### func (\*TabWriter) Flush +``` go +func (t *TabWriter) Flush() error +``` +Flush should be called after the last call to Write to ensure +that any data buffered in the Writer is written to output. Any +incomplete escape sequence at the end is considered +complete for formatting purposes. + + + +### func (\*TabWriter) Init +``` go +func (t *TabWriter) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter +``` +A Writer must be initialized with a call to Init. The first parameter (output) +specifies the filter output. The remaining parameters control the formatting: + + + minwidth minimal cell width including any padding + tabwidth width of tab characters (equivalent number of spaces) + padding padding added to a cell before computing its width + padchar ASCII char used for padding + if padchar == '\t', the Writer will assume that the + width of a '\t' in the formatted output is tabwidth, + and cells are left-aligned independent of align_left + (for correct-looking results, tabwidth must correspond + to the tab width in the viewer displaying the result) + flags formatting control + + + +## type Writer +``` go +type Writer struct { + io.Writer + // contains filtered or unexported fields +} +``` +Writer allows colors and styles to be specified. If the io.Writer +is not a terminal capable of color, all attempts to set colors or +styles are no-ops. + + + + + + + + + +### func NewWriter +``` go +func NewWriter(w io.Writer) *Writer +``` +NewWriter returns a Writer that allows the caller to specify colors and +styles. If the io.Writer is not a terminal capable of color, all attempts +to set colors or styles are no-ops. + + + + +### func (\*Writer) ClearStyle +``` go +func (w *Writer) ClearStyle(s Style) +``` +ClearStyle clears the text style. + + + +### func (\*Writer) Reset +``` go +func (w *Writer) Reset() +``` +Reset returns the default foreground and background colors with no styles. + + + +### func (\*Writer) SetBackground +``` go +func (w *Writer) SetBackground(c Color) +``` +SetBackground sets the background color. + + + +### func (\*Writer) SetForeground +``` go +func (w *Writer) SetForeground(c Color) +``` +SetForeground sets the foreground color. + + + +### func (\*Writer) SetStyle +``` go +func (w *Writer) SetStyle(s Style) +``` +SetStyle sets the text style. + + + + + + + + + +- - - +Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) \ No newline at end of file diff --git a/vendor/github.com/juju/ansiterm/attribute.go b/vendor/github.com/juju/ansiterm/attribute.go new file mode 100644 index 0000000..f2daa48 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/attribute.go @@ -0,0 +1,50 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "sort" + "strings" +) + +type attribute int + +const ( + unknownAttribute attribute = -1 + reset attribute = 0 +) + +// sgr returns the escape sequence for the Select Graphic Rendition +// for the attribute. +func (a attribute) sgr() string { + if a < 0 { + return "" + } + return fmt.Sprintf("\x1b[%dm", a) +} + +type attributes []attribute + +func (a attributes) Len() int { return len(a) } +func (a attributes) Less(i, j int) bool { return a[i] < a[j] } +func (a attributes) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// sgr returns the combined escape sequence for the Select Graphic Rendition +// for the sequence of attributes. +func (a attributes) sgr() string { + switch len(a) { + case 0: + return "" + case 1: + return a[0].sgr() + default: + sort.Sort(a) + var values []string + for _, attr := range a { + values = append(values, fmt.Sprint(attr)) + } + return fmt.Sprintf("\x1b[%sm", strings.Join(values, ";")) + } +} diff --git a/vendor/github.com/juju/ansiterm/color.go b/vendor/github.com/juju/ansiterm/color.go new file mode 100644 index 0000000..0a97de3 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/color.go @@ -0,0 +1,119 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +const ( + _ Color = iota + Default + Black + Red + Green + Yellow + Blue + Magenta + Cyan + Gray + DarkGray + BrightRed + BrightGreen + BrightYellow + BrightBlue + BrightMagenta + BrightCyan + White +) + +// Color represents one of the standard 16 ANSI colors. +type Color int + +// String returns the name of the color. +func (c Color) String() string { + switch c { + case Default: + return "default" + case Black: + return "black" + case Red: + return "red" + case Green: + return "green" + case Yellow: + return "yellow" + case Blue: + return "blue" + case Magenta: + return "magenta" + case Cyan: + return "cyan" + case Gray: + return "gray" + case DarkGray: + return "darkgray" + case BrightRed: + return "brightred" + case BrightGreen: + return "brightgreen" + case BrightYellow: + return "brightyellow" + case BrightBlue: + return "brightblue" + case BrightMagenta: + return "brightmagenta" + case BrightCyan: + return "brightcyan" + case White: + return "white" + default: + return "" + } +} + +func (c Color) foreground() attribute { + switch c { + case Default: + return 39 + case Black: + return 30 + case Red: + return 31 + case Green: + return 32 + case Yellow: + return 33 + case Blue: + return 34 + case Magenta: + return 35 + case Cyan: + return 36 + case Gray: + return 37 + case DarkGray: + return 90 + case BrightRed: + return 91 + case BrightGreen: + return 92 + case BrightYellow: + return 93 + case BrightBlue: + return 94 + case BrightMagenta: + return 95 + case BrightCyan: + return 96 + case White: + return 97 + default: + return unknownAttribute + } +} + +func (c Color) background() attribute { + value := c.foreground() + if value != unknownAttribute { + return value + 10 + } + return value +} diff --git a/vendor/github.com/juju/ansiterm/context.go b/vendor/github.com/juju/ansiterm/context.go new file mode 100644 index 0000000..e61a867 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/context.go @@ -0,0 +1,95 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "io" +) + +// Context provides a way to specify both foreground and background colors +// along with other styles and write text to a Writer with those colors and +// styles. +type Context struct { + Foreground Color + Background Color + Styles []Style +} + +// Foreground is a convenience function that creates a Context with the +// specified color as the foreground color. +func Foreground(color Color) *Context { + return &Context{Foreground: color} +} + +// Background is a convenience function that creates a Context with the +// specified color as the background color. +func Background(color Color) *Context { + return &Context{Background: color} +} + +// Styles is a convenience function that creates a Context with the +// specified styles set. +func Styles(styles ...Style) *Context { + return &Context{Styles: styles} +} + +// SetForeground sets the foreground to the specified color. +func (c *Context) SetForeground(color Color) *Context { + c.Foreground = color + return c +} + +// SetBackground sets the background to the specified color. +func (c *Context) SetBackground(color Color) *Context { + c.Background = color + return c +} + +// SetStyle replaces the styles with the new values. +func (c *Context) SetStyle(styles ...Style) *Context { + c.Styles = styles + return c +} + +type sgrWriter interface { + io.Writer + writeSGR(value sgr) +} + +// Fprintf will set the sgr values of the writer to the specified +// foreground, background and styles, then write the formatted string, +// then reset the writer. +func (c *Context) Fprintf(w sgrWriter, format string, args ...interface{}) { + w.writeSGR(c) + fmt.Fprintf(w, format, args...) + w.writeSGR(reset) +} + +// Fprint will set the sgr values of the writer to the specified foreground, +// background and styles, then formats using the default formats for its +// operands and writes to w. Spaces are added between operands when neither is +// a string. It returns the number of bytes written and any write error +// encountered. +func (c *Context) Fprint(w sgrWriter, args ...interface{}) { + w.writeSGR(c) + fmt.Fprint(w, args...) + w.writeSGR(reset) +} + +func (c *Context) sgr() string { + var values attributes + if foreground := c.Foreground.foreground(); foreground != unknownAttribute { + values = append(values, foreground) + } + if background := c.Background.background(); background != unknownAttribute { + values = append(values, background) + } + for _, style := range c.Styles { + if value := style.enable(); value != unknownAttribute { + values = append(values, value) + } + } + return values.sgr() +} diff --git a/vendor/github.com/juju/ansiterm/doc.go b/vendor/github.com/juju/ansiterm/doc.go new file mode 100644 index 0000000..7827007 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/doc.go @@ -0,0 +1,6 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +// Package ansiterm provides a Writer that writes out the ANSI escape +// codes for color and styles. +package ansiterm diff --git a/vendor/github.com/juju/ansiterm/style.go b/vendor/github.com/juju/ansiterm/style.go new file mode 100644 index 0000000..0be42da --- /dev/null +++ b/vendor/github.com/juju/ansiterm/style.go @@ -0,0 +1,72 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +const ( + _ Style = iota + Bold + Faint + Italic + Underline + Blink + Reverse + Strikethrough + Conceal +) + +type Style int + +func (s Style) String() string { + switch s { + case Bold: + return "bold" + case Faint: + return "faint" + case Italic: + return "italic" + case Underline: + return "underline" + case Blink: + return "blink" + case Reverse: + return "reverse" + case Strikethrough: + return "strikethrough" + case Conceal: + return "conceal" + default: + return "" + } +} + +func (s Style) enable() attribute { + switch s { + case Bold: + return 1 + case Faint: + return 2 + case Italic: + return 3 + case Underline: + return 4 + case Blink: + return 5 + case Reverse: + return 7 + case Conceal: + return 8 + case Strikethrough: + return 9 + default: + return unknownAttribute + } +} + +func (s Style) disable() attribute { + value := s.enable() + if value != unknownAttribute { + return value + 20 + } + return value +} diff --git a/vendor/github.com/juju/ansiterm/tabwriter.go b/vendor/github.com/juju/ansiterm/tabwriter.go new file mode 100644 index 0000000..1ff6faa --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter.go @@ -0,0 +1,64 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "io" + + "github.com/juju/ansiterm/tabwriter" +) + +// NewTabWriter returns a writer that is able to set colors and styels. +// The ansi escape codes are stripped for width calculations. +func NewTabWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { + return new(TabWriter).Init(output, minwidth, tabwidth, padding, padchar, flags) +} + +// TabWriter is a filter that inserts padding around tab-delimited +// columns in its input to align them in the output. +// +// It also setting of colors and styles over and above the standard +// tabwriter package. +type TabWriter struct { + Writer + tw tabwriter.Writer +} + +// Flush should be called after the last call to Write to ensure +// that any data buffered in the Writer is written to output. Any +// incomplete escape sequence at the end is considered +// complete for formatting purposes. +// +func (t *TabWriter) Flush() error { + return t.tw.Flush() +} + +// SetColumnAlignRight will mark a particular column as align right. +// This is reset on the next flush. +func (t *TabWriter) SetColumnAlignRight(column int) { + t.tw.SetColumnAlignRight(column) +} + +// A Writer must be initialized with a call to Init. The first parameter (output) +// specifies the filter output. The remaining parameters control the formatting: +// +// minwidth minimal cell width including any padding +// tabwidth width of tab characters (equivalent number of spaces) +// padding padding added to a cell before computing its width +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwidth, +// and cells are left-aligned independent of align_left +// (for correct-looking results, tabwidth must correspond +// to the tab width in the viewer displaying the result) +// flags formatting control +// +func (t *TabWriter) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *TabWriter { + writer, colorCapable := colorEnabledWriter(output) + t.Writer = Writer{ + Writer: t.tw.Init(writer, minwidth, tabwidth, padding, padchar, flags), + noColor: !colorCapable, + } + return t +} diff --git a/vendor/github.com/juju/ansiterm/tabwriter/LICENSE b/vendor/github.com/juju/ansiterm/tabwriter/LICENSE new file mode 100644 index 0000000..7448756 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go b/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go new file mode 100644 index 0000000..98949d0 --- /dev/null +++ b/vendor/github.com/juju/ansiterm/tabwriter/tabwriter.go @@ -0,0 +1,587 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is mostly a copy of the go standard library text/tabwriter. With +// the additional stripping of ansi control characters for width calculations. + +// Package tabwriter implements a write filter (tabwriter.Writer) that +// translates tabbed columns in input into properly aligned text. +// +// The package is using the Elastic Tabstops algorithm described at +// http://nickgravgaard.com/elastictabstops/index.html. +// +package tabwriter + +import ( + "bytes" + "io" + "unicode/utf8" + + "github.com/lunixbochs/vtclean" +) + +// ---------------------------------------------------------------------------- +// Filter implementation + +// A cell represents a segment of text terminated by tabs or line breaks. +// The text itself is stored in a separate buffer; cell only describes the +// segment's size in bytes, its width in runes, and whether it's an htab +// ('\t') terminated cell. +// +type cell struct { + size int // cell size in bytes + width int // cell width in runes + htab bool // true if the cell is terminated by an htab ('\t') +} + +// A Writer is a filter that inserts padding around tab-delimited +// columns in its input to align them in the output. +// +// The Writer treats incoming bytes as UTF-8 encoded text consisting +// of cells terminated by (horizontal or vertical) tabs or line +// breaks (newline or formfeed characters). Cells in adjacent lines +// constitute a column. The Writer inserts padding as needed to +// make all cells in a column have the same width, effectively +// aligning the columns. It assumes that all characters have the +// same width except for tabs for which a tabwidth must be specified. +// Note that cells are tab-terminated, not tab-separated: trailing +// non-tab text at the end of a line does not form a column cell. +// +// The Writer assumes that all Unicode code points have the same width; +// this may not be true in some fonts. +// +// If DiscardEmptyColumns is set, empty columns that are terminated +// entirely by vertical (or "soft") tabs are discarded. Columns +// terminated by horizontal (or "hard") tabs are not affected by +// this flag. +// +// If a Writer is configured to filter HTML, HTML tags and entities +// are passed through. The widths of tags and entities are +// assumed to be zero (tags) and one (entities) for formatting purposes. +// +// A segment of text may be escaped by bracketing it with Escape +// characters. The tabwriter passes escaped text segments through +// unchanged. In particular, it does not interpret any tabs or line +// breaks within the segment. If the StripEscape flag is set, the +// Escape characters are stripped from the output; otherwise they +// are passed through as well. For the purpose of formatting, the +// width of the escaped text is always computed excluding the Escape +// characters. +// +// The formfeed character ('\f') acts like a newline but it also +// terminates all columns in the current line (effectively calling +// Flush). Cells in the next line start new columns. Unless found +// inside an HTML tag or inside an escaped text segment, formfeed +// characters appear as newlines in the output. +// +// The Writer must buffer input internally, because proper spacing +// of one line may depend on the cells in future lines. Clients must +// call Flush when done calling Write. +// +type Writer struct { + // configuration + output io.Writer + minwidth int + tabwidth int + padding int + padbytes [8]byte + flags uint + + // current state + buf bytes.Buffer // collected text excluding tabs or line breaks + pos int // buffer position up to which cell.width of incomplete cell has been computed + cell cell // current incomplete cell; cell.width is up to buf[pos] excluding ignored sections + endChar byte // terminating char of escaped sequence (Escape for escapes, '>', ';' for HTML tags/entities, or 0) + lines [][]cell // list of lines; each line is a list of cells + widths []int // list of column widths in runes - re-used during formatting + alignment map[int]uint // column alignment +} + +func (b *Writer) addLine() { b.lines = append(b.lines, []cell{}) } + +// Reset the current state. +func (b *Writer) reset() { + b.buf.Reset() + b.pos = 0 + b.cell = cell{} + b.endChar = 0 + b.lines = b.lines[0:0] + b.widths = b.widths[0:0] + b.alignment = make(map[int]uint) + b.addLine() +} + +// Internal representation (current state): +// +// - all text written is appended to buf; tabs and line breaks are stripped away +// - at any given time there is a (possibly empty) incomplete cell at the end +// (the cell starts after a tab or line break) +// - cell.size is the number of bytes belonging to the cell so far +// - cell.width is text width in runes of that cell from the start of the cell to +// position pos; html tags and entities are excluded from this width if html +// filtering is enabled +// - the sizes and widths of processed text are kept in the lines list +// which contains a list of cells for each line +// - the widths list is a temporary list with current widths used during +// formatting; it is kept in Writer because it's re-used +// +// |<---------- size ---------->| +// | | +// |<- width ->|<- ignored ->| | +// | | | | +// [---processed---tab------------......] +// ^ ^ ^ +// | | | +// buf start of incomplete cell pos + +// Formatting can be controlled with these flags. +const ( + // Ignore html tags and treat entities (starting with '&' + // and ending in ';') as single characters (width = 1). + FilterHTML uint = 1 << iota + + // Strip Escape characters bracketing escaped text segments + // instead of passing them through unchanged with the text. + StripEscape + + // Force right-alignment of cell content. + // Default is left-alignment. + AlignRight + + // Handle empty columns as if they were not present in + // the input in the first place. + DiscardEmptyColumns + + // Always use tabs for indentation columns (i.e., padding of + // leading empty cells on the left) independent of padchar. + TabIndent + + // Print a vertical bar ('|') between columns (after formatting). + // Discarded columns appear as zero-width columns ("||"). + Debug +) + +// A Writer must be initialized with a call to Init. The first parameter (output) +// specifies the filter output. The remaining parameters control the formatting: +// +// minwidth minimal cell width including any padding +// tabwidth width of tab characters (equivalent number of spaces) +// padding padding added to a cell before computing its width +// padchar ASCII char used for padding +// if padchar == '\t', the Writer will assume that the +// width of a '\t' in the formatted output is tabwidth, +// and cells are left-aligned independent of align_left +// (for correct-looking results, tabwidth must correspond +// to the tab width in the viewer displaying the result) +// flags formatting control +// +func (b *Writer) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer { + if minwidth < 0 || tabwidth < 0 || padding < 0 { + panic("negative minwidth, tabwidth, or padding") + } + b.output = output + b.minwidth = minwidth + b.tabwidth = tabwidth + b.padding = padding + for i := range b.padbytes { + b.padbytes[i] = padchar + } + if padchar == '\t' { + // tab padding enforces left-alignment + flags &^= AlignRight + } + b.flags = flags + + b.reset() + + return b +} + +// debugging support (keep code around) +func (b *Writer) dump() { + pos := 0 + for i, line := range b.lines { + print("(", i, ") ") + for _, c := range line { + print("[", string(b.buf.Bytes()[pos:pos+c.size]), "]") + pos += c.size + } + print("\n") + } + print("\n") +} + +// local error wrapper so we can distinguish errors we want to return +// as errors from genuine panics (which we don't want to return as errors) +type osError struct { + err error +} + +func (b *Writer) write0(buf []byte) { + n, err := b.output.Write(buf) + if n != len(buf) && err == nil { + err = io.ErrShortWrite + } + if err != nil { + panic(osError{err}) + } +} + +func (b *Writer) writeN(src []byte, n int) { + for n > len(src) { + b.write0(src) + n -= len(src) + } + b.write0(src[0:n]) +} + +var ( + newline = []byte{'\n'} + tabs = []byte("\t\t\t\t\t\t\t\t") +) + +func (b *Writer) writePadding(textw, cellw int, useTabs bool) { + if b.padbytes[0] == '\t' || useTabs { + // padding is done with tabs + if b.tabwidth == 0 { + return // tabs have no width - can't do any padding + } + // make cellw the smallest multiple of b.tabwidth + cellw = (cellw + b.tabwidth - 1) / b.tabwidth * b.tabwidth + n := cellw - textw // amount of padding + if n < 0 { + panic("internal error") + } + b.writeN(tabs, (n+b.tabwidth-1)/b.tabwidth) + return + } + + // padding is done with non-tab characters + b.writeN(b.padbytes[0:], cellw-textw) +} + +var vbar = []byte{'|'} + +func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) { + pos = pos0 + for i := line0; i < line1; i++ { + line := b.lines[i] + + // if TabIndent is set, use tabs to pad leading empty cells + useTabs := b.flags&TabIndent != 0 + + for j, c := range line { + if j > 0 && b.flags&Debug != 0 { + // indicate column break + b.write0(vbar) + } + + if c.size == 0 { + // empty cell + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], useTabs) + } + } else { + // non-empty cell + useTabs = false + alignColumnRight := b.alignment[j] == AlignRight + if (b.flags&AlignRight == 0) && !alignColumnRight { // align left + b.write0(b.buf.Bytes()[pos : pos+c.size]) + pos += c.size + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], false) + } + } else if alignColumnRight && j < len(b.widths) { + // just this column + internalSize := b.widths[j] - b.padding + if j < len(b.widths) { + b.writePadding(c.width, internalSize, false) + } + b.write0(b.buf.Bytes()[pos : pos+c.size]) + if b.padding > 0 { + b.writePadding(0, b.padding, false) + } + pos += c.size + } else { // align right + if j < len(b.widths) { + b.writePadding(c.width, b.widths[j], false) + } + b.write0(b.buf.Bytes()[pos : pos+c.size]) + pos += c.size + } + } + } + + if i+1 == len(b.lines) { + // last buffered line - we don't have a newline, so just write + // any outstanding buffered data + b.write0(b.buf.Bytes()[pos : pos+b.cell.size]) + pos += b.cell.size + } else { + // not the last line - write newline + b.write0(newline) + } + } + return +} + +// Format the text between line0 and line1 (excluding line1); pos +// is the buffer position corresponding to the beginning of line0. +// Returns the buffer position corresponding to the beginning of +// line1 and an error, if any. +// +func (b *Writer) format(pos0 int, line0, line1 int) (pos int) { + pos = pos0 + column := len(b.widths) + for this := line0; this < line1; this++ { + line := b.lines[this] + + if column < len(line)-1 { + // cell exists in this column => this line + // has more cells than the previous line + // (the last cell per line is ignored because cells are + // tab-terminated; the last cell per line describes the + // text before the newline/formfeed and does not belong + // to a column) + + // print unprinted lines until beginning of block + pos = b.writeLines(pos, line0, this) + line0 = this + + // column block begin + width := b.minwidth // minimal column width + discardable := true // true if all cells in this column are empty and "soft" + for ; this < line1; this++ { + line = b.lines[this] + if column < len(line)-1 { + // cell exists in this column + c := line[column] + // update width + if w := c.width + b.padding; w > width { + width = w + } + // update discardable + if c.width > 0 || c.htab { + discardable = false + } + } else { + break + } + } + // column block end + + // discard empty columns if necessary + if discardable && b.flags&DiscardEmptyColumns != 0 { + width = 0 + } + + // format and print all columns to the right of this column + // (we know the widths of this column and all columns to the left) + b.widths = append(b.widths, width) // push width + pos = b.format(pos, line0, this) + b.widths = b.widths[0 : len(b.widths)-1] // pop width + line0 = this + } + } + + // print unprinted lines until end + return b.writeLines(pos, line0, line1) +} + +// Append text to current cell. +func (b *Writer) append(text []byte) { + b.buf.Write(text) + b.cell.size += len(text) +} + +// Update the cell width. +func (b *Writer) updateWidth() { + // ---- Changes here ----- + newChars := b.buf.Bytes()[b.pos:b.buf.Len()] + cleaned := vtclean.Clean(string(newChars), false) // false to strip colors + b.cell.width += utf8.RuneCount([]byte(cleaned)) + // --- end of changes ---- + b.pos = b.buf.Len() +} + +// To escape a text segment, bracket it with Escape characters. +// For instance, the tab in this string "Ignore this tab: \xff\t\xff" +// does not terminate a cell and constitutes a single character of +// width one for formatting purposes. +// +// The value 0xff was chosen because it cannot appear in a valid UTF-8 sequence. +// +const Escape = '\xff' + +// Start escaped mode. +func (b *Writer) startEscape(ch byte) { + switch ch { + case Escape: + b.endChar = Escape + case '<': + b.endChar = '>' + case '&': + b.endChar = ';' + } +} + +// Terminate escaped mode. If the escaped text was an HTML tag, its width +// is assumed to be zero for formatting purposes; if it was an HTML entity, +// its width is assumed to be one. In all other cases, the width is the +// unicode width of the text. +// +func (b *Writer) endEscape() { + switch b.endChar { + case Escape: + b.updateWidth() + if b.flags&StripEscape == 0 { + b.cell.width -= 2 // don't count the Escape chars + } + case '>': // tag of zero width + case ';': + b.cell.width++ // entity, count as one rune + } + b.pos = b.buf.Len() + b.endChar = 0 +} + +// Terminate the current cell by adding it to the list of cells of the +// current line. Returns the number of cells in that line. +// +func (b *Writer) terminateCell(htab bool) int { + b.cell.htab = htab + line := &b.lines[len(b.lines)-1] + *line = append(*line, b.cell) + b.cell = cell{} + return len(*line) +} + +func handlePanic(err *error, op string) { + if e := recover(); e != nil { + if nerr, ok := e.(osError); ok { + *err = nerr.err + return + } + panic("tabwriter: panic during " + op) + } +} + +// Flush should be called after the last call to Write to ensure +// that any data buffered in the Writer is written to output. Any +// incomplete escape sequence at the end is considered +// complete for formatting purposes. +// +func (b *Writer) Flush() (err error) { + defer b.reset() // even in the presence of errors + defer handlePanic(&err, "Flush") + + // add current cell if not empty + if b.cell.size > 0 { + if b.endChar != 0 { + // inside escape - terminate it even if incomplete + b.endEscape() + } + b.terminateCell(false) + } + + // format contents of buffer + b.format(0, 0, len(b.lines)) + + return +} + +var hbar = []byte("---\n") + +// SetColumnAlignRight will mark a particular column as align right. +// This is reset on the next flush. +func (b *Writer) SetColumnAlignRight(column int) { + b.alignment[column] = AlignRight +} + +// Write writes buf to the writer b. +// The only errors returned are ones encountered +// while writing to the underlying output stream. +// +func (b *Writer) Write(buf []byte) (n int, err error) { + defer handlePanic(&err, "Write") + + // split text into cells + n = 0 + for i, ch := range buf { + if b.endChar == 0 { + // outside escape + switch ch { + case '\t', '\v', '\n', '\f': + // end of cell + b.append(buf[n:i]) + b.updateWidth() + n = i + 1 // ch consumed + ncells := b.terminateCell(ch == '\t') + if ch == '\n' || ch == '\f' { + // terminate line + b.addLine() + if ch == '\f' || ncells == 1 { + // A '\f' always forces a flush. Otherwise, if the previous + // line has only one cell which does not have an impact on + // the formatting of the following lines (the last cell per + // line is ignored by format()), thus we can flush the + // Writer contents. + if err = b.Flush(); err != nil { + return + } + if ch == '\f' && b.flags&Debug != 0 { + // indicate section break + b.write0(hbar) + } + } + } + + case Escape: + // start of escaped sequence + b.append(buf[n:i]) + b.updateWidth() + n = i + if b.flags&StripEscape != 0 { + n++ // strip Escape + } + b.startEscape(Escape) + + case '<', '&': + // possibly an html tag/entity + if b.flags&FilterHTML != 0 { + // begin of tag/entity + b.append(buf[n:i]) + b.updateWidth() + n = i + b.startEscape(ch) + } + } + + } else { + // inside escape + if ch == b.endChar { + // end of tag/entity + j := i + 1 + if ch == Escape && b.flags&StripEscape != 0 { + j = i // strip Escape + } + b.append(buf[n:j]) + n = i + 1 // ch consumed + b.endEscape() + } + } + } + + // append leftover text + b.append(buf[n:]) + n = len(buf) + return +} + +// NewWriter allocates and initializes a new tabwriter.Writer. +// The parameters are the same as for the Init function. +// +func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer { + return new(Writer).Init(output, minwidth, tabwidth, padding, padchar, flags) +} diff --git a/vendor/github.com/juju/ansiterm/terminal.go b/vendor/github.com/juju/ansiterm/terminal.go new file mode 100644 index 0000000..96fd11c --- /dev/null +++ b/vendor/github.com/juju/ansiterm/terminal.go @@ -0,0 +1,32 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "io" + "os" + + "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" +) + +// colorEnabledWriter returns a writer that can handle the ansi color codes +// and true if the writer passed in is a terminal capable of color. If the +// TERM environment variable is set to "dumb", the terminal is not considered +// color capable. +func colorEnabledWriter(w io.Writer) (io.Writer, bool) { + f, ok := w.(*os.File) + if !ok { + return w, false + } + // Check the TERM environment variable specifically + // to check for "dumb" terminals. + if os.Getenv("TERM") == "dumb" { + return w, false + } + if !isatty.IsTerminal(f.Fd()) { + return w, false + } + return colorable.NewColorable(f), true +} diff --git a/vendor/github.com/juju/ansiterm/writer.go b/vendor/github.com/juju/ansiterm/writer.go new file mode 100644 index 0000000..32437bb --- /dev/null +++ b/vendor/github.com/juju/ansiterm/writer.go @@ -0,0 +1,74 @@ +// Copyright 2016 Canonical Ltd. +// Licensed under the LGPLv3, see LICENCE file for details. + +package ansiterm + +import ( + "fmt" + "io" +) + +// Writer allows colors and styles to be specified. If the io.Writer +// is not a terminal capable of color, all attempts to set colors or +// styles are no-ops. +type Writer struct { + io.Writer + + noColor bool +} + +// NewWriter returns a Writer that allows the caller to specify colors and +// styles. If the io.Writer is not a terminal capable of color, all attempts +// to set colors or styles are no-ops. +func NewWriter(w io.Writer) *Writer { + writer, colorCapable := colorEnabledWriter(w) + return &Writer{ + Writer: writer, + noColor: !colorCapable, + } +} + +// SetColorCapable forces the writer to either write the ANSI escape color +// if capable is true, or to not write them if capable is false. +func (w *Writer) SetColorCapable(capable bool) { + w.noColor = !capable +} + +// SetForeground sets the foreground color. +func (w *Writer) SetForeground(c Color) { + w.writeSGR(c.foreground()) +} + +// SetBackground sets the background color. +func (w *Writer) SetBackground(c Color) { + w.writeSGR(c.background()) +} + +// SetStyle sets the text style. +func (w *Writer) SetStyle(s Style) { + w.writeSGR(s.enable()) +} + +// ClearStyle clears the text style. +func (w *Writer) ClearStyle(s Style) { + w.writeSGR(s.disable()) +} + +// Reset returns the default foreground and background colors with no styles. +func (w *Writer) Reset() { + w.writeSGR(reset) +} + +type sgr interface { + // sgr returns the combined escape sequence for the Select Graphic Rendition. + sgr() string +} + +// writeSGR takes the appropriate integer SGR parameters +// and writes out the ANIS escape code. +func (w *Writer) writeSGR(value sgr) { + if w.noColor { + return + } + fmt.Fprint(w, value.sgr()) +} diff --git a/vendor/github.com/lunixbochs/vtclean/.travis.yml b/vendor/github.com/lunixbochs/vtclean/.travis.yml new file mode 100644 index 0000000..fc0a543 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/.travis.yml @@ -0,0 +1,9 @@ +language: go +sudo: false + +script: go test -v + +go: + - 1.5 + - 1.6 + - 1.7 diff --git a/vendor/github.com/lunixbochs/vtclean/LICENSE b/vendor/github.com/lunixbochs/vtclean/LICENSE new file mode 100644 index 0000000..42e8263 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Ryan Hileman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/lunixbochs/vtclean/README.md b/vendor/github.com/lunixbochs/vtclean/README.md new file mode 100644 index 0000000..99910a4 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/README.md @@ -0,0 +1,46 @@ +[![Build Status](https://travis-ci.org/lunixbochs/vtclean.svg?branch=master)](https://travis-ci.org/lunixbochs/vtclean) + +vtclean +---- + +Clean up raw terminal output by stripping escape sequences, optionally preserving color. + +Get it: `go get github.com/lunixbochs/vtclean/vtclean` + +API: + + import "github.com/lunixbochs/vtclean" + vtclean.Clean(line string, color bool) string + +Command line example: + + $ echo -e '\x1b[1;32mcolor example + color forced to stop at end of line + backspace is ba\b\bgood + no beeps!\x07\x07' | ./vtclean -color + + color example + color forced to stop at end of line + backspace is good + no beeps! + +Go example: + + package main + + import ( + "fmt" + "github.com/lunixbochs/vtclean" + ) + + func main() { + line := vtclean.Clean( + "\033[1;32mcolor, " + + "curs\033[Aor, " + + "backspace\b\b\b\b\b\b\b\b\b\b\b\033[K", false) + fmt.Println(line) + } + +Output: + + color, cursor diff --git a/vendor/github.com/lunixbochs/vtclean/io.go b/vendor/github.com/lunixbochs/vtclean/io.go new file mode 100644 index 0000000..31be007 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/io.go @@ -0,0 +1,93 @@ +package vtclean + +import ( + "bufio" + "bytes" + "io" +) + +type reader struct { + io.Reader + scanner *bufio.Scanner + buf []byte + + color bool +} + +func NewReader(r io.Reader, color bool) io.Reader { + return &reader{Reader: r, color: color} +} + +func (r *reader) scan() bool { + if r.scanner == nil { + r.scanner = bufio.NewScanner(r.Reader) + } + if len(r.buf) > 0 { + return true + } + if r.scanner.Scan() { + r.buf = []byte(Clean(r.scanner.Text(), r.color) + "\n") + return true + } + return false +} + +func (r *reader) fill(p []byte) int { + n := len(r.buf) + copy(p, r.buf) + if len(p) < len(r.buf) { + r.buf = r.buf[len(p):] + n = len(p) + } else { + r.buf = nil + } + return n +} + +func (r *reader) Read(p []byte) (int, error) { + n := r.fill(p) + if n < len(p) { + if !r.scan() { + if n == 0 { + return 0, io.EOF + } + return n, nil + } + n += r.fill(p[n:]) + } + return n, nil +} + +type writer struct { + io.Writer + buf []byte + color bool +} + +func NewWriter(w io.Writer, color bool) io.WriteCloser { + return &writer{Writer: w, color: color} +} + +func (w *writer) Write(p []byte) (int, error) { + buf := append(w.buf, p...) + lines := bytes.Split(buf, []byte("\n")) + if len(lines) > 0 { + last := len(lines) - 1 + w.buf = lines[last] + count := 0 + for _, line := range lines[:last] { + n, err := w.Writer.Write([]byte(Clean(string(line), w.color) + "\n")) + count += n + if err != nil { + return count, err + } + } + } + return len(p), nil +} + +func (w *writer) Close() error { + cl := Clean(string(w.buf), w.color) + _, err := w.Writer.Write([]byte(cl)) + return err +} diff --git a/vendor/github.com/lunixbochs/vtclean/line.go b/vendor/github.com/lunixbochs/vtclean/line.go new file mode 100644 index 0000000..66ee990 --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/line.go @@ -0,0 +1,113 @@ +package vtclean + +type char struct { + char byte + vt100 []byte +} + +func chars(p []byte) []char { + tmp := make([]char, len(p)) + for i, v := range p { + tmp[i].char = v + } + return tmp +} + +type lineEdit struct { + buf []char + pos, size int + vt100 []byte +} + +func newLineEdit(length int) *lineEdit { + return &lineEdit{buf: make([]char, length)} +} + +func (l *lineEdit) Vt100(p []byte) { + l.vt100 = p +} + +func (l *lineEdit) Move(x int) { + if x < 0 && l.pos <= -x { + l.pos = 0 + } else if x > 0 && l.pos+x > l.size { + l.pos = l.size + } else { + l.pos += x + } +} + +func (l *lineEdit) MoveAbs(x int) { + if x < l.size { + l.pos = x + } +} + +func (l *lineEdit) Write(p []byte) { + c := chars(p) + if len(c) > 0 { + c[0].vt100 = l.vt100 + l.vt100 = nil + } + if len(l.buf)-l.pos < len(c) { + l.buf = append(l.buf[:l.pos], c...) + } else { + copy(l.buf[l.pos:], c) + } + l.pos += len(c) + if l.pos > l.size { + l.size = l.pos + } +} + +func (l *lineEdit) Insert(p []byte) { + c := chars(p) + if len(c) > 0 { + c[0].vt100 = l.vt100 + l.vt100 = nil + } + l.size += len(c) + c = append(c, l.buf[l.pos:]...) + l.buf = append(l.buf[:l.pos], c...) +} + +func (l *lineEdit) Delete(n int) { + most := l.size - l.pos + if n > most { + n = most + } + copy(l.buf[l.pos:], l.buf[l.pos+n:]) + l.size -= n +} + +func (l *lineEdit) Clear() { + for i := 0; i < len(l.buf); i++ { + l.buf[i].char = ' ' + } +} +func (l *lineEdit) ClearLeft() { + for i := 0; i < l.pos+1; i++ { + l.buf[i].char = ' ' + } +} +func (l *lineEdit) ClearRight() { + l.size = l.pos +} + +func (l *lineEdit) Bytes() []byte { + length := 0 + buf := l.buf[:l.size] + for _, v := range buf { + length += 1 + len(v.vt100) + } + tmp := make([]byte, 0, length) + for _, v := range buf { + tmp = append(tmp, v.vt100...) + tmp = append(tmp, v.char) + } + return tmp +} + +func (l *lineEdit) String() string { + return string(l.Bytes()) +} diff --git a/vendor/github.com/lunixbochs/vtclean/vtclean.go b/vendor/github.com/lunixbochs/vtclean/vtclean.go new file mode 100644 index 0000000..64fe01f --- /dev/null +++ b/vendor/github.com/lunixbochs/vtclean/vtclean.go @@ -0,0 +1,95 @@ +package vtclean + +import ( + "bytes" + "regexp" + "strconv" +) + +// regex based on ECMA-48: +// 1. optional: +// one of [ or ] +// any amount of 0x30-0x3f +// any amount of 0x20-0x2f +// 3. exactly one 0x40-0x7e +var vt100re = regexp.MustCompile(`^\033([\[\]]([0-9:;<=>\?]*)([!"#$%&'()*+,\-./]*))?([@A-Z\[\]^_\x60a-z{|}~])`) +var vt100exc = regexp.MustCompile(`^\033(\[[^a-zA-Z0-9@\?]+|[\(\)]).`) + +// this is to handle the RGB escape generated by `tput initc 1 500 500 500` +var vt100long = regexp.MustCompile(`^\033](\d+);([^\033]+)\033\\`) + +func Clean(line string, color bool) string { + var edit = newLineEdit(len(line)) + lineb := []byte(line) + + hadColor := false + for i := 0; i < len(lineb); { + c := lineb[i] + switch c { + case '\r': + edit.MoveAbs(0) + case '\b': + edit.Move(-1) + case '\033': + // set terminal title + if bytes.HasPrefix(lineb[i:], []byte("\x1b]0;")) { + pos := bytes.Index(lineb[i:], []byte("\a")) + if pos != -1 { + i += pos + 1 + continue + } + } + if m := vt100long.Find(lineb[i:]); m != nil { + i += len(m) + } else if m := vt100exc.Find(lineb[i:]); m != nil { + i += len(m) + } else if m := vt100re.FindSubmatch(lineb[i:]); m != nil { + i += len(m[0]) + num := string(m[2]) + n, err := strconv.Atoi(num) + if err != nil || n > 10000 { + n = 1 + } + switch m[4][0] { + case 'm': + if color { + hadColor = true + edit.Vt100(m[0]) + } + case '@': + edit.Insert(bytes.Repeat([]byte{' '}, n)) + case 'G': + edit.MoveAbs(n) + case 'C': + edit.Move(n) + case 'D': + edit.Move(-n) + case 'P': + edit.Delete(n) + case 'K': + switch num { + case "", "0": + edit.ClearRight() + case "1": + edit.ClearLeft() + case "2": + edit.Clear() + } + } + } else { + i += 1 + } + continue + default: + if c == '\n' || c == '\t' || c >= ' ' { + edit.Write([]byte{c}) + } + } + i += 1 + } + out := edit.Bytes() + if hadColor { + out = append(out, []byte("\033[0m")...) + } + return string(out) +} diff --git a/vendor/github.com/manifoldco/promptui/.gitignore b/vendor/github.com/manifoldco/promptui/.gitignore new file mode 100644 index 0000000..8ee1778 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.gitignore @@ -0,0 +1,3 @@ +vendor +all-cover.txt +bin/ diff --git a/vendor/github.com/manifoldco/promptui/.golangci.yml b/vendor/github.com/manifoldco/promptui/.golangci.yml new file mode 100644 index 0000000..e232bfb --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.golangci.yml @@ -0,0 +1,26 @@ +run: + deadline: 5m + +issues: + # Disable maximums so we see all issues + max-per-linter: 0 + max-same-issues: 0 + + # golangci-lint ignores missing docstrings by default. That's no good! + exclude-use-default: false + +linters: + disable-all: true + enable: + - misspell + - golint + - goimports + - ineffassign + - deadcode + - gofmt + - govet + - structcheck + - unconvert + - megacheck + - typecheck + - varcheck diff --git a/vendor/github.com/manifoldco/promptui/.travis.yml b/vendor/github.com/manifoldco/promptui/.travis.yml new file mode 100644 index 0000000..01c16c4 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/.travis.yml @@ -0,0 +1,14 @@ +dist: bionic +language: go + +go: + - "1.12.x" + - "1.13.x" + +branches: + only: + - master + +after_success: + # only report coverage for go-version 1.11 + - if [[ $TRAVIS_GO_VERSION =~ ^1\.11 ]] ; then bash <(curl -s https://codecov.io/bash) -f all-cover.txt; fi diff --git a/vendor/github.com/manifoldco/promptui/CHANGELOG.md b/vendor/github.com/manifoldco/promptui/CHANGELOG.md new file mode 100644 index 0000000..563e9d0 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/CHANGELOG.md @@ -0,0 +1,123 @@ +# CHANGELOG + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## Unreleased + +## [0.8.0] - 2020-09-28 + +### Added + +- Support ctrl-h for backspace +- Allow hiding entered data after submit +- Allow masking input with an empty rune to hide input length + +### Fixed + +- Fix echo of cursor after input is finished +- Better support for keycodes on Windows + + +## [0.7.0] - 2020-01-11 + +### Added + +- Add support for configurable Stdin/Stdout on Prompt +- Add support for setting initial cursor position +- Switch to golangci-lint for linting + +### Removed + +- Removed support for Go 1.11 + +### Fixed + +- Reduce tool-based deps, hopefully fixing any install issues + +## [0.6.0] - 2019-11-29 + +### Added + +- Support configurable stdin + +### Fixed + +- Correct the dep on go-i18n + +## [0.5.0] - 2019-11-29 + +### Added + +- Now building and testing on go 1.11, go 1.12, and go 1.13 + +### Removed + +- Removed support for Go versions that don't include modules. + +## [0.4.0] - 2019-02-19 + +### Added + +- The text displayed when an item was successfully selected can be hidden + +## [0.3.2] - 2018-11-26 + +### Added + +- Support Go modules + +### Fixed + +- Fix typos in PromptTemplates documentation + +## [0.3.1] - 2018-07-26 + +### Added + +- Improved documentation for GoDoc +- Navigation keys information for Windows + +### Fixed + +- `success` template was not properly displayed after a successful prompt. + +## [0.3.0] - 2018-05-22 + +### Added + +- Background colors codes and template helpers +- `AllowEdit` for prompt to prevent deletion of the default value by any key +- Added `StartInSearchMode` to allow starting the prompt in search mode + +### Fixed + +- `` key press on Windows +- `juju/ansiterm` dependency +- `chzyer/readline#136` new api with ReadCloser +- Deleting UTF-8 characters sequence + +## [0.2.1] - 2017-11-30 + +### Fixed + +- `SelectWithAdd` panicking on `.Run` due to lack of keys setup +- Backspace key on Windows + +## [0.2.0] - 2017-11-16 + +### Added + +- `Select` items can now be searched + +## [0.1.0] - 2017-11-02 + +### Added + +- extract `promptui` from [torus](https://github.com/manifoldco/torus-cli) as a + standalone lib. +- `promptui.Prompt` provides a single input line to capture user information. +- `promptui.Select` provides a list of options to choose from. Users can + navigate through the list either one item at time or by pagination diff --git a/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md b/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..cc58cce --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/CODE_OF_CONDUCT.md @@ -0,0 +1,73 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, +body size, disability, ethnicity, gender identity and expression, level of +experience, nationality, personal appearance, race, religion, or sexual +identity and orientation. + +## Our Standards + +Examples of behaviour that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behaviour by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behaviour and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behaviour. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviours that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an +appointed representative at an online or offline event. Representation of a +project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at +[hello@manifold.co](mailto:hello@manifold.co). All complaints will be reviewed +and investigated and will result in a response that is deemed necessary and +appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, +available at +[http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4). diff --git a/vendor/github.com/manifoldco/promptui/LICENSE.md b/vendor/github.com/manifoldco/promptui/LICENSE.md new file mode 100644 index 0000000..3ae687b --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/LICENSE.md @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Arigato Machine Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/manifoldco/promptui/Makefile b/vendor/github.com/manifoldco/promptui/Makefile new file mode 100644 index 0000000..078a561 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/Makefile @@ -0,0 +1,49 @@ +export GO111MODULE := on +export PATH := ./bin:$(PATH) + +ci: bootstrap lint cover +.PHONY: ci + +################################################# +# Bootstrapping for base golang package and tool deps +################################################# + +bootstrap: + curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.21.0 +.PHONY: bootstrap + +mod-update: + go get -u -m + go mod tidy + +mod-tidy: + go mod tidy + +.PHONY: $(CMD_PKGS) +.PHONY: mod-update mod-tidy + +################################################# +# Test and linting +################################################# +# Run all the linters +lint: + bin/golangci-lint run ./... +.PHONY: lint + +test: + CGO_ENABLED=0 go test $$(go list ./... | grep -v generated) +.PHONY: test + +COVER_TEST_PKGS:=$(shell find . -type f -name '*_test.go' | rev | cut -d "/" -f 2- | rev | grep -v generated | sort -u) +$(COVER_TEST_PKGS:=-cover): %-cover: all-cover.txt + @CGO_ENABLED=0 go test -v -coverprofile=$@.out -covermode=atomic ./$* + @if [ -f $@.out ]; then \ + grep -v "mode: atomic" < $@.out >> all-cover.txt; \ + rm $@.out; \ + fi + +all-cover.txt: + echo "mode: atomic" > all-cover.txt + +cover: all-cover.txt $(COVER_TEST_PKGS:=-cover) +.PHONY: cover all-cover.txt diff --git a/vendor/github.com/manifoldco/promptui/README.md b/vendor/github.com/manifoldco/promptui/README.md new file mode 100644 index 0000000..4fd4dc6 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/README.md @@ -0,0 +1,107 @@ +# promptui + +Interactive prompt for command-line applications. + +We built Promptui because we wanted to make it easy and fun to explore cloud +services with [manifold cli](https://github.com/manifoldco/manifold-cli). + +[Code of Conduct](./CODE_OF_CONDUCT.md) | +[Contribution Guidelines](./.github/CONTRIBUTING.md) + +[![GitHub release](https://img.shields.io/github/tag/manifoldco/promptui.svg?label=latest)](https://github.com/manifoldco/promptui/releases) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/manifoldco/promptui) +[![Travis](https://img.shields.io/travis/manifoldco/promptui/master.svg)](https://travis-ci.org/manifoldco/promptui) +[![Go Report Card](https://goreportcard.com/badge/github.com/manifoldco/promptui)](https://goreportcard.com/report/github.com/manifoldco/promptui) +[![License](https://img.shields.io/badge/license-BSD-blue.svg)](./LICENSE.md) + +## Overview + +![promptui](https://media.giphy.com/media/xUNda0Ngb5qsogLsBi/giphy.gif) + +Promptui is a library providing a simple interface to create command-line +prompts for go. It can be easily integrated into +[spf13/cobra](https://github.com/spf13/cobra), +[urfave/cli](https://github.com/urfave/cli) or any cli go application. + +Promptui has two main input modes: + +- `Prompt` provides a single line for user input. Prompt supports + optional live validation, confirmation and masking the input. + +- `Select` provides a list of options to choose from. Select supports + pagination, search, detailed view and custom templates. + +For a full list of options check [GoDoc](https://godoc.org/github.com/manifoldco/promptui). + +## Basic Usage + +### Prompt + +```go +package main + +import ( + "errors" + "fmt" + "strconv" + + "github.com/manifoldco/promptui" +) + +func main() { + validate := func(input string) error { + _, err := strconv.ParseFloat(input, 64) + if err != nil { + return errors.New("Invalid number") + } + return nil + } + + prompt := promptui.Prompt{ + Label: "Number", + Validate: validate, + } + + result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", result) +} +``` + +### Select + +```go +package main + +import ( + "fmt" + + "github.com/manifoldco/promptui" +) + +func main() { + prompt := promptui.Select{ + Label: "Select Day", + Items: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", + "Saturday", "Sunday"}, + } + + _, result, err := prompt.Run() + + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + fmt.Printf("You choose %q\n", result) +} +``` + +### More Examples + +See full list of [examples](https://github.com/manifoldco/promptui/tree/master/_examples) diff --git a/vendor/github.com/manifoldco/promptui/codes.go b/vendor/github.com/manifoldco/promptui/codes.go new file mode 100644 index 0000000..8138c40 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/codes.go @@ -0,0 +1,120 @@ +package promptui + +import ( + "fmt" + "strconv" + "strings" + "text/template" +) + +const esc = "\033[" + +type attribute int + +// The possible state of text inside the application, either Bold, faint, italic or underline. +// +// These constants are called through the use of the Styler function. +const ( + reset attribute = iota + + FGBold + FGFaint + FGItalic + FGUnderline +) + +// The possible colors of text inside the application. +// +// These constants are called through the use of the Styler function. +const ( + FGBlack attribute = iota + 30 + FGRed + FGGreen + FGYellow + FGBlue + FGMagenta + FGCyan + FGWhite +) + +// The possible background colors of text inside the application. +// +// These constants are called through the use of the Styler function. +const ( + BGBlack attribute = iota + 40 + BGRed + BGGreen + BGYellow + BGBlue + BGMagenta + BGCyan + BGWhite +) + +// ResetCode is the character code used to reset the terminal formatting +var ResetCode = fmt.Sprintf("%s%dm", esc, reset) + +const ( + hideCursor = esc + "?25l" + showCursor = esc + "?25h" + clearLine = esc + "2K" +) + +// FuncMap defines template helpers for the output. It can be extended as a regular map. +// +// The functions inside the map link the state, color and background colors strings detected in templates to a Styler +// function that applies the given style using the corresponding constant. +var FuncMap = template.FuncMap{ + "black": Styler(FGBlack), + "red": Styler(FGRed), + "green": Styler(FGGreen), + "yellow": Styler(FGYellow), + "blue": Styler(FGBlue), + "magenta": Styler(FGMagenta), + "cyan": Styler(FGCyan), + "white": Styler(FGWhite), + "bgBlack": Styler(BGBlack), + "bgRed": Styler(BGRed), + "bgGreen": Styler(BGGreen), + "bgYellow": Styler(BGYellow), + "bgBlue": Styler(BGBlue), + "bgMagenta": Styler(BGMagenta), + "bgCyan": Styler(BGCyan), + "bgWhite": Styler(BGWhite), + "bold": Styler(FGBold), + "faint": Styler(FGFaint), + "italic": Styler(FGItalic), + "underline": Styler(FGUnderline), +} + +func upLine(n uint) string { + return movementCode(n, 'A') +} + +func movementCode(n uint, code rune) string { + return esc + strconv.FormatUint(uint64(n), 10) + string(code) +} + +// Styler is a function that accepts multiple possible styling transforms from the state, +// color and background colors constants and transforms them into a templated string +// to apply those styles in the CLI. +// +// The returned styling function accepts a string that will be extended with +// the wrapping function's styling attributes. +func Styler(attrs ...attribute) func(interface{}) string { + attrstrs := make([]string, len(attrs)) + for i, v := range attrs { + attrstrs[i] = strconv.Itoa(int(v)) + } + + seq := strings.Join(attrstrs, ";") + + return func(v interface{}) string { + end := "" + s, ok := v.(string) + if !ok || !strings.HasSuffix(s, ResetCode) { + end = ResetCode + } + return fmt.Sprintf("%s%sm%v%s", esc, seq, v, end) + } +} diff --git a/vendor/github.com/manifoldco/promptui/cursor.go b/vendor/github.com/manifoldco/promptui/cursor.go new file mode 100644 index 0000000..7f0961b --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/cursor.go @@ -0,0 +1,232 @@ +package promptui + +import ( + "fmt" + "strings" +) + +// Pointer is A specific type that translates a given set of runes into a given +// set of runes pointed at by the cursor. +type Pointer func(to []rune) []rune + +func defaultCursor(ignored []rune) []rune { + return []rune("\u2588") +} + +func blockCursor(input []rune) []rune { + return []rune(fmt.Sprintf("\\e[7m%s\\e[0m", string(input))) +} + +func pipeCursor(input []rune) []rune { + marker := []rune("|") + out := []rune{} + out = append(out, marker...) + out = append(out, input...) + return out +} + +var ( + // DefaultCursor is a big square block character. Obscures whatever was + // input. + DefaultCursor Pointer = defaultCursor + // BlockCursor is a cursor which highlights a character by inverting colors + // on it. + BlockCursor Pointer = blockCursor + // PipeCursor is a pipe character "|" which appears before the input + // character. + PipeCursor Pointer = pipeCursor +) + +// Cursor tracks the state associated with the movable cursor +// The strategy is to keep the prompt, input pristine except for requested +// modifications. The insertion of the cursor happens during a `format` call +// and we read in new input via an `Update` call +type Cursor struct { + // shows where the user inserts/updates text + Cursor Pointer + // what the user entered, and what we will echo back to them, after + // insertion of the cursor and prefixing with the prompt + input []rune + // Put the cursor before this slice + Position int + erase bool +} + +// NewCursor create a new cursor, with the DefaultCursor, the specified input, +// and position at the end of the specified starting input. +func NewCursor(startinginput string, pointer Pointer, eraseDefault bool) Cursor { + if pointer == nil { + pointer = defaultCursor + } + cur := Cursor{Cursor: pointer, Position: len(startinginput), input: []rune(startinginput), erase: eraseDefault} + if eraseDefault { + cur.Start() + } else { + cur.End() + } + return cur +} + +func (c *Cursor) String() string { + return fmt.Sprintf( + "Cursor: %s, input %s, Position %d", + string(c.Cursor([]rune(""))), string(c.input), c.Position) +} + +// End is a convenience for c.Place(len(c.input)) so you don't have to know how I +// indexed. +func (c *Cursor) End() { + c.Place(len(c.input)) +} + +// Start is convenience for c.Place(0) so you don't have to know how I +// indexed. +func (c *Cursor) Start() { + c.Place(0) +} + +// ensures we are in bounds. +func (c *Cursor) correctPosition() { + if c.Position > len(c.input) { + c.Position = len(c.input) + } + + if c.Position < 0 { + c.Position = 0 + } +} + +// insert the cursor rune array into r before the provided index +func format(a []rune, c *Cursor) string { + i := c.Position + var b []rune + + out := make([]rune, 0) + if i < len(a) { + b = c.Cursor(a[i : i+1]) + out = append(out, a[:i]...) // does not include i + out = append(out, b...) // add the cursor + out = append(out, a[i+1:]...) // add the rest after i + } else { + b = c.Cursor([]rune{}) + out = append(out, a...) + out = append(out, b...) + } + return string(out) +} + +// Format renders the input with the Cursor appropriately positioned. +func (c *Cursor) Format() string { + r := c.input + // insert the cursor + return format(r, c) +} + +// FormatMask replaces all input runes with the mask rune. +func (c *Cursor) FormatMask(mask rune) string { + if mask == ' ' { + return format([]rune{}, c) + } + + r := make([]rune, len(c.input)) + for i := range r { + r[i] = mask + } + return format(r, c) +} + +// Update inserts newinput into the input []rune in the appropriate place. +// The cursor is moved to the end of the inputed sequence. +func (c *Cursor) Update(newinput string) { + a := c.input + b := []rune(newinput) + i := c.Position + a = append(a[:i], append(b, a[i:]...)...) + c.input = a + c.Move(len(b)) +} + +// Get returns a copy of the input +func (c *Cursor) Get() string { + return string(c.input) +} + +// GetMask returns a mask string with length equal to the input +func (c *Cursor) GetMask(mask rune) string { + return strings.Repeat(string(mask), len(c.input)) +} + +// Replace replaces the previous input with whatever is specified, and moves the +// cursor to the end position +func (c *Cursor) Replace(input string) { + c.input = []rune(input) + c.End() +} + +// Place moves the cursor to the absolute array index specified by position +func (c *Cursor) Place(position int) { + c.Position = position + c.correctPosition() +} + +// Move moves the cursor over in relative terms, by shift indices. +func (c *Cursor) Move(shift int) { + // delete the current cursor + c.Position = c.Position + shift + c.correctPosition() +} + +// Backspace removes the rune that precedes the cursor +// +// It handles being at the beginning or end of the row, and moves the cursor to +// the appropriate position. +func (c *Cursor) Backspace() { + a := c.input + i := c.Position + if i == 0 { + // Shrug + return + } + if i == len(a) { + c.input = a[:i-1] + } else { + c.input = append(a[:i-1], a[i:]...) + } + // now it's pointing to the i+1th element + c.Move(-1) +} + +// Listen is a readline Listener that updates internal cursor state appropriately. +func (c *Cursor) Listen(line []rune, pos int, key rune) ([]rune, int, bool) { + if line != nil { + // no matter what, update our internal representation. + c.Update(string(line)) + } + + switch key { + case 0: // empty + case KeyEnter: + return []rune(c.Get()), c.Position, false + case KeyBackspace, KeyCtrlH: + if c.erase { + c.erase = false + c.Replace("") + } + c.Backspace() + case KeyForward: + // the user wants to edit the default, despite how we set it up. Let + // them. + c.erase = false + c.Move(1) + case KeyBackward: + c.Move(-1) + default: + if c.erase { + c.erase = false + c.Replace("") + c.Update(string(key)) + } + } + + return []rune(c.Get()), c.Position, true +} diff --git a/vendor/github.com/manifoldco/promptui/go.mod b/vendor/github.com/manifoldco/promptui/go.mod new file mode 100644 index 0000000..760a44d --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/go.mod @@ -0,0 +1,16 @@ +module github.com/manifoldco/promptui + +go 1.12 + +require ( + github.com/chzyer/logex v1.1.10 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect + github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a + github.com/kr/pretty v0.1.0 // indirect + github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/vendor/github.com/manifoldco/promptui/go.sum b/vendor/github.com/manifoldco/promptui/go.sum new file mode 100644 index 0000000..be5f990 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/go.sum @@ -0,0 +1,23 @@ +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b h1:MQE+LT/ABUuuvEZ+YQAMSXindAdUh7slEmAkup74op4= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/vendor/github.com/manifoldco/promptui/keycodes.go b/vendor/github.com/manifoldco/promptui/keycodes.go new file mode 100644 index 0000000..ef5cd6f --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes.go @@ -0,0 +1,29 @@ +package promptui + +import "github.com/chzyer/readline" + +// These runes are used to identify the commands entered by the user in the command prompt. They map +// to specific actions of promptui in prompt mode and can be remapped if necessary. +var ( + // KeyEnter is the default key for submission/selection. + KeyEnter rune = readline.CharEnter + + // KeyCtrlH is the key for deleting input text. + KeyCtrlH rune = readline.CharCtrlH + + // KeyPrev is the default key to go up during selection. + KeyPrev rune = readline.CharPrev + KeyPrevDisplay = "↑" + + // KeyNext is the default key to go down during selection. + KeyNext rune = readline.CharNext + KeyNextDisplay = "↓" + + // KeyBackward is the default key to page up during selection. + KeyBackward rune = readline.CharBackward + KeyBackwardDisplay = "←" + + // KeyForward is the default key to page down during selection. + KeyForward rune = readline.CharForward + KeyForwardDisplay = "→" +) diff --git a/vendor/github.com/manifoldco/promptui/keycodes_other.go b/vendor/github.com/manifoldco/promptui/keycodes_other.go new file mode 100644 index 0000000..789feae --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes_other.go @@ -0,0 +1,10 @@ +// +build !windows + +package promptui + +import "github.com/chzyer/readline" + +var ( + // KeyBackspace is the default key for deleting input text. + KeyBackspace rune = readline.CharBackspace +) diff --git a/vendor/github.com/manifoldco/promptui/keycodes_windows.go b/vendor/github.com/manifoldco/promptui/keycodes_windows.go new file mode 100644 index 0000000..737d156 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/keycodes_windows.go @@ -0,0 +1,10 @@ +// +build windows + +package promptui + +// source: https://msdn.microsoft.com/en-us/library/aa243025(v=vs.60).aspx + +var ( + // KeyBackspace is the default key for deleting input text inside a command line prompt. + KeyBackspace rune = 8 +) diff --git a/vendor/github.com/manifoldco/promptui/list/list.go b/vendor/github.com/manifoldco/promptui/list/list.go new file mode 100644 index 0000000..c98a39c --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/list/list.go @@ -0,0 +1,237 @@ +package list + +import ( + "fmt" + "reflect" + "strings" +) + +// Searcher is a base function signature that is used inside select when activating the search mode. +// If defined, it is called on each items of the select and should return a boolean for whether or not +// the item fits the searched term. +type Searcher func(input string, index int) bool + +// NotFound is an index returned when no item was selected. This could +// happen due to a search without results. +const NotFound = -1 + +// List holds a collection of items that can be displayed with an N number of +// visible items. The list can be moved up, down by one item of time or an +// entire page (ie: visible size). It keeps track of the current selected item. +type List struct { + items []*interface{} + scope []*interface{} + cursor int // cursor holds the index of the current selected item + size int // size is the number of visible options + start int + Searcher Searcher +} + +// New creates and initializes a list of searchable items. The items attribute must be a slice type with a +// size greater than 0. Error will be returned if those two conditions are not met. +func New(items interface{}, size int) (*List, error) { + if size < 1 { + return nil, fmt.Errorf("list size %d must be greater than 0", size) + } + + if items == nil || reflect.TypeOf(items).Kind() != reflect.Slice { + return nil, fmt.Errorf("items %v is not a slice", items) + } + + slice := reflect.ValueOf(items) + values := make([]*interface{}, slice.Len()) + + for i := range values { + item := slice.Index(i).Interface() + values[i] = &item + } + + return &List{size: size, items: values, scope: values}, nil +} + +// Prev moves the visible list back one item. If the selected item is out of +// view, the new select item becomes the last visible item. If the list is +// already at the top, nothing happens. +func (l *List) Prev() { + if l.cursor > 0 { + l.cursor-- + } + + if l.start > l.cursor { + l.start = l.cursor + } +} + +// Search allows the list to be filtered by a given term. The list must +// implement the searcher function signature for this functionality to work. +func (l *List) Search(term string) { + term = strings.Trim(term, " ") + l.cursor = 0 + l.start = 0 + l.search(term) +} + +// CancelSearch stops the current search and returns the list to its +// original order. +func (l *List) CancelSearch() { + l.cursor = 0 + l.start = 0 + l.scope = l.items +} + +func (l *List) search(term string) { + var scope []*interface{} + + for i, item := range l.items { + if l.Searcher(term, i) { + scope = append(scope, item) + } + } + + l.scope = scope +} + +// Start returns the current render start position of the list. +func (l *List) Start() int { + return l.start +} + +// SetStart sets the current scroll position. Values out of bounds will be +// clamped. +func (l *List) SetStart(i int) { + if i < 0 { + i = 0 + } + if i > l.cursor { + l.start = l.cursor + } else { + l.start = i + } +} + +// SetCursor sets the position of the cursor in the list. Values out of bounds +// will be clamped. +func (l *List) SetCursor(i int) { + max := len(l.scope) - 1 + if i >= max { + i = max + } + if i < 0 { + i = 0 + } + l.cursor = i + + if l.start > l.cursor { + l.start = l.cursor + } else if l.start+l.size <= l.cursor { + l.start = l.cursor - l.size + 1 + } +} + +// Next moves the visible list forward one item. If the selected item is out of +// view, the new select item becomes the first visible item. If the list is +// already at the bottom, nothing happens. +func (l *List) Next() { + max := len(l.scope) - 1 + + if l.cursor < max { + l.cursor++ + } + + if l.start+l.size <= l.cursor { + l.start = l.cursor - l.size + 1 + } +} + +// PageUp moves the visible list backward by x items. Where x is the size of the +// visible items on the list. The selected item becomes the first visible item. +// If the list is already at the bottom, the selected item becomes the last +// visible item. +func (l *List) PageUp() { + start := l.start - l.size + if start < 0 { + l.start = 0 + } else { + l.start = start + } + + cursor := l.start + + if cursor < l.cursor { + l.cursor = cursor + } +} + +// PageDown moves the visible list forward by x items. Where x is the size of +// the visible items on the list. The selected item becomes the first visible +// item. +func (l *List) PageDown() { + start := l.start + l.size + max := len(l.scope) - l.size + + switch { + case len(l.scope) < l.size: + l.start = 0 + case start > max: + l.start = max + default: + l.start = start + } + + cursor := l.start + + if cursor == l.cursor { + l.cursor = len(l.scope) - 1 + } else if cursor > l.cursor { + l.cursor = cursor + } +} + +// CanPageDown returns whether a list can still PageDown(). +func (l *List) CanPageDown() bool { + max := len(l.scope) + return l.start+l.size < max +} + +// CanPageUp returns whether a list can still PageUp(). +func (l *List) CanPageUp() bool { + return l.start > 0 +} + +// Index returns the index of the item currently selected inside the searched list. If no item is selected, +// the NotFound (-1) index is returned. +func (l *List) Index() int { + selected := l.scope[l.cursor] + + for i, item := range l.items { + if item == selected { + return i + } + } + + return NotFound +} + +// Items returns a slice equal to the size of the list with the current visible +// items and the index of the active item in this list. +func (l *List) Items() ([]interface{}, int) { + var result []interface{} + max := len(l.scope) + end := l.start + l.size + + if end > max { + end = max + } + + active := NotFound + + for i, j := l.start, 0; i < end; i, j = i+1, j+1 { + if l.cursor == i { + active = j + } + + result = append(result, *l.scope[i]) + } + + return result, active +} diff --git a/vendor/github.com/manifoldco/promptui/prompt.go b/vendor/github.com/manifoldco/promptui/prompt.go new file mode 100644 index 0000000..8e35123 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/prompt.go @@ -0,0 +1,341 @@ +package promptui + +import ( + "fmt" + "io" + "strings" + "text/template" + + "github.com/chzyer/readline" + "github.com/manifoldco/promptui/screenbuf" +) + +// Prompt represents a single line text field input with options for validation and input masks. +type Prompt struct { + // Label is the value displayed on the command line prompt. + // + // The value for Label can be a simple string or a struct that will need to be accessed by dot notation + // inside the templates. For example, `{{ .Name }}` will display the name property of a struct. + Label interface{} + + // Default is the initial value for the prompt. This value will be displayed next to the prompt's label + // and the user will be able to view or change it depending on the options. + Default string + + // AllowEdit lets the user edit the default value. If false, any key press + // other than automatically clears the default value. + AllowEdit bool + + // Validate is an optional function that fill be used against the entered value in the prompt to validate it. + Validate ValidateFunc + + // Mask is an optional rune that sets which character to display instead of the entered characters. This + // allows hiding private information like passwords. + Mask rune + + // HideEntered sets whether to hide the text after the user has pressed enter. + HideEntered bool + + // Templates can be used to customize the prompt output. If nil is passed, the + // default templates are used. See the PromptTemplates docs for more info. + Templates *PromptTemplates + + // IsConfirm makes the prompt ask for a yes or no ([Y/N]) question rather than request an input. When set, + // most properties related to input will be ignored. + IsConfirm bool + + // IsVimMode enables vi-like movements (hjkl) and editing. + IsVimMode bool + + // the Pointer defines how to render the cursor. + Pointer Pointer + + Stdin io.ReadCloser + Stdout io.WriteCloser +} + +// PromptTemplates allow a prompt to be customized following stdlib +// text/template syntax. Custom state, colors and background color are available for use inside +// the templates and are documented inside the Variable section of the docs. +// +// Examples +// +// text/templates use a special notation to display programmable content. Using the double bracket notation, +// the value can be printed with specific helper functions. For example +// +// This displays the value given to the template as pure, unstylized text. +// '{{ . }}' +// +// This displays the value colored in cyan +// '{{ . | cyan }}' +// +// This displays the value colored in red with a cyan background-color +// '{{ . | red | cyan }}' +// +// See the doc of text/template for more info: https://golang.org/pkg/text/template/ +type PromptTemplates struct { + // Prompt is a text/template for the prompt label displayed on the left side of the prompt. + Prompt string + + // Prompt is a text/template for the prompt label when IsConfirm is set as true. + Confirm string + + // Valid is a text/template for the prompt label when the value entered is valid. + Valid string + + // Invalid is a text/template for the prompt label when the value entered is invalid. + Invalid string + + // Success is a text/template for the prompt label when the user has pressed entered and the value has been + // deemed valid by the validation function. The label will keep using this template even when the prompt ends + // inside the console. + Success string + + // Prompt is a text/template for the prompt label when the value is invalid due to an error triggered by + // the prompt's validation function. + ValidationError string + + // FuncMap is a map of helper functions that can be used inside of templates according to the text/template + // documentation. + // + // By default, FuncMap contains the color functions used to color the text in templates. If FuncMap + // is overridden, the colors functions must be added in the override from promptui.FuncMap to work. + FuncMap template.FuncMap + + prompt *template.Template + valid *template.Template + invalid *template.Template + validation *template.Template + success *template.Template +} + +// Run executes the prompt. Its displays the label and default value if any, asking the user to enter a value. +// Run will keep the prompt alive until it has been canceled from the command prompt or it has received a valid +// value. It will return the value and an error if any occurred during the prompt's execution. +func (p *Prompt) Run() (string, error) { + var err error + + err = p.prepareTemplates() + if err != nil { + return "", err + } + + c := &readline.Config{ + Stdin: p.Stdin, + Stdout: p.Stdout, + EnableMask: p.Mask != 0, + MaskRune: p.Mask, + HistoryLimit: -1, + VimMode: p.IsVimMode, + UniqueEditLine: true, + } + + err = c.Init() + if err != nil { + return "", err + } + + rl, err := readline.NewEx(c) + if err != nil { + return "", err + } + // we're taking over the cursor, so stop showing it. + rl.Write([]byte(hideCursor)) + sb := screenbuf.New(rl) + + validFn := func(x string) error { + return nil + } + if p.Validate != nil { + validFn = p.Validate + } + + var inputErr error + input := p.Default + if p.IsConfirm { + input = "" + } + eraseDefault := input != "" && !p.AllowEdit + cur := NewCursor(input, p.Pointer, eraseDefault) + + listen := func(input []rune, pos int, key rune) ([]rune, int, bool) { + _, _, keepOn := cur.Listen(input, pos, key) + err := validFn(cur.Get()) + var prompt []byte + + if err != nil { + prompt = render(p.Templates.invalid, p.Label) + } else { + prompt = render(p.Templates.valid, p.Label) + if p.IsConfirm { + prompt = render(p.Templates.prompt, p.Label) + } + } + + echo := cur.Format() + if p.Mask != 0 { + echo = cur.FormatMask(p.Mask) + } + + prompt = append(prompt, []byte(echo)...) + sb.Reset() + sb.Write(prompt) + if inputErr != nil { + validation := render(p.Templates.validation, inputErr) + sb.Write(validation) + inputErr = nil + } + sb.Flush() + return nil, 0, keepOn + } + + c.SetListener(listen) + + for { + _, err = rl.Readline() + inputErr = validFn(cur.Get()) + if inputErr == nil { + break + } + + if err != nil { + break + } + } + + if err != nil { + switch err { + case readline.ErrInterrupt: + err = ErrInterrupt + case io.EOF: + err = ErrEOF + } + if err.Error() == "Interrupt" { + err = ErrInterrupt + } + sb.Reset() + sb.WriteString("") + sb.Flush() + rl.Write([]byte(showCursor)) + rl.Close() + return "", err + } + + echo := cur.Get() + if p.Mask != 0 { + echo = cur.GetMask(p.Mask) + } + + prompt := render(p.Templates.success, p.Label) + prompt = append(prompt, []byte(echo)...) + + if p.IsConfirm { + lowerDefault := strings.ToLower(p.Default) + if strings.ToLower(cur.Get()) != "y" && (lowerDefault != "y" || (lowerDefault == "y" && cur.Get() != "")) { + prompt = render(p.Templates.invalid, p.Label) + err = ErrAbort + } + } + + if p.HideEntered { + clearScreen(sb) + } else { + sb.Reset() + sb.Write(prompt) + sb.Flush() + } + + rl.Write([]byte(showCursor)) + rl.Close() + + return cur.Get(), err +} + +func (p *Prompt) prepareTemplates() error { + tpls := p.Templates + if tpls == nil { + tpls = &PromptTemplates{} + } + + if tpls.FuncMap == nil { + tpls.FuncMap = FuncMap + } + + bold := Styler(FGBold) + + if p.IsConfirm { + if tpls.Confirm == "" { + confirm := "y/N" + if strings.ToLower(p.Default) == "y" { + confirm = "Y/n" + } + tpls.Confirm = fmt.Sprintf(`{{ "%s" | bold }} {{ . | bold }}? {{ "[%s]" | faint }} `, IconInitial, confirm) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Confirm) + if err != nil { + return err + } + + tpls.prompt = tpl + } else { + if tpls.Prompt == "" { + tpls.Prompt = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconInitial), bold(":")) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Prompt) + if err != nil { + return err + } + + tpls.prompt = tpl + } + + if tpls.Valid == "" { + tpls.Valid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconGood), bold(":")) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Valid) + if err != nil { + return err + } + + tpls.valid = tpl + + if tpls.Invalid == "" { + tpls.Invalid = fmt.Sprintf("%s {{ . | bold }}%s ", bold(IconBad), bold(":")) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Invalid) + if err != nil { + return err + } + + tpls.invalid = tpl + + if tpls.ValidationError == "" { + tpls.ValidationError = `{{ ">>" | red }} {{ . | red }}` + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.ValidationError) + if err != nil { + return err + } + + tpls.validation = tpl + + if tpls.Success == "" { + tpls.Success = fmt.Sprintf("{{ . | faint }}%s ", Styler(FGFaint)(":")) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Success) + if err != nil { + return err + } + + tpls.success = tpl + + p.Templates = tpls + + return nil +} diff --git a/vendor/github.com/manifoldco/promptui/promptui.go b/vendor/github.com/manifoldco/promptui/promptui.go new file mode 100644 index 0000000..6248e58 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/promptui.go @@ -0,0 +1,27 @@ +// Package promptui is a library providing a simple interface to create command-line prompts for go. +// It can be easily integrated into spf13/cobra, urfave/cli or any cli go application. +// +// promptui has two main input modes: +// +// Prompt provides a single line for user input. It supports optional live validation, +// confirmation and masking the input. +// +// Select provides a list of options to choose from. It supports pagination, search, +// detailed view and custom templates. +package promptui + +import "errors" + +// ErrEOF is the error returned from prompts when EOF is encountered. +var ErrEOF = errors.New("^D") + +// ErrInterrupt is the error returned from prompts when an interrupt (ctrl-c) is +// encountered. +var ErrInterrupt = errors.New("^C") + +// ErrAbort is the error returned when confirm prompts are supplied "n" +var ErrAbort = errors.New("") + +// ValidateFunc is a placeholder type for any validation functions that validates a given input. It should return +// a ValidationError if the input is not valid. +type ValidateFunc func(string) error diff --git a/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go b/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go new file mode 100644 index 0000000..8613900 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/screenbuf/screenbuf.go @@ -0,0 +1,151 @@ +package screenbuf + +import ( + "bytes" + "fmt" + "io" +) + +const esc = "\033[" + +var ( + clearLine = []byte(esc + "2K\r") + moveUp = []byte(esc + "1A") + moveDown = []byte(esc + "1B") +) + +// ScreenBuf is a convenient way to write to terminal screens. It creates, +// clears and, moves up or down lines as needed to write the output to the +// terminal using ANSI escape codes. +type ScreenBuf struct { + w io.Writer + buf *bytes.Buffer + reset bool + cursor int + height int +} + +// New creates and initializes a new ScreenBuf. +func New(w io.Writer) *ScreenBuf { + return &ScreenBuf{buf: &bytes.Buffer{}, w: w} +} + +// Reset truncates the underlining buffer and marks all its previous lines to be +// cleared during the next Write. +func (s *ScreenBuf) Reset() { + s.buf.Reset() + s.reset = true +} + +// Clear clears all previous lines and the output starts from the top. +func (s *ScreenBuf) Clear() error { + for i := 0; i < s.height; i++ { + _, err := s.buf.Write(moveUp) + if err != nil { + return err + } + _, err = s.buf.Write(clearLine) + if err != nil { + return err + } + } + s.cursor = 0 + s.height = 0 + s.reset = false + return nil +} + +// Write writes a single line to the underlining buffer. If the ScreenBuf was +// previously reset, all previous lines are cleared and the output starts from +// the top. Lines with \r or \n will cause an error since they can interfere with the +// terminal ability to move between lines. +func (s *ScreenBuf) Write(b []byte) (int, error) { + if bytes.ContainsAny(b, "\r\n") { + return 0, fmt.Errorf("%q should not contain either \\r or \\n", b) + } + + if s.reset { + if err := s.Clear(); err != nil { + return 0, err + } + } + + switch { + case s.cursor == s.height: + n, err := s.buf.Write(clearLine) + if err != nil { + return n, err + } + + n, err = s.buf.Write(b) + if err != nil { + return n, err + } + + _, err = s.buf.Write([]byte("\n")) + if err != nil { + return n, err + } + + s.height++ + s.cursor++ + return n, nil + case s.cursor < s.height: + n, err := s.buf.Write(clearLine) + if err != nil { + return n, err + } + n, err = s.buf.Write(b) + if err != nil { + return n, err + } + n, err = s.buf.Write(moveDown) + if err != nil { + return n, err + } + s.cursor++ + return n, nil + default: + return 0, fmt.Errorf("Invalid write cursor position (%d) exceeded line height: %d", s.cursor, s.height) + } +} + +// Flush writes any buffered data to the underlying io.Writer, ensuring that any pending data is displayed. +func (s *ScreenBuf) Flush() error { + for i := s.cursor; i < s.height; i++ { + if i < s.height { + _, err := s.buf.Write(clearLine) + if err != nil { + return err + } + } + _, err := s.buf.Write(moveDown) + if err != nil { + return err + } + } + + _, err := s.buf.WriteTo(s.w) + if err != nil { + return err + } + + s.buf.Reset() + + for i := 0; i < s.height; i++ { + _, err := s.buf.Write(moveUp) + if err != nil { + return err + } + } + + s.cursor = 0 + + return nil +} + +// WriteString is a convenient function to write a new line passing a string. +// Check ScreenBuf.Write() for a detailed explanation of the function behaviour. +func (s *ScreenBuf) WriteString(str string) (int, error) { + return s.Write([]byte(str)) +} diff --git a/vendor/github.com/manifoldco/promptui/select.go b/vendor/github.com/manifoldco/promptui/select.go new file mode 100644 index 0000000..19b9e0c --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/select.go @@ -0,0 +1,637 @@ +package promptui + +import ( + "bytes" + "fmt" + "io" + "os" + "text/template" + + "github.com/chzyer/readline" + "github.com/juju/ansiterm" + "github.com/manifoldco/promptui/list" + "github.com/manifoldco/promptui/screenbuf" +) + +// SelectedAdd is used internally inside SelectWithAdd when the add option is selected in select mode. +// Since -1 is not a possible selected index, this ensure that add mode is always unique inside +// SelectWithAdd's logic. +const SelectedAdd = -1 + +// Select represents a list of items used to enable selections, they can be used as search engines, menus +// or as a list of items in a cli based prompt. +type Select struct { + // Label is the text displayed on top of the list to direct input. The IconInitial value "?" will be + // appended automatically to the label so it does not need to be added. + // + // The value for Label can be a simple string or a struct that will need to be accessed by dot notation + // inside the templates. For example, `{{ .Name }}` will display the name property of a struct. + Label interface{} + + // Items are the items to display inside the list. It expect a slice of any kind of values, including strings. + // + // If using a slice of strings, promptui will use those strings directly into its base templates or the + // provided templates. If using any other type in the slice, it will attempt to transform it into a string + // before giving it to its templates. Custom templates will override this behavior if using the dot notation + // inside the templates. + // + // For example, `{{ .Name }}` will display the name property of a struct. + Items interface{} + + // Size is the number of items that should appear on the select before scrolling is necessary. Defaults to 5. + Size int + + // CursorPos is the initial position of the cursor. + CursorPos int + + // IsVimMode sets whether to use vim mode when using readline in the command prompt. Look at + // https://godoc.org/github.com/chzyer/readline#Config for more information on readline. + IsVimMode bool + + // HideHelp sets whether to hide help information. + HideHelp bool + + // HideSelected sets whether to hide the text displayed after an item is successfully selected. + HideSelected bool + + // Templates can be used to customize the select output. If nil is passed, the + // default templates are used. See the SelectTemplates docs for more info. + Templates *SelectTemplates + + // Keys is the set of keys used in select mode to control the command line interface. See the SelectKeys docs for + // more info. + Keys *SelectKeys + + // Searcher is a function that can be implemented to refine the base searching algorithm in selects. + // + // Search is a function that will receive the searched term and the item's index and should return a boolean + // for whether or not the terms are alike. It is unimplemented by default and search will not work unless + // it is implemented. + Searcher list.Searcher + + // StartInSearchMode sets whether or not the select mode should start in search mode or selection mode. + // For search mode to work, the Search property must be implemented. + StartInSearchMode bool + + list *list.List + + // A function that determines how to render the cursor + Pointer Pointer + + Stdin io.ReadCloser + Stdout io.WriteCloser +} + +// SelectKeys defines the available keys used by select mode to enable the user to move around the list +// and trigger search mode. See the Key struct docs for more information on keys. +type SelectKeys struct { + // Next is the key used to move to the next element inside the list. Defaults to down arrow key. + Next Key + + // Prev is the key used to move to the previous element inside the list. Defaults to up arrow key. + Prev Key + + // PageUp is the key used to jump back to the first element inside the list. Defaults to left arrow key. + PageUp Key + + // PageUp is the key used to jump forward to the last element inside the list. Defaults to right arrow key. + PageDown Key + + // Search is the key used to trigger the search mode for the list. Default to the "/" key. + Search Key +} + +// Key defines a keyboard code and a display representation for the help menu. +type Key struct { + // Code is a rune that will be used to compare against typed keys with readline. + // Check https://github.com/chzyer/readline for a list of codes + Code rune + + // Display is the string that will be displayed inside the help menu to help inform the user + // of which key to use on his keyboard for various functions. + Display string +} + +// SelectTemplates allow a select list to be customized following stdlib +// text/template syntax. Custom state, colors and background color are available for use inside +// the templates and are documented inside the Variable section of the docs. +// +// Examples +// +// text/templates use a special notation to display programmable content. Using the double bracket notation, +// the value can be printed with specific helper functions. For example +// +// This displays the value given to the template as pure, unstylized text. Structs are transformed to string +// with this notation. +// '{{ . }}' +// +// This displays the name property of the value colored in cyan +// '{{ .Name | cyan }}' +// +// This displays the label property of value colored in red with a cyan background-color +// '{{ .Label | red | cyan }}' +// +// See the doc of text/template for more info: https://golang.org/pkg/text/template/ +// +// Notes +// +// Setting any of these templates will remove the icons from the default templates. They must +// be added back in each of their specific templates. The styles.go constants contains the default icons. +type SelectTemplates struct { + // Label is a text/template for the main command line label. Defaults to printing the label as it with + // the IconInitial. + Label string + + // Active is a text/template for when an item is currently active within the list. + Active string + + // Inactive is a text/template for when an item is not currently active inside the list. This + // template is used for all items unless they are active or selected. + Inactive string + + // Selected is a text/template for when an item was successfully selected. + Selected string + + // Details is a text/template for when an item current active to show + // additional information. It can have multiple lines. + // + // Detail will always be displayed for the active element and thus can be used to display additional + // information on the element beyond its label. + // + // promptui will not trim spaces and tabs will be displayed if the template is indented. + Details string + + // Help is a text/template for displaying instructions at the top. By default + // it shows keys for movement and search. + Help string + + // FuncMap is a map of helper functions that can be used inside of templates according to the text/template + // documentation. + // + // By default, FuncMap contains the color functions used to color the text in templates. If FuncMap + // is overridden, the colors functions must be added in the override from promptui.FuncMap to work. + FuncMap template.FuncMap + + label *template.Template + active *template.Template + inactive *template.Template + selected *template.Template + details *template.Template + help *template.Template +} + +// SearchPrompt is the prompt displayed in search mode. +var SearchPrompt = "Search: " + +// Run executes the select list. It displays the label and the list of items, asking the user to chose any +// value within to list. Run will keep the prompt alive until it has been canceled from +// the command prompt or it has received a valid value. It will return the value and an error if any +// occurred during the select's execution. +func (s *Select) Run() (int, string, error) { + return s.RunCursorAt(s.CursorPos, 0) +} + +// RunCursorAt executes the select list, initializing the cursor to the given +// position. Invalid cursor positions will be clamped to valid values. It +// displays the label and the list of items, asking the user to chose any value +// within to list. Run will keep the prompt alive until it has been canceled +// from the command prompt or it has received a valid value. It will return +// the value and an error if any occurred during the select's execution. +func (s *Select) RunCursorAt(cursorPos, scroll int) (int, string, error) { + if s.Size == 0 { + s.Size = 5 + } + + l, err := list.New(s.Items, s.Size) + if err != nil { + return 0, "", err + } + l.Searcher = s.Searcher + + s.list = l + + s.setKeys() + + err = s.prepareTemplates() + if err != nil { + return 0, "", err + } + return s.innerRun(cursorPos, scroll, ' ') +} + +func (s *Select) innerRun(cursorPos, scroll int, top rune) (int, string, error) { + c := &readline.Config{ + Stdin: s.Stdin, + Stdout: s.Stdout, + } + err := c.Init() + if err != nil { + return 0, "", err + } + + c.Stdin = readline.NewCancelableStdin(c.Stdin) + + if s.IsVimMode { + c.VimMode = true + } + + c.HistoryLimit = -1 + c.UniqueEditLine = true + + rl, err := readline.NewEx(c) + if err != nil { + return 0, "", err + } + + rl.Write([]byte(hideCursor)) + sb := screenbuf.New(rl) + + cur := NewCursor("", s.Pointer, false) + + canSearch := s.Searcher != nil + searchMode := s.StartInSearchMode + s.list.SetCursor(cursorPos) + s.list.SetStart(scroll) + + c.SetListener(func(line []rune, pos int, key rune) ([]rune, int, bool) { + switch { + case key == KeyEnter: + return nil, 0, true + case key == s.Keys.Next.Code || (key == 'j' && !searchMode): + s.list.Next() + case key == s.Keys.Prev.Code || (key == 'k' && !searchMode): + s.list.Prev() + case key == s.Keys.Search.Code: + if !canSearch { + break + } + + if searchMode { + searchMode = false + cur.Replace("") + s.list.CancelSearch() + } else { + searchMode = true + } + case key == KeyBackspace || key == KeyCtrlH: + if !canSearch || !searchMode { + break + } + + cur.Backspace() + if len(cur.Get()) > 0 { + s.list.Search(cur.Get()) + } else { + s.list.CancelSearch() + } + case key == s.Keys.PageUp.Code || (key == 'h' && !searchMode): + s.list.PageUp() + case key == s.Keys.PageDown.Code || (key == 'l' && !searchMode): + s.list.PageDown() + default: + if canSearch && searchMode { + cur.Update(string(line)) + s.list.Search(cur.Get()) + } + } + + if searchMode { + header := SearchPrompt + cur.Format() + sb.WriteString(header) + } else if !s.HideHelp { + help := s.renderHelp(canSearch) + sb.Write(help) + } + + label := render(s.Templates.label, s.Label) + sb.Write(label) + + items, idx := s.list.Items() + last := len(items) - 1 + + for i, item := range items { + page := " " + + switch i { + case 0: + if s.list.CanPageUp() { + page = "↑" + } else { + page = string(top) + } + case last: + if s.list.CanPageDown() { + page = "↓" + } + } + + output := []byte(page + " ") + + if i == idx { + output = append(output, render(s.Templates.active, item)...) + } else { + output = append(output, render(s.Templates.inactive, item)...) + } + + sb.Write(output) + } + + if idx == list.NotFound { + sb.WriteString("") + sb.WriteString("No results") + } else { + active := items[idx] + + details := s.renderDetails(active) + for _, d := range details { + sb.Write(d) + } + } + + sb.Flush() + + return nil, 0, true + }) + + for { + _, err = rl.Readline() + + if err != nil { + switch { + case err == readline.ErrInterrupt, err.Error() == "Interrupt": + err = ErrInterrupt + case err == io.EOF: + err = ErrEOF + } + break + } + + _, idx := s.list.Items() + if idx != list.NotFound { + break + } + + } + + if err != nil { + if err.Error() == "Interrupt" { + err = ErrInterrupt + } + sb.Reset() + sb.WriteString("") + sb.Flush() + rl.Write([]byte(showCursor)) + rl.Close() + return 0, "", err + } + + items, idx := s.list.Items() + item := items[idx] + + if s.HideSelected { + clearScreen(sb) + } else { + sb.Reset() + sb.Write(render(s.Templates.selected, item)) + sb.Flush() + } + + rl.Write([]byte(showCursor)) + rl.Close() + + return s.list.Index(), fmt.Sprintf("%v", item), err +} + +// ScrollPosition returns the current scroll position. +func (s *Select) ScrollPosition() int { + return s.list.Start() +} + +func (s *Select) prepareTemplates() error { + tpls := s.Templates + if tpls == nil { + tpls = &SelectTemplates{} + } + + if tpls.FuncMap == nil { + tpls.FuncMap = FuncMap + } + + if tpls.Label == "" { + tpls.Label = fmt.Sprintf("%s {{.}}: ", IconInitial) + } + + tpl, err := template.New("").Funcs(tpls.FuncMap).Parse(tpls.Label) + if err != nil { + return err + } + + tpls.label = tpl + + if tpls.Active == "" { + tpls.Active = fmt.Sprintf("%s {{ . | underline }}", IconSelect) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Active) + if err != nil { + return err + } + + tpls.active = tpl + + if tpls.Inactive == "" { + tpls.Inactive = " {{.}}" + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Inactive) + if err != nil { + return err + } + + tpls.inactive = tpl + + if tpls.Selected == "" { + tpls.Selected = fmt.Sprintf(`{{ "%s" | green }} {{ . | faint }}`, IconGood) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Selected) + if err != nil { + return err + } + tpls.selected = tpl + + if tpls.Details != "" { + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Details) + if err != nil { + return err + } + + tpls.details = tpl + } + + if tpls.Help == "" { + tpls.Help = fmt.Sprintf(`{{ "Use the arrow keys to navigate:" | faint }} {{ .NextKey | faint }} ` + + `{{ .PrevKey | faint }} {{ .PageDownKey | faint }} {{ .PageUpKey | faint }} ` + + `{{ if .Search }} {{ "and" | faint }} {{ .SearchKey | faint }} {{ "toggles search" | faint }}{{ end }}`) + } + + tpl, err = template.New("").Funcs(tpls.FuncMap).Parse(tpls.Help) + if err != nil { + return err + } + + tpls.help = tpl + + s.Templates = tpls + + return nil +} + +// SelectWithAdd represents a list for selecting a single item inside a list of items with the possibility to +// add new items to the list. +type SelectWithAdd struct { + // Label is the text displayed on top of the list to direct input. The IconInitial value "?" will be + // appended automatically to the label so it does not need to be added. + Label string + + // Items are the items to display inside the list. Each item will be listed individually with the + // AddLabel as the first item of the list. + Items []string + + // AddLabel is the label used for the first item of the list that enables adding a new item. + // Selecting this item in the list displays the add item prompt using promptui/prompt. + AddLabel string + + // Validate is an optional function that fill be used against the entered value in the prompt to validate it. + // If the value is valid, it is returned to the callee to be added in the list. + Validate ValidateFunc + + // IsVimMode sets whether to use vim mode when using readline in the command prompt. Look at + // https://godoc.org/github.com/chzyer/readline#Config for more information on readline. + IsVimMode bool + + // a function that defines how to render the cursor + Pointer Pointer + + // HideHelp sets whether to hide help information. + HideHelp bool +} + +// Run executes the select list. Its displays the label and the list of items, asking the user to chose any +// value within to list or add his own. Run will keep the prompt alive until it has been canceled from +// the command prompt or it has received a valid value. +// +// If the addLabel is selected in the list, this function will return a -1 index with the added label and no error. +// Otherwise, it will return the index and the value of the selected item. In any case, if an error is triggered, it +// will also return the error as its third return value. +func (sa *SelectWithAdd) Run() (int, string, error) { + if len(sa.Items) > 0 { + newItems := append([]string{sa.AddLabel}, sa.Items...) + + list, err := list.New(newItems, 5) + if err != nil { + return 0, "", err + } + + s := Select{ + Label: sa.Label, + Items: newItems, + IsVimMode: sa.IsVimMode, + HideHelp: sa.HideHelp, + Size: 5, + list: list, + Pointer: sa.Pointer, + } + s.setKeys() + + err = s.prepareTemplates() + if err != nil { + return 0, "", err + } + + selected, value, err := s.innerRun(1, 0, '+') + if err != nil || selected != 0 { + return selected - 1, value, err + } + + // XXX run through terminal for windows + os.Stdout.Write([]byte(upLine(1) + "\r" + clearLine)) + } + + p := Prompt{ + Label: sa.AddLabel, + Validate: sa.Validate, + IsVimMode: sa.IsVimMode, + Pointer: sa.Pointer, + } + value, err := p.Run() + return SelectedAdd, value, err +} + +func (s *Select) setKeys() { + if s.Keys != nil { + return + } + s.Keys = &SelectKeys{ + Prev: Key{Code: KeyPrev, Display: KeyPrevDisplay}, + Next: Key{Code: KeyNext, Display: KeyNextDisplay}, + PageUp: Key{Code: KeyBackward, Display: KeyBackwardDisplay}, + PageDown: Key{Code: KeyForward, Display: KeyForwardDisplay}, + Search: Key{Code: '/', Display: "/"}, + } +} + +func (s *Select) renderDetails(item interface{}) [][]byte { + if s.Templates.details == nil { + return nil + } + + var buf bytes.Buffer + w := ansiterm.NewTabWriter(&buf, 0, 0, 8, ' ', 0) + + err := s.Templates.details.Execute(w, item) + if err != nil { + fmt.Fprintf(w, "%v", item) + } + + w.Flush() + + output := buf.Bytes() + + return bytes.Split(output, []byte("\n")) +} + +func (s *Select) renderHelp(b bool) []byte { + keys := struct { + NextKey string + PrevKey string + PageDownKey string + PageUpKey string + Search bool + SearchKey string + }{ + NextKey: s.Keys.Next.Display, + PrevKey: s.Keys.Prev.Display, + PageDownKey: s.Keys.PageDown.Display, + PageUpKey: s.Keys.PageUp.Display, + SearchKey: s.Keys.Search.Display, + Search: b, + } + + return render(s.Templates.help, keys) +} + +func render(tpl *template.Template, data interface{}) []byte { + var buf bytes.Buffer + err := tpl.Execute(&buf, data) + if err != nil { + return []byte(fmt.Sprintf("%v", data)) + } + return buf.Bytes() +} + +func clearScreen(sb *screenbuf.ScreenBuf) { + sb.Reset() + sb.Clear() + sb.Flush() +} diff --git a/vendor/github.com/manifoldco/promptui/styles.go b/vendor/github.com/manifoldco/promptui/styles.go new file mode 100644 index 0000000..d7698c9 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/styles.go @@ -0,0 +1,23 @@ +// +build !windows + +package promptui + +// These are the default icons used by promptui for select and prompts. These should not be overridden and instead +// customized through the use of custom templates +var ( + // IconInitial is the icon used when starting in prompt mode and the icon next to the label when + // starting in select mode. + IconInitial = Styler(FGBlue)("?") + + // IconGood is the icon used when a good answer is entered in prompt mode. + IconGood = Styler(FGGreen)("✔") + + // IconWarn is the icon used when a good, but potentially invalid answer is entered in prompt mode. + IconWarn = Styler(FGYellow)("⚠") + + // IconBad is the icon used when a bad answer is entered in prompt mode. + IconBad = Styler(FGRed)("✗") + + // IconSelect is the icon used to identify the currently selected item in select mode. + IconSelect = Styler(FGBold)("▸") +) diff --git a/vendor/github.com/manifoldco/promptui/styles_windows.go b/vendor/github.com/manifoldco/promptui/styles_windows.go new file mode 100644 index 0000000..36de268 --- /dev/null +++ b/vendor/github.com/manifoldco/promptui/styles_windows.go @@ -0,0 +1,21 @@ +package promptui + +// These are the default icons used bu promptui for select and prompts. They can either be overridden directly +// from these variable or customized through the use of custom templates +var ( + // IconInitial is the icon used when starting in prompt mode and the icon next to the label when + // starting in select mode. + IconInitial = Styler(FGBlue)("?") + + // IconGood is the icon used when a good answer is entered in prompt mode. + IconGood = Styler(FGGreen)("v") + + // IconWarn is the icon used when a good, but potentially invalid answer is entered in prompt mode. + IconWarn = Styler(FGYellow)("!") + + // IconBad is the icon used when a bad answer is entered in prompt mode. + IconBad = Styler(FGRed)("x") + + // IconSelect is the icon used to identify the currently selected item in select mode. + IconSelect = Styler(FGBold)(">") +) diff --git a/vendor/github.com/mattn/go-colorable/.travis.yml b/vendor/github.com/mattn/go-colorable/.travis.yml new file mode 100644 index 0000000..98db8f0 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - tip + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw diff --git a/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/mattn/go-colorable/LICENSE new file mode 100644 index 0000000..91b5cef --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Yasuhiro Matsumoto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mattn/go-colorable/README.md b/vendor/github.com/mattn/go-colorable/README.md new file mode 100644 index 0000000..56729a9 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/README.md @@ -0,0 +1,48 @@ +# go-colorable + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable) +[![Build Status](https://travis-ci.org/mattn/go-colorable.svg?branch=master)](https://travis-ci.org/mattn/go-colorable) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-colorable/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-colorable?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable) + +Colorable writer for windows. + +For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) +This package is possible to handle escape sequence for ansi color on windows. + +## Too Bad! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) + + +## So Good! + +![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) + +## Usage + +```go +logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) +logrus.SetOutput(colorable.NewColorableStdout()) + +logrus.Info("succeeded") +logrus.Warn("not correct") +logrus.Error("something error") +logrus.Fatal("panic") +``` + +You can compile above code on non-windows OSs. + +## Installation + +``` +$ go get github.com/mattn/go-colorable +``` + +# License + +MIT + +# Author + +Yasuhiro Matsumoto (a.k.a mattn) diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go new file mode 100644 index 0000000..1f28d77 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go @@ -0,0 +1,29 @@ +// +build appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go new file mode 100644 index 0000000..887f203 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_others.go @@ -0,0 +1,30 @@ +// +build !windows +// +build !appengine + +package colorable + +import ( + "io" + "os" + + _ "github.com/mattn/go-isatty" +) + +// NewColorable return new instance of Writer which handle escape sequence. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return os.Stdout +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return os.Stderr +} diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go new file mode 100644 index 0000000..e17a547 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -0,0 +1,884 @@ +// +build windows +// +build !appengine + +package colorable + +import ( + "bytes" + "io" + "math" + "os" + "strconv" + "strings" + "syscall" + "unsafe" + + "github.com/mattn/go-isatty" +) + +const ( + foregroundBlue = 0x1 + foregroundGreen = 0x2 + foregroundRed = 0x4 + foregroundIntensity = 0x8 + foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) + backgroundBlue = 0x10 + backgroundGreen = 0x20 + backgroundRed = 0x40 + backgroundIntensity = 0x80 + backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) +) + +type wchar uint16 +type short int16 +type dword uint32 +type word uint16 + +type coord struct { + x short + y short +} + +type smallRect struct { + left short + top short + right short + bottom short +} + +type consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word + window smallRect + maximumWindowSize coord +} + +type consoleCursorInfo struct { + size dword + visible int32 +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") + procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") + procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") + procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") + procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") + procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") +) + +// Writer provide colorable Writer to the console +type Writer struct { + out io.Writer + handle syscall.Handle + oldattr word + oldpos coord +} + +// NewColorable return new instance of Writer which handle escape sequence from File. +func NewColorable(file *os.File) io.Writer { + if file == nil { + panic("nil passed instead of *os.File to NewColorable()") + } + + if isatty.IsTerminal(file.Fd()) { + var csbi consoleScreenBufferInfo + handle := syscall.Handle(file.Fd()) + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} + } + return file +} + +// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +func NewColorableStdout() io.Writer { + return NewColorable(os.Stdout) +} + +// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +func NewColorableStderr() io.Writer { + return NewColorable(os.Stderr) +} + +var color256 = map[int]int{ + 0: 0x000000, + 1: 0x800000, + 2: 0x008000, + 3: 0x808000, + 4: 0x000080, + 5: 0x800080, + 6: 0x008080, + 7: 0xc0c0c0, + 8: 0x808080, + 9: 0xff0000, + 10: 0x00ff00, + 11: 0xffff00, + 12: 0x0000ff, + 13: 0xff00ff, + 14: 0x00ffff, + 15: 0xffffff, + 16: 0x000000, + 17: 0x00005f, + 18: 0x000087, + 19: 0x0000af, + 20: 0x0000d7, + 21: 0x0000ff, + 22: 0x005f00, + 23: 0x005f5f, + 24: 0x005f87, + 25: 0x005faf, + 26: 0x005fd7, + 27: 0x005fff, + 28: 0x008700, + 29: 0x00875f, + 30: 0x008787, + 31: 0x0087af, + 32: 0x0087d7, + 33: 0x0087ff, + 34: 0x00af00, + 35: 0x00af5f, + 36: 0x00af87, + 37: 0x00afaf, + 38: 0x00afd7, + 39: 0x00afff, + 40: 0x00d700, + 41: 0x00d75f, + 42: 0x00d787, + 43: 0x00d7af, + 44: 0x00d7d7, + 45: 0x00d7ff, + 46: 0x00ff00, + 47: 0x00ff5f, + 48: 0x00ff87, + 49: 0x00ffaf, + 50: 0x00ffd7, + 51: 0x00ffff, + 52: 0x5f0000, + 53: 0x5f005f, + 54: 0x5f0087, + 55: 0x5f00af, + 56: 0x5f00d7, + 57: 0x5f00ff, + 58: 0x5f5f00, + 59: 0x5f5f5f, + 60: 0x5f5f87, + 61: 0x5f5faf, + 62: 0x5f5fd7, + 63: 0x5f5fff, + 64: 0x5f8700, + 65: 0x5f875f, + 66: 0x5f8787, + 67: 0x5f87af, + 68: 0x5f87d7, + 69: 0x5f87ff, + 70: 0x5faf00, + 71: 0x5faf5f, + 72: 0x5faf87, + 73: 0x5fafaf, + 74: 0x5fafd7, + 75: 0x5fafff, + 76: 0x5fd700, + 77: 0x5fd75f, + 78: 0x5fd787, + 79: 0x5fd7af, + 80: 0x5fd7d7, + 81: 0x5fd7ff, + 82: 0x5fff00, + 83: 0x5fff5f, + 84: 0x5fff87, + 85: 0x5fffaf, + 86: 0x5fffd7, + 87: 0x5fffff, + 88: 0x870000, + 89: 0x87005f, + 90: 0x870087, + 91: 0x8700af, + 92: 0x8700d7, + 93: 0x8700ff, + 94: 0x875f00, + 95: 0x875f5f, + 96: 0x875f87, + 97: 0x875faf, + 98: 0x875fd7, + 99: 0x875fff, + 100: 0x878700, + 101: 0x87875f, + 102: 0x878787, + 103: 0x8787af, + 104: 0x8787d7, + 105: 0x8787ff, + 106: 0x87af00, + 107: 0x87af5f, + 108: 0x87af87, + 109: 0x87afaf, + 110: 0x87afd7, + 111: 0x87afff, + 112: 0x87d700, + 113: 0x87d75f, + 114: 0x87d787, + 115: 0x87d7af, + 116: 0x87d7d7, + 117: 0x87d7ff, + 118: 0x87ff00, + 119: 0x87ff5f, + 120: 0x87ff87, + 121: 0x87ffaf, + 122: 0x87ffd7, + 123: 0x87ffff, + 124: 0xaf0000, + 125: 0xaf005f, + 126: 0xaf0087, + 127: 0xaf00af, + 128: 0xaf00d7, + 129: 0xaf00ff, + 130: 0xaf5f00, + 131: 0xaf5f5f, + 132: 0xaf5f87, + 133: 0xaf5faf, + 134: 0xaf5fd7, + 135: 0xaf5fff, + 136: 0xaf8700, + 137: 0xaf875f, + 138: 0xaf8787, + 139: 0xaf87af, + 140: 0xaf87d7, + 141: 0xaf87ff, + 142: 0xafaf00, + 143: 0xafaf5f, + 144: 0xafaf87, + 145: 0xafafaf, + 146: 0xafafd7, + 147: 0xafafff, + 148: 0xafd700, + 149: 0xafd75f, + 150: 0xafd787, + 151: 0xafd7af, + 152: 0xafd7d7, + 153: 0xafd7ff, + 154: 0xafff00, + 155: 0xafff5f, + 156: 0xafff87, + 157: 0xafffaf, + 158: 0xafffd7, + 159: 0xafffff, + 160: 0xd70000, + 161: 0xd7005f, + 162: 0xd70087, + 163: 0xd700af, + 164: 0xd700d7, + 165: 0xd700ff, + 166: 0xd75f00, + 167: 0xd75f5f, + 168: 0xd75f87, + 169: 0xd75faf, + 170: 0xd75fd7, + 171: 0xd75fff, + 172: 0xd78700, + 173: 0xd7875f, + 174: 0xd78787, + 175: 0xd787af, + 176: 0xd787d7, + 177: 0xd787ff, + 178: 0xd7af00, + 179: 0xd7af5f, + 180: 0xd7af87, + 181: 0xd7afaf, + 182: 0xd7afd7, + 183: 0xd7afff, + 184: 0xd7d700, + 185: 0xd7d75f, + 186: 0xd7d787, + 187: 0xd7d7af, + 188: 0xd7d7d7, + 189: 0xd7d7ff, + 190: 0xd7ff00, + 191: 0xd7ff5f, + 192: 0xd7ff87, + 193: 0xd7ffaf, + 194: 0xd7ffd7, + 195: 0xd7ffff, + 196: 0xff0000, + 197: 0xff005f, + 198: 0xff0087, + 199: 0xff00af, + 200: 0xff00d7, + 201: 0xff00ff, + 202: 0xff5f00, + 203: 0xff5f5f, + 204: 0xff5f87, + 205: 0xff5faf, + 206: 0xff5fd7, + 207: 0xff5fff, + 208: 0xff8700, + 209: 0xff875f, + 210: 0xff8787, + 211: 0xff87af, + 212: 0xff87d7, + 213: 0xff87ff, + 214: 0xffaf00, + 215: 0xffaf5f, + 216: 0xffaf87, + 217: 0xffafaf, + 218: 0xffafd7, + 219: 0xffafff, + 220: 0xffd700, + 221: 0xffd75f, + 222: 0xffd787, + 223: 0xffd7af, + 224: 0xffd7d7, + 225: 0xffd7ff, + 226: 0xffff00, + 227: 0xffff5f, + 228: 0xffff87, + 229: 0xffffaf, + 230: 0xffffd7, + 231: 0xffffff, + 232: 0x080808, + 233: 0x121212, + 234: 0x1c1c1c, + 235: 0x262626, + 236: 0x303030, + 237: 0x3a3a3a, + 238: 0x444444, + 239: 0x4e4e4e, + 240: 0x585858, + 241: 0x626262, + 242: 0x6c6c6c, + 243: 0x767676, + 244: 0x808080, + 245: 0x8a8a8a, + 246: 0x949494, + 247: 0x9e9e9e, + 248: 0xa8a8a8, + 249: 0xb2b2b2, + 250: 0xbcbcbc, + 251: 0xc6c6c6, + 252: 0xd0d0d0, + 253: 0xdadada, + 254: 0xe4e4e4, + 255: 0xeeeeee, +} + +// `\033]0;TITLESTR\007` +func doTitleSequence(er *bytes.Reader) error { + var c byte + var err error + + c, err = er.ReadByte() + if err != nil { + return err + } + if c != '0' && c != '2' { + return nil + } + c, err = er.ReadByte() + if err != nil { + return err + } + if c != ';' { + return nil + } + title := make([]byte, 0, 80) + for { + c, err = er.ReadByte() + if err != nil { + return err + } + if c == 0x07 || c == '\n' { + break + } + title = append(title, c) + } + if len(title) > 0 { + title8, err := syscall.UTF16PtrFromString(string(title)) + if err == nil { + procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) + } + } + return nil +} + +// Write write data on console +func (w *Writer) Write(data []byte) (n int, err error) { + var csbi consoleScreenBufferInfo + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + + if c2 == ']' { + if err := doTitleSequence(er); err != nil { + break loop + } + continue + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + var m byte + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + m = c + break + } + buf.Write([]byte(string(c))) + } + + switch m { + case 'A': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'B': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'C': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'D': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'E': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y += short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'F': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = 0 + csbi.cursorPosition.y -= short(n) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'G': + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + csbi.cursorPosition.x = short(n - 1) + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'H', 'f': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + if buf.Len() > 0 { + token := strings.Split(buf.String(), ";") + switch len(token) { + case 1: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + csbi.cursorPosition.y = short(n1 - 1) + case 2: + n1, err := strconv.Atoi(token[0]) + if err != nil { + continue + } + n2, err := strconv.Atoi(token[1]) + if err != nil { + continue + } + csbi.cursorPosition.x = short(n2 - 1) + csbi.cursorPosition.y = short(n1 - 1) + } + } else { + csbi.cursorPosition.y = 0 + } + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) + case 'J': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + var count, written dword + var cursor coord + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top} + count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'K': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + var count, written dword + switch n { + case 0: + cursor = coord{x: csbi.cursorPosition.x + 1, y: csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x - 1) + case 1: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + count = dword(csbi.size.x - csbi.cursorPosition.x) + case 2: + cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} + count = dword(csbi.size.x) + } + procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'm': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + attr := csbi.attributes + cs := buf.String() + if cs == "" { + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) + continue + } + token := strings.Split(cs, ";") + for i := 0; i < len(token); i++ { + ns := token[i] + if n, err = strconv.Atoi(ns); err == nil { + switch { + case n == 0 || n == 100: + attr = w.oldattr + case 1 <= n && n <= 5: + attr |= foregroundIntensity + case n == 7: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case n == 22 || n == 25: + attr |= foregroundIntensity + case n == 27: + attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) + case 30 <= n && n <= 37: + attr &= backgroundMask + if (n-30)&1 != 0 { + attr |= foregroundRed + } + if (n-30)&2 != 0 { + attr |= foregroundGreen + } + if (n-30)&4 != 0 { + attr |= foregroundBlue + } + case n == 38: // set foreground color. + if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256foreAttr == nil { + n256setup() + } + attr &= backgroundMask + attr |= n256foreAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & backgroundMask) + } + case n == 39: // reset foreground color. + attr &= backgroundMask + attr |= w.oldattr & foregroundMask + case 40 <= n && n <= 47: + attr &= foregroundMask + if (n-40)&1 != 0 { + attr |= backgroundRed + } + if (n-40)&2 != 0 { + attr |= backgroundGreen + } + if (n-40)&4 != 0 { + attr |= backgroundBlue + } + case n == 48: // set background color. + if i < len(token)-2 && token[i+1] == "5" { + if n256, err := strconv.Atoi(token[i+2]); err == nil { + if n256backAttr == nil { + n256setup() + } + attr &= foregroundMask + attr |= n256backAttr[n256] + i += 2 + } + } else { + attr = attr & (w.oldattr & foregroundMask) + } + case n == 49: // reset foreground color. + attr &= foregroundMask + attr |= w.oldattr & backgroundMask + case 90 <= n && n <= 97: + attr = (attr & backgroundMask) + attr |= foregroundIntensity + if (n-90)&1 != 0 { + attr |= foregroundRed + } + if (n-90)&2 != 0 { + attr |= foregroundGreen + } + if (n-90)&4 != 0 { + attr |= foregroundBlue + } + case 100 <= n && n <= 107: + attr = (attr & foregroundMask) + attr |= backgroundIntensity + if (n-100)&1 != 0 { + attr |= backgroundRed + } + if (n-100)&2 != 0 { + attr |= backgroundGreen + } + if (n-100)&4 != 0 { + attr |= backgroundBlue + } + } + procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) + } + } + case 'h': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 'l': + var ci consoleCursorInfo + cs := buf.String() + if cs == "5>" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 1 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } else if cs == "?25" { + procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + ci.visible = 0 + procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) + } + case 's': + procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) + w.oldpos = csbi.cursorPosition + case 'u': + procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) + } + } + + return len(data), nil +} + +type consoleColor struct { + rgb int + red bool + green bool + blue bool + intensity bool +} + +func (c consoleColor) foregroundAttr() (attr word) { + if c.red { + attr |= foregroundRed + } + if c.green { + attr |= foregroundGreen + } + if c.blue { + attr |= foregroundBlue + } + if c.intensity { + attr |= foregroundIntensity + } + return +} + +func (c consoleColor) backgroundAttr() (attr word) { + if c.red { + attr |= backgroundRed + } + if c.green { + attr |= backgroundGreen + } + if c.blue { + attr |= backgroundBlue + } + if c.intensity { + attr |= backgroundIntensity + } + return +} + +var color16 = []consoleColor{ + {0x000000, false, false, false, false}, + {0x000080, false, false, true, false}, + {0x008000, false, true, false, false}, + {0x008080, false, true, true, false}, + {0x800000, true, false, false, false}, + {0x800080, true, false, true, false}, + {0x808000, true, true, false, false}, + {0xc0c0c0, true, true, true, false}, + {0x808080, false, false, false, true}, + {0x0000ff, false, false, true, true}, + {0x00ff00, false, true, false, true}, + {0x00ffff, false, true, true, true}, + {0xff0000, true, false, false, true}, + {0xff00ff, true, false, true, true}, + {0xffff00, true, true, false, true}, + {0xffffff, true, true, true, true}, +} + +type hsv struct { + h, s, v float32 +} + +func (a hsv) dist(b hsv) float32 { + dh := a.h - b.h + switch { + case dh > 0.5: + dh = 1 - dh + case dh < -0.5: + dh = -1 - dh + } + ds := a.s - b.s + dv := a.v - b.v + return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) +} + +func toHSV(rgb int) hsv { + r, g, b := float32((rgb&0xFF0000)>>16)/256.0, + float32((rgb&0x00FF00)>>8)/256.0, + float32(rgb&0x0000FF)/256.0 + min, max := minmax3f(r, g, b) + h := max - min + if h > 0 { + if max == r { + h = (g - b) / h + if h < 0 { + h += 6 + } + } else if max == g { + h = 2 + (b-r)/h + } else { + h = 4 + (r-g)/h + } + } + h /= 6.0 + s := max - min + if max != 0 { + s /= max + } + v := max + return hsv{h: h, s: s, v: v} +} + +type hsvTable []hsv + +func toHSVTable(rgbTable []consoleColor) hsvTable { + t := make(hsvTable, len(rgbTable)) + for i, c := range rgbTable { + t[i] = toHSV(c.rgb) + } + return t +} + +func (t hsvTable) find(rgb int) consoleColor { + hsv := toHSV(rgb) + n := 7 + l := float32(5.0) + for i, p := range t { + d := hsv.dist(p) + if d < l { + l, n = d, i + } + } + return color16[n] +} + +func minmax3f(a, b, c float32) (min, max float32) { + if a < b { + if b < c { + return a, c + } else if a < c { + return a, b + } else { + return c, b + } + } else { + if a < c { + return b, c + } else if b < c { + return b, a + } else { + return c, a + } + } +} + +var n256foreAttr []word +var n256backAttr []word + +func n256setup() { + n256foreAttr = make([]word, 256) + n256backAttr = make([]word, 256) + t := toHSVTable(color16) + for i, rgb := range color256 { + c := t.find(rgb) + n256foreAttr[i] = c.foregroundAttr() + n256backAttr[i] = c.backgroundAttr() + } +} diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go new file mode 100644 index 0000000..9721e16 --- /dev/null +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -0,0 +1,55 @@ +package colorable + +import ( + "bytes" + "io" +) + +// NonColorable hold writer but remove escape sequence. +type NonColorable struct { + out io.Writer +} + +// NewNonColorable return new instance of Writer which remove escape sequence from Writer. +func NewNonColorable(w io.Writer) io.Writer { + return &NonColorable{out: w} +} + +// Write write data on console +func (w *NonColorable) Write(data []byte) (n int, err error) { + er := bytes.NewReader(data) + var bw [1]byte +loop: + for { + c1, err := er.ReadByte() + if err != nil { + break loop + } + if c1 != 0x1b { + bw[0] = c1 + w.out.Write(bw[:]) + continue + } + c2, err := er.ReadByte() + if err != nil { + break loop + } + if c2 != 0x5b { + continue + } + + var buf bytes.Buffer + for { + c, err := er.ReadByte() + if err != nil { + break loop + } + if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { + break + } + buf.Write([]byte(string(c))) + } + } + + return len(data), nil +} diff --git a/vendor/github.com/mattn/go-isatty/.travis.yml b/vendor/github.com/mattn/go-isatty/.travis.yml new file mode 100644 index 0000000..5597e02 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - tip + +os: + - linux + - osx + +before_install: + - go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover +script: + - $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 diff --git a/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/mattn/go-isatty/LICENSE new file mode 100644 index 0000000..65dc692 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/LICENSE @@ -0,0 +1,9 @@ +Copyright (c) Yasuhiro MATSUMOTO + +MIT License (Expat) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/mattn/go-isatty/README.md b/vendor/github.com/mattn/go-isatty/README.md new file mode 100644 index 0000000..1e69004 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/README.md @@ -0,0 +1,50 @@ +# go-isatty + +[![Godoc Reference](https://godoc.org/github.com/mattn/go-isatty?status.svg)](http://godoc.org/github.com/mattn/go-isatty) +[![Build Status](https://travis-ci.org/mattn/go-isatty.svg?branch=master)](https://travis-ci.org/mattn/go-isatty) +[![Coverage Status](https://coveralls.io/repos/github/mattn/go-isatty/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-isatty?branch=master) +[![Go Report Card](https://goreportcard.com/badge/mattn/go-isatty)](https://goreportcard.com/report/mattn/go-isatty) + +isatty for golang + +## Usage + +```go +package main + +import ( + "fmt" + "github.com/mattn/go-isatty" + "os" +) + +func main() { + if isatty.IsTerminal(os.Stdout.Fd()) { + fmt.Println("Is Terminal") + } else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { + fmt.Println("Is Cygwin/MSYS2 Terminal") + } else { + fmt.Println("Is Not Terminal") + } +} +``` + +## Installation + +``` +$ go get github.com/mattn/go-isatty +``` + +## License + +MIT + +## Author + +Yasuhiro Matsumoto (a.k.a mattn) + +## Thanks + +* k-takata: base idea for IsCygwinTerminal + + https://github.com/k-takata/go-iscygpty diff --git a/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/mattn/go-isatty/doc.go new file mode 100644 index 0000000..17d4f90 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/doc.go @@ -0,0 +1,2 @@ +// Package isatty implements interface to isatty +package isatty diff --git a/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/mattn/go-isatty/isatty_appengine.go new file mode 100644 index 0000000..9584a98 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_appengine.go @@ -0,0 +1,15 @@ +// +build appengine + +package isatty + +// IsTerminal returns true if the file descriptor is terminal which +// is always false on on appengine classic which is a sandboxed PaaS. +func IsTerminal(fd uintptr) bool { + return false +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/mattn/go-isatty/isatty_bsd.go new file mode 100644 index 0000000..42f2514 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_bsd.go @@ -0,0 +1,18 @@ +// +build darwin freebsd openbsd netbsd dragonfly +// +build !appengine + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TIOCGETA + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/mattn/go-isatty/isatty_linux.go new file mode 100644 index 0000000..7384cf9 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux.go @@ -0,0 +1,18 @@ +// +build linux +// +build !appengine,!ppc64,!ppc64le + +package isatty + +import ( + "syscall" + "unsafe" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go new file mode 100644 index 0000000..44e5d21 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go @@ -0,0 +1,19 @@ +// +build linux +// +build ppc64 ppc64le + +package isatty + +import ( + "unsafe" + + syscall "golang.org/x/sys/unix" +) + +const ioctlReadTermios = syscall.TCGETS + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var termios syscall.Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/mattn/go-isatty/isatty_others.go new file mode 100644 index 0000000..9d8b4a5 --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_others.go @@ -0,0 +1,10 @@ +// +build !windows +// +build !appengine + +package isatty + +// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 +// terminal. This is also always false on this environment. +func IsCygwinTerminal(fd uintptr) bool { + return false +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/mattn/go-isatty/isatty_solaris.go new file mode 100644 index 0000000..1f0c6bf --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_solaris.go @@ -0,0 +1,16 @@ +// +build solaris +// +build !appengine + +package isatty + +import ( + "golang.org/x/sys/unix" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c +func IsTerminal(fd uintptr) bool { + var termio unix.Termio + err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) + return err == nil +} diff --git a/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/mattn/go-isatty/isatty_windows.go new file mode 100644 index 0000000..af51cbc --- /dev/null +++ b/vendor/github.com/mattn/go-isatty/isatty_windows.go @@ -0,0 +1,94 @@ +// +build windows +// +build !appengine + +package isatty + +import ( + "strings" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + fileNameInfo uintptr = 2 + fileTypePipe = 3 +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") + procGetFileType = kernel32.NewProc("GetFileType") +) + +func init() { + // Check if GetFileInformationByHandleEx is available. + if procGetFileInformationByHandleEx.Find() != nil { + procGetFileInformationByHandleEx = nil + } +} + +// IsTerminal return true if the file descriptor is terminal. +func IsTerminal(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// Check pipe name is used for cygwin/msys2 pty. +// Cygwin/MSYS2 PTY has a name like: +// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master +func isCygwinPipeName(name string) bool { + token := strings.Split(name, "-") + if len(token) < 5 { + return false + } + + if token[0] != `\msys` && token[0] != `\cygwin` { + return false + } + + if token[1] == "" { + return false + } + + if !strings.HasPrefix(token[2], "pty") { + return false + } + + if token[3] != `from` && token[3] != `to` { + return false + } + + if token[4] != "master" { + return false + } + + return true +} + +// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 +// terminal. +func IsCygwinTerminal(fd uintptr) bool { + if procGetFileInformationByHandleEx == nil { + return false + } + + // Cygwin/msys's pty is a pipe. + ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) + if ft != fileTypePipe || e != 0 { + return false + } + + var buf [2 + syscall.MAX_PATH]uint16 + r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), + 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), + uintptr(len(buf)*2), 0, 0) + if r == 0 || e != 0 { + return false + } + + l := *(*uint32)(unsafe.Pointer(&buf)) + return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 4e9e011..464380b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -11,6 +11,8 @@ github.com/Nvveen/Gotty github.com/beorn7/perks/quantile # github.com/cenkalti/backoff/v3 v3.0.0 github.com/cenkalti/backoff/v3 +# github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e +github.com/chzyer/readline # github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e ## explicit github.com/containerd/continuity/pathdriver @@ -46,11 +48,25 @@ github.com/hashicorp/hcl/json/token github.com/helloyi/go-sshclient # github.com/inconshreveable/mousetrap v1.0.0 github.com/inconshreveable/mousetrap +# github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a +github.com/juju/ansiterm +github.com/juju/ansiterm/tabwriter # github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 ## explicit github.com/kballard/go-shellquote +# github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a +github.com/lunixbochs/vtclean # github.com/magiconair/properties v1.8.1 github.com/magiconair/properties +# github.com/manifoldco/promptui v0.8.0 +## explicit +github.com/manifoldco/promptui +github.com/manifoldco/promptui/list +github.com/manifoldco/promptui/screenbuf +# github.com/mattn/go-colorable v0.0.9 +github.com/mattn/go-colorable +# github.com/mattn/go-isatty v0.0.4 +github.com/mattn/go-isatty # github.com/matttproud/golang_protobuf_extensions v1.0.1 github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/mitchellh/mapstructure v1.1.2