Sweet, this is overengineered already

This commit is contained in:
kayos@tcp.direct 2022-07-08 05:37:21 -07:00
parent 3574a6dcec
commit e6874ed3b8
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
854 changed files with 310245 additions and 29 deletions

@ -277,8 +277,8 @@ func (c *Bridge) Lights() []*HueLight {
func promptForUser(cnt *Bridge) bool {
log.Info().Msg("found new bridge")
confirmPrompt := tui.Select{
Label: "Create new user?",
Items: []string{"Yes", "No"},
Label: "How should we authenticate?",
Items: []string{"Create new user", "Provide existing username"},
CursorPos: 0,
IsVimMode: false,
Pointer: func(x []rune) []rune {
@ -295,11 +295,46 @@ func promptForUser(cnt *Bridge) bool {
log.Error().Err(err).Msg("failed")
return false
}
log.Info().Str("caller", cnt.Host).Msg(newuser)
log.Trace().Msg("logging in using: " + newuser)
cnt.Bridge = cnt.Bridge.Login(newuser)
cnt.User = newuser
case 1:
userEntry := tui.Prompt{
Label: "Username:",
Validate: func(s string) error {
if len(s) < 40 {
return errors.New("username must be at least 40 characters")
}
return nil
},
Mask: 'x',
HideEntered: false,
Pointer: func(x []rune) []rune {
return []rune("")
},
}
var err error
var input string
input, err = userEntry.Run()
if err != nil {
log.Error().Err(err).Msg("failed")
}
cnt.User = strings.TrimSpace(input)
}
log.Info().Str("caller", cnt.Host).Msg("logging in...")
log.Trace().Msg("logging in using: " + cnt.User)
cnt.Bridge = cnt.Bridge.Login(cnt.User)
_, err := cnt.Bridge.GetCapabilities()
if err != nil {
log.Error().Err(err).Msg("failed to verify that we are logged in")
return false
}
config.Snek.Set("bridges", map[string]interface{}{
"hostname": cnt.Host,
"username": cnt.User,
})
if err = config.Snek.WriteConfig(); err != nil {
log.Warn().Msg("failed to write config")
} else {
log.Info().Msg("configuration saved!")
}
return true
}
@ -336,14 +371,16 @@ addrIter:
return candidates
}
func enumerateBridge(a net.Addr) net.Addr {
func enumerateBridge(a net.Addr) interface{} {
var err error
if _, err = net.DialTimeout("tcp", a.String()+":80", 2*time.Second); err != nil {
log.Debug().Err(err).Msgf("failed to dial %s", a.String())
return nil
}
var resp *http.Response
resp, err = http.DefaultClient.Get("http://" + a.String() + "/debug/clip.html")
c := http.DefaultClient
c.Timeout = 2 * time.Second
resp, err = c.Get("http://" + a.String() + "/api/config")
if err != nil {
log.Debug().Err(err).Msgf("failed to get %s", a.String())
return nil
@ -357,11 +394,13 @@ func enumerateBridge(a net.Addr) net.Addr {
log.Warn().Err(err).Msg("failed to read response")
return nil
}
if !strings.Contains(string(ret), "CLIP API Debugger") {
if !strings.Contains(string(ret), "Philips hue") || !strings.Contains(string(ret), "bridgeid") {
log.Debug().Msgf("%s does not appear to be a hue bridge", a.String())
return nil
}
return a
br, _ := huego.NewCustom(ret, a.String(), http.DefaultClient)
return br
}
func scanChoicePrompt(interfaces []net.Interface) net.Interface {
@ -378,16 +417,16 @@ func scanChoicePrompt(interfaces []net.Interface) net.Interface {
return interfaces[choice]
}
func checkAddrs(addrs []net.Addr, working *int32, resChan chan net.Addr) {
func checkAddrs(addrs []net.Addr, working *int32, resChan chan interface{}) {
var init = &sync.Once{}
log.Trace().Msg("checking addresses")
for _, a := range addrs {
log.Trace().Msgf("checking %s", a.String())
ips := network.IterateNetRange(netaddr.MustParseIPPrefix(a.String()))
for ipa := range ips {
init.Do(func() { resChan <- nil })
init.Do(func() { resChan <- &huego.Bridge{} })
for {
if atomic.LoadInt32(working) > 30 {
if atomic.LoadInt32(working) > 50 {
time.Sleep(time.Second)
}
break
@ -404,8 +443,8 @@ func checkAddrs(addrs []net.Addr, working *int32, resChan chan net.Addr) {
}
// Determine the LAN network, then look for http servers on all of the local IPs.
func scanForBridges() ([]net.Addr, error) {
var hueIPs []net.Addr
func scanForBridges() ([]*huego.Bridge, error) {
var hueIPs []*huego.Bridge
interfaces, err := net.Interfaces()
if err != nil {
return nil, err
@ -421,7 +460,7 @@ func scanForBridges() ([]net.Addr, error) {
return nil, err
}
var working int32
resChan := make(chan net.Addr, 55)
resChan := make(chan interface{}, 55)
log.Trace().Interface("addresses", addrs).Msg("checkAddrs()")
go checkAddrs(addrs, &working, resChan)
<-resChan // wait for sync.Once to throw us a nil
@ -430,9 +469,10 @@ resultLoop:
for {
select {
case res := <-resChan:
if res != nil {
log.Info().Msgf("found bridge at %s", res.String())
hueIPs = append(hueIPs, res)
bridge, ok := res.(*huego.Bridge)
if ok && bridge != nil {
log.Info().Msgf("found %T: %v", bridge, bridge)
hueIPs = append(hueIPs, bridge)
}
default:
if atomic.LoadInt32(&working) <= 0 {
@ -464,23 +504,23 @@ func promptForDiscovery() error {
return errNoBridges
}
log.Info().Msg("searching for bridges...")
addrs, err := scanForBridges()
bridges, err := scanForBridges()
if err != nil {
return err
}
if len(addrs) < 1 {
if len(bridges) < 1 {
return errNoBridges
}
var cs []huego.Bridge
for _, a := range addrs {
cs = append(cs, huego.Bridge{Host: a.String()})
var cs []*huego.Bridge
for _, brd := range bridges {
cs = append(cs, brd)
}
Lucifer.Lock()
defer Lucifer.Unlock()
for _, c := range cs {
cnt := &Bridge{
Bridge: &c,
Bridge: c,
RWMutex: &sync.RWMutex{},
}
if promptForUser(cnt) {

12
main.go

@ -71,15 +71,17 @@ func FindLights(ctx context.Context, c *lights.Bridge) error {
}
}
func getNewSensors(Known *lights.Bridge) {
go Known.FindSensors()
Sensors, err := Known.GetNewSensors()
func getNewSensors(known *lights.Bridge) {
go known.FindSensors()
Sensors, err := known.GetNewSensors()
if err != nil {
log.Fatal().Err(err).Msg("failed to get sensors")
}
if Sensors == nil {
log.Fatal().Caller(1).Msg("nil")
}
for len(Sensors.Sensors) < 1 {
Sensors, err = Known.GetNewSensors()
Sensors, err = known.GetNewSensors()
if err != nil {
log.Error().Err(err).Msg("")
}

@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
bin/
# Folders
pkg/
_obj
_test
# Architecture specific extensions/prefixes
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Glide
vendor/

@ -0,0 +1,79 @@
# Change Log
## v0.3.0 (2018/??/??)
next release.
## v0.2.3 (2018/10/25)
### What's new?
* Add `prompt.FuzzyFilter` for fuzzy matching at [#92](https://git.tcp.direct/tcp.direct/go-prompt/pull/92).
* Add `OptionShowCompletionAtStart` to show completion at start at [#100](https://git.tcp.direct/tcp.direct/go-prompt/pull/100).
* Add `prompt.NewStderrWriter` at [#102](https://git.tcp.direct/tcp.direct/go-prompt/pull/102).
### Fixed
* Fix resetting display attributes (please see [pull #104](https://git.tcp.direct/tcp.direct/go-prompt/pull/104) for more details).
* Fix error handling of Flush function in ConsoleWriter (please see [pull #97](https://git.tcp.direct/tcp.direct/go-prompt/pull/97) for more details).
* Fix panic problem when reading from stdin before starting the prompt (please see [issue #88](https://git.tcp.direct/tcp.direct/go-prompt/issues/88) for more details).
### Removed or Deprecated
* `prompt.NewStandardOutputWriter` is deprecated. Please use `prompt.NewStdoutWriter`.
## v0.2.2 (2018/06/28)
### What's new?
* Support CJK(Chinese, Japanese and Korean) and Cyrillic characters.
* Add OptionCompletionWordSeparator(x string) to customize insertion points for completions.
* To support this, text query functions by arbitrary word separator are added in Document (please see [here](https://git.tcp.direct/tcp.direct/go-prompt/pull/79) for more details).
* Add FilePathCompleter to complete file path on your system.
* Add option to customize ascii code key bindings.
* Add GetWordAfterCursor method in Document.
### Removed or Deprecated
* prompt.Choose shortcut function is deprecated.
## v0.2.1 (2018/02/14)
### What's New?
* ~~It seems that windows support is almost perfect.~~
* A critical bug is found :( When you change a terminal window size, the layout will be broken because current implementation cannot catch signal for updating window size on Windows.
### Fixed
* Fix a Shift+Tab handling on Windows.
* Fix 4-dimension arrow keys handling on Windows.
## v0.2.0 (2018/02/13)
### What's New?
* Supports scrollbar when there are too many matched suggestions
* Windows support (but please caution because this is still not perfect).
* Add OptionLivePrefix to update the prefix dynamically
* Implement clear screen by `Ctrl+L`.
### Fixed
* Fix the behavior of `Ctrl+W` keybind.
* Fix the panic because when running on a docker container (please see [here](https://git.tcp.direct/tcp.direct/go-prompt/pull/32) for details).
* Fix panic when making terminal window small size after input 2 lines of texts. See [here](https://git.tcp.direct/tcp.direct/go-prompt/issues/37) for details).
* And also fixed many bugs that layout is broken when using Terminal.app, GNU Terminal and a Goland(IntelliJ).
### News
New core developers are joined (alphabetical order).
* Nao Yonashiro (Github @orisano)
* Ryoma Abe (Github @Allajah)
* Yusuke Nakamura (Github @unasuke)
## v0.1.0 (2017/08/15)
Initial Release

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Masashi SHIBATA
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.

@ -0,0 +1,46 @@
.DEFAULT_GOAL := help
SOURCES := $(shell find . -prune -o -name "*.go" -not -name '*_test.go' -print)
GOIMPORTS ?= goimports
GOCILINT ?= golangci-lint
.PHONY: setup
setup: ## Setup for required tools.
go get -u golang.org/x/tools/cmd/goimports
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
go get -u golang.org/x/tools/cmd/stringer
.PHONY: fmt
fmt: $(SOURCES) ## Formatting source codes.
@$(GOIMPORTS) -w $^
.PHONY: lint
lint: ## Run golangci-lint.
@$(GOCILINT) run --no-config --disable-all --enable=goimports --enable=misspell ./...
.PHONY: test
test: ## Run tests with race condition checking.
@go test -race ./...
.PHONY: bench
bench: ## Run benchmarks.
@go test -bench=. -run=- -benchmem ./...
.PHONY: coverage
cover: ## Run the tests.
@go test -coverprofile=coverage.o
@go tool cover -func=coverage.o
.PHONY: generate
generate: ## Run go generate
@go generate ./...
.PHONY: build
build: ## Build example command lines.
./_example/build.sh
.PHONY: help
help: ## Show help text
@echo "Commands:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-10s\033[0m %s\n", $$1, $$2}'

@ -0,0 +1,125 @@
# go-prompt
[![Go Report Card](https://goreportcard.com/badge/git.tcp.direct/tcp.direct/go-prompt)](https://goreportcard.com/report/git.tcp.direct/tcp.direct/go-prompt)
![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)
[![GoDoc](https://godoc.org/git.tcp.direct/tcp.direct/go-prompt?status.svg)](https://godoc.org/git.tcp.direct/tcp.direct/go-prompt)
![tests](https://git.tcp.direct/tcp.direct/go-prompt/workflows/tests/badge.svg)
A library for building powerful interactive prompts inspired by [python-prompt-toolkit](https://github.com/jonathanslenders/python-prompt-toolkit),
making it easier to build cross-platform command line tools using Go.
```go
package main
import (
"fmt"
"git.tcp.direct/tcp.direct/go-prompt"
)
func completer(d prompt.Document) []prompt.Suggest {
s := []prompt.Suggest{
{Text: "users", Description: "Store the username and age"},
{Text: "articles", Description: "Store the article text posted by user"},
{Text: "comments", Description: "Store the text commented to articles"},
}
return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
}
func main() {
fmt.Println("Please select table.")
t := prompt.Input("> ", completer)
fmt.Println("You selected " + t)
}
```
#### Projects using go-prompt
* [c-bata/kube-prompt : An interactive kubernetes client featuring auto-complete written in Go.](https://github.com/c-bata/kube-prompt)
* [rancher/cli : The Rancher Command Line Interface (CLI)is a unified tool to manage your Rancher server](https://github.com/rancher/cli)
* [kubicorn/kubicorn : Simple, cloud native infrastructure for Kubernetes.](https://github.com/kubicorn/kubicorn)
* [cch123/asm-cli : Interactive shell of assembly language(X86/X64) based on unicorn and rasm2](https://github.com/cch123/asm-cli)
* [ktr0731/evans : more expressive universal gRPC client](https://github.com/ktr0731/evans)
* [CrushedPixel/moshpit: A Command-line tool for datamoshing.](https://github.com/CrushedPixel/moshpit)
* [last-ent/testy-go: Testy Go: A tool for easy testing!](https://github.com/last-ent/testy-go)
* [tiagorlampert/CHAOS: a PoC that allow generate payloads and control remote operating systems.](https://github.com/tiagorlampert/CHAOS)
* [abs-lang/abs: ABS is a scripting language that works best on terminal. It tries to combine the elegance of languages such as Python, or Ruby, to the convenience of Bash.](https://github.com/abs-lang/abs)
* [takashabe/btcli: btcli is a CLI client for the Bigtable. Has many read options and auto-completion.](https://github.com/takashabe/btcli)
* [ysn2233/kafka-prompt: An interactive kafka-prompt(kafka-shell) built on existing kafka command client](https://github.com/ysn2233/kafka-prompt)
* [fishi0x01/vsh: HashiCorp Vault interactive shell](https://github.com/fishi0x01/vsh)
* [mstrYoda/docker-shell: A simple interactive prompt for docker](https://github.com/mstrYoda/docker-shell)
* [c-bata/gh-prompt: An interactive GitHub CLI featuring auto-complete.](https://github.com/c-bata/gh-prompt)
* [docker-slim/docker-slim: Don't change anything in your Docker container image and minify it by up to 30x (and for compiled languages even more) making it secure too! (free and open source)](https://github.com/docker-slim/docker-slim)
* [rueyaa332266/ezcron: Ezcron is a CLI tool, helping you deal with cron expression easier.](https://github.com/rueyaa332266/ezcron)
* [qingstor/qsctl: Advanced command line tool for QingStor Object Storage.](https://github.com/qingstor/qsctl)
* [segmentio/topicctl: Tool for declarative management of Kafka topics](https://github.com/segmentio/topicctl)
* [chriswalz/bit: Bit is a modern Git CLI](https://github.com/chriswalz/bit)
* (If you create a CLI utility using go-prompt and want your own project to be listed here, please submit a GitHub issue.)
## Features
### Powerful auto-completion
[![demo](https://github.com/c-bata/assets/raw/master/go-prompt/kube-prompt.gif)](https://github.com/c-bata/kube-prompt)
(This is a GIF animation of kube-prompt.)
### Flexible options
go-prompt provides many options. Please check [option section of GoDoc](https://godoc.org/git.tcp.direct/tcp.direct/go-prompt#Option) for more details.
[![options](https://github.com/c-bata/assets/raw/master/go-prompt/prompt-options.png)](#flexible-options)
### Keyboard Shortcuts
Emacs-like keyboard shortcuts are available by default (these also are the default shortcuts in Bash shell).
You can customize and expand these shortcuts.
[![keyboard shortcuts](https://github.com/c-bata/assets/raw/master/go-prompt/keyboard-shortcuts.gif)](#keyboard-shortcuts)
Key Binding | Description
---------------------|---------------------------------------------------------
<kbd>Ctrl + A</kbd> | Go to the beginning of the line (Home)
<kbd>Ctrl + E</kbd> | Go to the end of the line (End)
<kbd>Ctrl + P</kbd> | Previous command (Up arrow)
<kbd>Ctrl + N</kbd> | Next command (Down arrow)
<kbd>Ctrl + F</kbd> | Forward one character
<kbd>Ctrl + B</kbd> | Backward one character
<kbd>Ctrl + D</kbd> | Delete character under the cursor
<kbd>Ctrl + H</kbd> | Delete character before the cursor (Backspace)
<kbd>Ctrl + W</kbd> | Cut the word before the cursor to the clipboard
<kbd>Ctrl + K</kbd> | Cut the line after the cursor to the clipboard
<kbd>Ctrl + U</kbd> | Cut the line before the cursor to the clipboard
<kbd>Ctrl + L</kbd> | Clear the screen
### History
You can use <kbd>Up arrow</kbd> and <kbd>Down arrow</kbd> to walk through the history of commands executed.
[![History](https://github.com/c-bata/assets/raw/master/go-prompt/history.gif)](#history)
### Multiple platform support
We have confirmed go-prompt works fine in the following terminals:
* iTerm2 (macOS)
* Terminal.app (macOS)
* Command Prompt (Windows)
* gnome-terminal (Ubuntu)
## Links
* [Change Log](./CHANGELOG.md)
* [GoDoc](http://godoc.org/git.tcp.direct/tcp.direct/go-prompt)
* [gocover.io](https://gocover.io/git.tcp.direct/tcp.direct/go-prompt)
## Author
Masashi Shibata
* Twitter: [@c\_bata\_](https://twitter.com/c_bata_/)
* Github: [@c-bata](https://github.com/c-bata/)
## License
This software is licensed under the MIT license, see [LICENSE](./LICENSE) for more information.

@ -0,0 +1,191 @@
package prompt
import (
"strings"
"git.tcp.direct/Mirrors/go-prompt/internal/debug"
)
// Buffer emulates the console buffer.
type Buffer struct {
workingLines []string // The working lines. Similar to history
workingIndex int
cursorPosition int
cacheDocument *Document
preferredColumn int // Remember the original column for the next up/down movement.
lastKeyStroke Key
}
// Text returns string of the current line.
func (b *Buffer) Text() string {
return b.workingLines[b.workingIndex]
}
// Document method to return document instance from the current text and cursor position.
func (b *Buffer) Document() (d *Document) {
if b.cacheDocument == nil ||
b.cacheDocument.Text != b.Text() ||
b.cacheDocument.cursorPosition != b.cursorPosition {
b.cacheDocument = &Document{
Text: b.Text(),
cursorPosition: b.cursorPosition,
}
}
b.cacheDocument.lastKey = b.lastKeyStroke
return b.cacheDocument
}
// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
func (b *Buffer) DisplayCursorPosition() int {
return b.Document().DisplayCursorPosition()
}
// InsertText insert string from current line.
func (b *Buffer) InsertText(v string, overwrite bool, moveCursor bool) {
or := []rune(b.Text())
oc := b.cursorPosition
if overwrite {
overwritten := string(or[oc : oc+len(v)])
if strings.Contains(overwritten, "\n") {
i := strings.IndexAny(overwritten, "\n")
overwritten = overwritten[:i]
}
b.setText(string(or[:oc]) + v + string(or[oc+len(overwritten):]))
} else {
b.setText(string(or[:oc]) + v + string(or[oc:]))
}
if moveCursor {
b.cursorPosition += len([]rune(v))
}
}
// SetText method to set text and update cursorPosition.
// (When doing this, make sure that the cursor_position is valid for this text.
// text/cursor_position should be consistent at any time, otherwise set a Document instead.)
func (b *Buffer) setText(v string) {
debug.Assert(b.cursorPosition <= len([]rune(v)), "length of input should be shorter than cursor position")
b.workingLines[b.workingIndex] = v
}
// Set cursor position. Return whether it changed.
func (b *Buffer) setCursorPosition(p int) {
if p > 0 {
b.cursorPosition = p
} else {
b.cursorPosition = 0
}
}
func (b *Buffer) setDocument(d *Document) {
b.cacheDocument = d
b.setCursorPosition(d.cursorPosition) // Call before setText because setText check the relation between cursorPosition and line length.
b.setText(d.Text)
}
// CursorLeft move to left on the current line.
func (b *Buffer) CursorLeft(count int) {
l := b.Document().GetCursorLeftPosition(count)
b.cursorPosition += l
}
// CursorRight move to right on the current line.
func (b *Buffer) CursorRight(count int) {
l := b.Document().GetCursorRightPosition(count)
b.cursorPosition += l
}
// CursorUp move cursor to the previous line.
// (for multi-line edit).
func (b *Buffer) CursorUp(count int) {
orig := b.preferredColumn
if b.preferredColumn == -1 { // -1 means nil
orig = b.Document().CursorPositionCol()
}
b.cursorPosition += b.Document().GetCursorUpPosition(count, orig)
// Remember the original column for the next up/down movement.
b.preferredColumn = orig
}
// CursorDown move cursor to the next line.
// (for multi-line edit).
func (b *Buffer) CursorDown(count int) {
orig := b.preferredColumn
if b.preferredColumn == -1 { // -1 means nil
orig = b.Document().CursorPositionCol()
}
b.cursorPosition += b.Document().GetCursorDownPosition(count, orig)
// Remember the original column for the next up/down movement.
b.preferredColumn = orig
}
// DeleteBeforeCursor delete specified number of characters before cursor and return the deleted text.
func (b *Buffer) DeleteBeforeCursor(count int) (deleted string) {
debug.Assert(count >= 0, "count should be positive")
r := []rune(b.Text())
if b.cursorPosition > 0 {
start := b.cursorPosition - count
if start < 0 {
start = 0
}
deleted = string(r[start:b.cursorPosition])
b.setDocument(&Document{
Text: string(r[:start]) + string(r[b.cursorPosition:]),
cursorPosition: b.cursorPosition - len([]rune(deleted)),
})
}
return
}
// NewLine means CR.
func (b *Buffer) NewLine(copyMargin bool) {
if copyMargin {
b.InsertText("\n"+b.Document().leadingWhitespaceInCurrentLine(), false, true)
} else {
b.InsertText("\n", false, true)
}
}
// Delete specified number of characters and Return the deleted text.
func (b *Buffer) Delete(count int) (deleted string) {
r := []rune(b.Text())
if b.cursorPosition < len(r) {
deleted = b.Document().TextAfterCursor()[:count]
b.setText(string(r[:b.cursorPosition]) + string(r[b.cursorPosition+len(deleted):]))
}
return
}
// JoinNextLine joins the next line to the current one by deleting the line ending after the current line.
func (b *Buffer) JoinNextLine(separator string) {
if !b.Document().OnLastLine() {
b.cursorPosition += b.Document().GetEndOfLinePosition()
b.Delete(1)
// Remove spaces
b.setText(b.Document().TextBeforeCursor() + separator + strings.TrimLeft(b.Document().TextAfterCursor(), " "))
}
}
// SwapCharactersBeforeCursor swaps the last two characters before the cursor.
func (b *Buffer) SwapCharactersBeforeCursor() {
if b.cursorPosition >= 2 {
x := b.Text()[b.cursorPosition-2 : b.cursorPosition-1]
y := b.Text()[b.cursorPosition-1 : b.cursorPosition]
b.setText(b.Text()[:b.cursorPosition-2] + y + x + b.Text()[b.cursorPosition:])
}
}
// NewBuffer is constructor of Buffer struct.
func NewBuffer() (b *Buffer) {
b = &Buffer{
workingLines: []string{""},
workingIndex: 0,
preferredColumn: -1, // -1 means nil
}
return
}

@ -0,0 +1,190 @@
package prompt
import (
"strings"
"git.tcp.direct/Mirrors/go-prompt/internal/debug"
runewidth "github.com/mattn/go-runewidth"
)
const (
shortenSuffix = "..."
leftPrefix = " "
leftSuffix = " "
rightPrefix = " "
rightSuffix = " "
)
var (
leftMargin = runewidth.StringWidth(leftPrefix + leftSuffix)
rightMargin = runewidth.StringWidth(rightPrefix + rightSuffix)
completionMargin = leftMargin + rightMargin
)
// Suggest is printed when completing.
type Suggest struct {
Text string
Description string
}
// CompletionManager manages which suggestion is now selected.
type CompletionManager struct {
selected int // -1 means nothing one is selected.
tmp []Suggest
max uint16
completer Completer
verticalScroll int
wordSeparator string
showAtStart bool
}
// GetSelectedSuggestion returns the selected item.
func (c *CompletionManager) GetSelectedSuggestion() (s Suggest, ok bool) {
if c.selected == -1 {
return Suggest{}, false
} else if c.selected < -1 {
debug.Assert(false, "must not reach here")
c.selected = -1
return Suggest{}, false
}
return c.tmp[c.selected], true
}
// GetSuggestions returns the list of suggestion.
func (c *CompletionManager) GetSuggestions() []Suggest {
return c.tmp
}
// Reset to select nothing.
func (c *CompletionManager) Reset() {
c.selected = -1
c.verticalScroll = 0
c.Update(*NewDocument())
}
// Update to update the suggestions.
func (c *CompletionManager) Update(in Document) {
c.tmp = c.completer(in)
}
// Previous to select the previous suggestion item.
func (c *CompletionManager) Previous() {
if c.verticalScroll == c.selected && c.selected > 0 {
c.verticalScroll--
}
c.selected--
c.update()
}
// Next to select the next suggestion item.
func (c *CompletionManager) Next() {
if c.verticalScroll+int(c.max)-1 == c.selected {
c.verticalScroll++
}
c.selected++
c.update()
}
// Completing returns whether the CompletionManager selects something one.
func (c *CompletionManager) Completing() bool {
return c.selected != -1
}
func (c *CompletionManager) update() {
max := int(c.max)
if len(c.tmp) < max {
max = len(c.tmp)
}
if c.selected >= len(c.tmp) {
c.Reset()
} else if c.selected < -1 {
c.selected = len(c.tmp) - 1
c.verticalScroll = len(c.tmp) - max
}
}
func deleteBreakLineCharacters(s string) string {
s = strings.Replace(s, "\n", "", -1)
s = strings.Replace(s, "\r", "", -1)
return s
}
func formatTexts(o []string, max int, prefix, suffix string) (new []string, width int) {
l := len(o)
n := make([]string, l)
lenPrefix := runewidth.StringWidth(prefix)
lenSuffix := runewidth.StringWidth(suffix)
lenShorten := runewidth.StringWidth(shortenSuffix)
min := lenPrefix + lenSuffix + lenShorten
for i := 0; i < l; i++ {
o[i] = deleteBreakLineCharacters(o[i])
w := runewidth.StringWidth(o[i])
if width < w {
width = w
}
}
if width == 0 {
return n, 0
}
if min >= max {
return n, 0
}
if lenPrefix+width+lenSuffix > max {
width = max - lenPrefix - lenSuffix
}
for i := 0; i < l; i++ {
x := runewidth.StringWidth(o[i])
if x <= width {
spaces := strings.Repeat(" ", width-x)
n[i] = prefix + o[i] + spaces + suffix
} else if x > width {
x := runewidth.Truncate(o[i], width, shortenSuffix)
// When calling runewidth.Truncate("您好xxx您好xxx", 11, "...") returns "您好xxx..."
// But the length of this result is 10. So we need fill right using runewidth.FillRight.
n[i] = prefix + runewidth.FillRight(x, width) + suffix
}
}
return n, lenPrefix + width + lenSuffix
}
func formatSuggestions(suggests []Suggest, max int) (new []Suggest, width int) {
num := len(suggests)
new = make([]Suggest, num)
left := make([]string, num)
for i := 0; i < num; i++ {
left[i] = suggests[i].Text
}
right := make([]string, num)
for i := 0; i < num; i++ {
right[i] = suggests[i].Description
}
left, leftWidth := formatTexts(left, max, leftPrefix, leftSuffix)
if leftWidth == 0 {
return []Suggest{}, 0
}
right, rightWidth := formatTexts(right, max-leftWidth, rightPrefix, rightSuffix)
for i := 0; i < num; i++ {
new[i] = Suggest{Text: left[i], Description: right[i]}
}
return new, leftWidth + rightWidth
}
// NewCompletionManager returns initialized CompletionManager object.
func NewCompletionManager(completer Completer, max uint16) *CompletionManager {
return &CompletionManager{
selected: -1,
max: max,
completer: completer,
verticalScroll: 0,
}
}

@ -0,0 +1,441 @@
package prompt
import (
"strings"
"unicode/utf8"
"git.tcp.direct/Mirrors/go-prompt/internal/bisect"
istrings "git.tcp.direct/Mirrors/go-prompt/internal/strings"
runewidth "github.com/mattn/go-runewidth"
)
// Document has text displayed in terminal and cursor position.
type Document struct {
Text string
// This represents a index in a rune array of Document.Text.
// So if Document is "日本(cursor)語", cursorPosition is 2.
// But DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
cursorPosition int
lastKey Key
}
// NewDocument return the new empty document.
func NewDocument() *Document {
return &Document{
Text: "",
cursorPosition: 0,
}
}
// LastKeyStroke return the last key pressed in this document.
func (d *Document) LastKeyStroke() Key {
return d.lastKey
}
// DisplayCursorPosition returns the cursor position on rendered text on terminal emulators.
// So if Document is "日本(cursor)語", DisplayedCursorPosition returns 4 because '日' and '本' are double width characters.
func (d *Document) DisplayCursorPosition() int {
var position int
runes := []rune(d.Text)[:d.cursorPosition]
for i := range runes {
position += runewidth.RuneWidth(runes[i])
}
return position
}
// GetCharRelativeToCursor return character relative to cursor position, or empty string
func (d *Document) GetCharRelativeToCursor(offset int) (r rune) {
s := d.Text
cnt := 0
for len(s) > 0 {
cnt++
r, size := utf8.DecodeRuneInString(s)
if cnt == d.cursorPosition+offset {
return r
}
s = s[size:]
}
return 0
}
// TextBeforeCursor returns the text before the cursor.
func (d *Document) TextBeforeCursor() string {
r := []rune(d.Text)
return string(r[:d.cursorPosition])
}
// TextAfterCursor returns the text after the cursor.
func (d *Document) TextAfterCursor() string {
r := []rune(d.Text)
return string(r[d.cursorPosition:])
}
// GetWordBeforeCursor returns the word before the cursor.
// If we have whitespace before the cursor this returns an empty string.
func (d *Document) GetWordBeforeCursor() string {
x := d.TextBeforeCursor()
return x[d.FindStartOfPreviousWord():]
}
// GetWordAfterCursor returns the word after the cursor.
// If we have whitespace after the cursor this returns an empty string.
func (d *Document) GetWordAfterCursor() string {
x := d.TextAfterCursor()
return x[:d.FindEndOfCurrentWord()]
}
// GetWordBeforeCursorWithSpace returns the word before the cursor.
// Unlike GetWordBeforeCursor, it returns string containing space
func (d *Document) GetWordBeforeCursorWithSpace() string {
x := d.TextBeforeCursor()
return x[d.FindStartOfPreviousWordWithSpace():]
}
// GetWordAfterCursorWithSpace returns the word after the cursor.
// Unlike GetWordAfterCursor, it returns string containing space
func (d *Document) GetWordAfterCursorWithSpace() string {
x := d.TextAfterCursor()
return x[:d.FindEndOfCurrentWordWithSpace()]
}
// GetWordBeforeCursorUntilSeparator returns the text before the cursor until next separator.
func (d *Document) GetWordBeforeCursorUntilSeparator(sep string) string {
x := d.TextBeforeCursor()
return x[d.FindStartOfPreviousWordUntilSeparator(sep):]
}
// GetWordAfterCursorUntilSeparator returns the text after the cursor until next separator.
func (d *Document) GetWordAfterCursorUntilSeparator(sep string) string {
x := d.TextAfterCursor()
return x[:d.FindEndOfCurrentWordUntilSeparator(sep)]
}
// GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor returns the word before the cursor.
// Unlike GetWordBeforeCursor, it returns string containing space
func (d *Document) GetWordBeforeCursorUntilSeparatorIgnoreNextToCursor(sep string) string {
x := d.TextBeforeCursor()
return x[d.FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep):]
}
// GetWordAfterCursorUntilSeparatorIgnoreNextToCursor returns the word after the cursor.
// Unlike GetWordAfterCursor, it returns string containing space
func (d *Document) GetWordAfterCursorUntilSeparatorIgnoreNextToCursor(sep string) string {
x := d.TextAfterCursor()
return x[:d.FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep)]
}
// FindStartOfPreviousWord returns an index relative to the cursor position
// pointing to the start of the previous word. Return 0 if nothing was found.
func (d *Document) FindStartOfPreviousWord() int {
x := d.TextBeforeCursor()
i := strings.LastIndexByte(x, ' ')
if i != -1 {
return i + 1
}
return 0
}
// FindStartOfPreviousWordWithSpace is almost the same as FindStartOfPreviousWord.
// The only difference is to ignore contiguous spaces.
func (d *Document) FindStartOfPreviousWordWithSpace() int {
x := d.TextBeforeCursor()
end := istrings.LastIndexNotByte(x, ' ')
if end == -1 {
return 0
}
start := strings.LastIndexByte(x[:end], ' ')
if start == -1 {
return 0
}
return start + 1
}
// FindStartOfPreviousWordUntilSeparator is almost the same as FindStartOfPreviousWord.
// But this can specify Separator. Return 0 if nothing was found.
func (d *Document) FindStartOfPreviousWordUntilSeparator(sep string) int {
if sep == "" {
return d.FindStartOfPreviousWord()
}
x := d.TextBeforeCursor()
i := strings.LastIndexAny(x, sep)
if i != -1 {
return i + 1
}
return 0
}
// FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor is almost the same as FindStartOfPreviousWordWithSpace.
// But this can specify Separator. Return 0 if nothing was found.
func (d *Document) FindStartOfPreviousWordUntilSeparatorIgnoreNextToCursor(sep string) int {
if sep == "" {
return d.FindStartOfPreviousWordWithSpace()
}
x := d.TextBeforeCursor()
end := istrings.LastIndexNotAny(x, sep)
if end == -1 {
return 0
}
start := strings.LastIndexAny(x[:end], sep)
if start == -1 {
return 0
}
return start + 1
}
// FindEndOfCurrentWord returns an index relative to the cursor position.
// pointing to the end of the current word. Return 0 if nothing was found.
func (d *Document) FindEndOfCurrentWord() int {
x := d.TextAfterCursor()
i := strings.IndexByte(x, ' ')
if i != -1 {
return i
}
return len(x)
}
// FindEndOfCurrentWordWithSpace is almost the same as FindEndOfCurrentWord.
// The only difference is to ignore contiguous spaces.
func (d *Document) FindEndOfCurrentWordWithSpace() int {
x := d.TextAfterCursor()
start := istrings.IndexNotByte(x, ' ')
if start == -1 {
return len(x)
}
end := strings.IndexByte(x[start:], ' ')
if end == -1 {
return len(x)
}
return start + end
}
// FindEndOfCurrentWordUntilSeparator is almost the same as FindEndOfCurrentWord.
// But this can specify Separator. Return 0 if nothing was found.
func (d *Document) FindEndOfCurrentWordUntilSeparator(sep string) int {
if sep == "" {
return d.FindEndOfCurrentWord()
}
x := d.TextAfterCursor()
i := strings.IndexAny(x, sep)
if i != -1 {
return i
}
return len(x)
}
// FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor is almost the same as FindEndOfCurrentWordWithSpace.
// But this can specify Separator. Return 0 if nothing was found.
func (d *Document) FindEndOfCurrentWordUntilSeparatorIgnoreNextToCursor(sep string) int {
if sep == "" {
return d.FindEndOfCurrentWordWithSpace()
}
x := d.TextAfterCursor()
start := istrings.IndexNotAny(x, sep)
if start == -1 {
return len(x)
}
end := strings.IndexAny(x[start:], sep)
if end == -1 {
return len(x)
}
return start + end
}
// CurrentLineBeforeCursor returns the text from the start of the line until the cursor.
func (d *Document) CurrentLineBeforeCursor() string {
s := strings.Split(d.TextBeforeCursor(), "\n")
return s[len(s)-1]
}
// CurrentLineAfterCursor returns the text from the cursor until the end of the line.
func (d *Document) CurrentLineAfterCursor() string {
return strings.Split(d.TextAfterCursor(), "\n")[0]
}
// CurrentLine return the text on the line where the cursor is. (when the input
// consists of just one line, it equals `text`.
func (d *Document) CurrentLine() string {
return d.CurrentLineBeforeCursor() + d.CurrentLineAfterCursor()
}
// Array pointing to the start indexes of all the lines.
func (d *Document) lineStartIndexes() []int {
// TODO: Cache, because this is often reused.
// (If it is used, it's often used many times.
// And this has to be fast for editing big documents!)
lc := d.LineCount()
lengths := make([]int, lc)
for i, l := range d.Lines() {
lengths[i] = len(l)
}
// Calculate cumulative sums.
indexes := make([]int, lc+1)
indexes[0] = 0 // https://github.com/jonathanslenders/python-prompt-toolkit/blob/master/prompt_toolkit/document.py#L189
pos := 0
for i, l := range lengths {
pos += l + 1
indexes[i+1] = pos
}
if lc > 1 {
// Pop the last item. (This is not a new line.)
indexes = indexes[:lc]
}
return indexes
}
// For the index of a character at a certain line, calculate the index of
// the first character on that line.
func (d *Document) findLineStartIndex(index int) (pos int, lineStartIndex int) {
indexes := d.lineStartIndexes()
pos = bisect.Right(indexes, index) - 1
lineStartIndex = indexes[pos]
return
}
// CursorPositionRow returns the current row. (0-based.)
func (d *Document) CursorPositionRow() (row int) {
row, _ = d.findLineStartIndex(d.cursorPosition)
return
}
// CursorPositionCol returns the current column. (0-based.)
func (d *Document) CursorPositionCol() (col int) {
// Don't use self.text_before_cursor to calculate this. Creating substrings
// and splitting is too expensive for getting the cursor position.
_, index := d.findLineStartIndex(d.cursorPosition)
col = d.cursorPosition - index
return
}
// GetCursorLeftPosition returns the relative position for cursor left.
func (d *Document) GetCursorLeftPosition(count int) int {
if count < 0 {
return d.GetCursorRightPosition(-count)
}
if d.CursorPositionCol() > count {
return -count
}
return -d.CursorPositionCol()
}
// GetCursorRightPosition returns relative position for cursor right.
func (d *Document) GetCursorRightPosition(count int) int {
if count < 0 {
return d.GetCursorLeftPosition(-count)
}
if len(d.CurrentLineAfterCursor()) > count {
return count
}
return len(d.CurrentLineAfterCursor())
}
// GetCursorUpPosition return the relative cursor position (character index) where we would be
// if the user pressed the arrow-up button.
func (d *Document) GetCursorUpPosition(count int, preferredColumn int) int {
var col int
if preferredColumn == -1 { // -1 means nil
col = d.CursorPositionCol()
} else {
col = preferredColumn
}
row := d.CursorPositionRow() - count
if row < 0 {
row = 0
}
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
}
// GetCursorDownPosition return the relative cursor position (character index) where we would be if the
// user pressed the arrow-down button.
func (d *Document) GetCursorDownPosition(count int, preferredColumn int) int {
var col int
if preferredColumn == -1 { // -1 means nil
col = d.CursorPositionCol()
} else {
col = preferredColumn
}
row := d.CursorPositionRow() + count
return d.TranslateRowColToIndex(row, col) - d.cursorPosition
}
// Lines returns the array of all the lines.
func (d *Document) Lines() []string {
// TODO: Cache, because this one is reused very often.
return strings.Split(d.Text, "\n")
}
// LineCount return the number of lines in this document. If the document ends
// with a trailing \n, that counts as the beginning of a new line.
func (d *Document) LineCount() int {
return len(d.Lines())
}
// TranslateIndexToPosition given an index for the text, return the corresponding (row, col) tuple.
// (0-based. Returns (0, 0) for index=0.)
func (d *Document) TranslateIndexToPosition(index int) (row int, col int) {
row, rowIndex := d.findLineStartIndex(index)
col = index - rowIndex
return
}
// TranslateRowColToIndex given a (row, col), return the corresponding index.
// (Row and col params are 0-based.)
func (d *Document) TranslateRowColToIndex(row int, column int) (index int) {
indexes := d.lineStartIndexes()
if row < 0 {
row = 0
} else if row > len(indexes) {
row = len(indexes) - 1
}
index = indexes[row]
line := d.Lines()[row]
// python) result += max(0, min(col, len(line)))
if column > 0 || len(line) > 0 {
if column > len(line) {
index += len(line)
} else {
index += column
}
}
// Keep in range. (len(self.text) is included, because the cursor can be
// right after the end of the text as well.)
// python) result = max(0, min(result, len(self.text)))
if index > len(d.Text) {
index = len(d.Text)
}
if index < 0 {
index = 0
}
return index
}
// OnLastLine returns true when we are at the last line.
func (d *Document) OnLastLine() bool {
return d.CursorPositionRow() == (d.LineCount() - 1)
}
// GetEndOfLinePosition returns relative position for the end of this line.
func (d *Document) GetEndOfLinePosition() int {
return len([]rune(d.CurrentLineAfterCursor()))
}
func (d *Document) leadingWhitespaceInCurrentLine() (margin string) {
trimmed := strings.TrimSpace(d.CurrentLine())
margin = d.CurrentLine()[:len(d.CurrentLine())-len(trimmed)]
return
}

@ -0,0 +1,120 @@
package prompt
import "git.tcp.direct/Mirrors/go-prompt/internal/debug"
/*
========
PROGRESS
========
Moving the cursor
-----------------
* [x] Ctrl + a Go to the beginning of the line (Home)
* [x] Ctrl + e Go to the End of the line (End)
* [x] Ctrl + p Previous command (Up arrow)
* [x] Ctrl + n Next command (Down arrow)
* [x] Ctrl + f Forward one character
* [x] Ctrl + b Backward one character
* [x] Ctrl + xx Toggle between the start of line and current cursor position
Editing
-------
* [x] Ctrl + L Clear the Screen, similar to the clear command
* [x] Ctrl + d Delete character under the cursor
* [x] Ctrl + h Delete character before the cursor (Backspace)
* [x] Ctrl + w Cut the Word before the cursor to the clipboard.
* [x] Ctrl + k Cut the Line after the cursor to the clipboard.
* [x] Ctrl + u Cut/delete the Line before the cursor to the clipboard.
* [ ] Ctrl + t Swap the last two characters before the cursor (typo).
* [ ] Esc + t Swap the last two words before the cursor.
* [ ] ctrl + y Paste the last thing to be cut (yank)
* [ ] ctrl + _ Undo
*/
var emacsKeyBindings = []KeyBind{
// Go to the End of the line
{
Key: ControlE,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextAfterCursor())
buf.CursorRight(len(x))
},
},
// Go to the beginning of the line
{
Key: ControlA,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextBeforeCursor())
buf.CursorLeft(len(x))
},
},
// Cut the Line after the cursor
{
Key: ControlK,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextAfterCursor())
buf.Delete(len(x))
},
},
// Cut/delete the Line before the cursor
{
Key: ControlU,
Fn: func(buf *Buffer) {
x := []rune(buf.Document().TextBeforeCursor())
buf.DeleteBeforeCursor(len(x))
},
},
// Delete character under the cursor
{
Key: ControlD,
Fn: func(buf *Buffer) {
if buf.Text() != "" {
buf.Delete(1)
}
},
},
// Backspace
{
Key: ControlH,
Fn: func(buf *Buffer) {
buf.DeleteBeforeCursor(1)
},
},
// Right allow: Forward one character
{
Key: ControlF,
Fn: func(buf *Buffer) {
buf.CursorRight(1)
},
},
// Left allow: Backward one character
{
Key: ControlB,
Fn: func(buf *Buffer) {
buf.CursorLeft(1)
},
},
// Cut the Word before the cursor.
{
Key: ControlW,
Fn: func(buf *Buffer) {
buf.DeleteBeforeCursor(len([]rune(buf.Document().GetWordBeforeCursorWithSpace())))
},
},
// Clear the Screen, similar to the clear command
{
Key: ControlL,
Fn: func(buf *Buffer) {
consoleWriter.EraseScreen()
consoleWriter.CursorGoTo(0, 0)
debug.AssertNoError(consoleWriter.Flush())
},
},
}

@ -0,0 +1,71 @@
package prompt
import "strings"
// Filter is the type to filter the prompt.Suggestion array.
type Filter func([]Suggest, string, bool) []Suggest
// FilterHasPrefix checks whether the string completions.Text begins with sub.
func FilterHasPrefix(completions []Suggest, sub string, ignoreCase bool) []Suggest {
return filterSuggestions(completions, sub, ignoreCase, strings.HasPrefix)
}
// FilterHasSuffix checks whether the completion.Text ends with sub.
func FilterHasSuffix(completions []Suggest, sub string, ignoreCase bool) []Suggest {
return filterSuggestions(completions, sub, ignoreCase, strings.HasSuffix)
}
// FilterContains checks whether the completion.Text contains sub.
func FilterContains(completions []Suggest, sub string, ignoreCase bool) []Suggest {
return filterSuggestions(completions, sub, ignoreCase, strings.Contains)
}
// FilterFuzzy checks whether the completion.Text fuzzy matches sub.
// Fuzzy searching for "dog" is equivalent to "*d*o*g*". This search term
// would match, for example, "Good food is gone"
// ^ ^ ^
func FilterFuzzy(completions []Suggest, sub string, ignoreCase bool) []Suggest {
return filterSuggestions(completions, sub, ignoreCase, fuzzyMatch)
}
func fuzzyMatch(s, sub string) bool {
sChars := []rune(s)
sIdx := 0
// https://staticcheck.io/docs/checks#S1029
for _, c := range sub {
found := false
for ; sIdx < len(sChars); sIdx++ {
if sChars[sIdx] == c {
found = true
sIdx++
break
}
}
if !found {
return false
}
}
return true
}
func filterSuggestions(suggestions []Suggest, sub string, ignoreCase bool, function func(string, string) bool) []Suggest {
if sub == "" {
return suggestions
}
if ignoreCase {
sub = strings.ToUpper(sub)
}
ret := make([]Suggest, 0, len(suggestions))
for i := range suggestions {
c := suggestions[i].Text
if ignoreCase {
c = strings.ToUpper(c)
}
if function(c, sub) {
ret = append(ret, suggestions[i])
}
}
return ret
}

@ -0,0 +1,61 @@
package prompt
// History stores the texts that are entered.
type History struct {
histories []string
tmp []string
selected int
}
// Add to add text in history.
func (h *History) Add(input string) {
h.histories = append(h.histories, input)
h.Clear()
}
// Clear to clear the history.
func (h *History) Clear() {
h.tmp = make([]string, len(h.histories))
for i := range h.histories {
h.tmp[i] = h.histories[i]
}
h.tmp = append(h.tmp, "")
h.selected = len(h.tmp) - 1
}
// Older saves a buffer of current line and get a buffer of previous line by up-arrow.
// The changes of line buffers are stored until new history is created.
func (h *History) Older(buf *Buffer) (new *Buffer, changed bool) {
if len(h.tmp) == 1 || h.selected == 0 {
return buf, false
}
h.tmp[h.selected] = buf.Text()
h.selected--
new = NewBuffer()
new.InsertText(h.tmp[h.selected], false, true)
return new, true
}
// Newer saves a buffer of current line and get a buffer of next line by up-arrow.
// The changes of line buffers are stored until new history is created.
func (h *History) Newer(buf *Buffer) (new *Buffer, changed bool) {
if h.selected >= len(h.tmp)-1 {
return buf, false
}
h.tmp[h.selected] = buf.Text()
h.selected++
new = NewBuffer()
new.InsertText(h.tmp[h.selected], false, true)
return new, true
}
// NewHistory returns new history object.
func NewHistory() *History {
return &History{
histories: []string{},
tmp: []string{""},
selected: 0,
}
}

@ -0,0 +1,169 @@
package prompt
import "bytes"
// WinSize represents the width and height of terminal.
type WinSize struct {
Row uint16
Col uint16
}
// ConsoleParser is an interface to abstract input layer.
type ConsoleParser interface {
// Setup should be called before starting input
Setup() error
// TearDown should be called after stopping input
TearDown() error
// GetWinSize returns WinSize object to represent width and height of terminal.
GetWinSize() *WinSize
// Read returns byte array.
Read() ([]byte, error)
}
// GetKey returns Key correspond to input byte codes.
func GetKey(b []byte) Key {
for _, k := range ASCIISequences {
if bytes.Equal(k.ASCIICode, b) {
return k.Key
}
}
return NotDefined
}
// ASCIISequences holds mappings of the key and byte array.
var ASCIISequences = []*ASCIICode{
{Key: Escape, ASCIICode: []byte{0x1b}},
{Key: ControlSpace, ASCIICode: []byte{0x00}},
{Key: ControlA, ASCIICode: []byte{0x1}},
{Key: ControlB, ASCIICode: []byte{0x2}},
{Key: ControlC, ASCIICode: []byte{0x3}},
{Key: ControlD, ASCIICode: []byte{0x4}},
{Key: ControlE, ASCIICode: []byte{0x5}},
{Key: ControlF, ASCIICode: []byte{0x6}},
{Key: ControlG, ASCIICode: []byte{0x7}},
{Key: ControlH, ASCIICode: []byte{0x8}},
//{Key: ControlI, ASCIICode: []byte{0x9}},
//{Key: ControlJ, ASCIICode: []byte{0xa}},
{Key: ControlK, ASCIICode: []byte{0xb}},
{Key: ControlL, ASCIICode: []byte{0xc}},
{Key: ControlM, ASCIICode: []byte{0xd}},
{Key: ControlN, ASCIICode: []byte{0xe}},
{Key: ControlO, ASCIICode: []byte{0xf}},
{Key: ControlP, ASCIICode: []byte{0x10}},
{Key: ControlQ, ASCIICode: []byte{0x11}},
{Key: ControlR, ASCIICode: []byte{0x12}},
{Key: ControlS, ASCIICode: []byte{0x13}},
{Key: ControlT, ASCIICode: []byte{0x14}},
{Key: ControlU, ASCIICode: []byte{0x15}},
{Key: ControlV, ASCIICode: []byte{0x16}},
{Key: ControlW, ASCIICode: []byte{0x17}},
{Key: ControlX, ASCIICode: []byte{0x18}},
{Key: ControlY, ASCIICode: []byte{0x19}},
{Key: ControlZ, ASCIICode: []byte{0x1a}},
{Key: ControlBackslash, ASCIICode: []byte{0x1c}},
{Key: ControlSquareClose, ASCIICode: []byte{0x1d}},
{Key: ControlCircumflex, ASCIICode: []byte{0x1e}},
{Key: ControlUnderscore, ASCIICode: []byte{0x1f}},
{Key: Backspace, ASCIICode: []byte{0x7f}},
{Key: Up, ASCIICode: []byte{0x1b, 0x5b, 0x41}},
{Key: Down, ASCIICode: []byte{0x1b, 0x5b, 0x42}},
{Key: Right, ASCIICode: []byte{0x1b, 0x5b, 0x43}},
{Key: Left, ASCIICode: []byte{0x1b, 0x5b, 0x44}},
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x48}},
{Key: Home, ASCIICode: []byte{0x1b, 0x30, 0x48}},
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x46}},
{Key: End, ASCIICode: []byte{0x1b, 0x30, 0x46}},
{Key: Enter, ASCIICode: []byte{0xa}},
{Key: Delete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
{Key: ShiftDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x32, 0x7e}},
{Key: ControlDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x35, 0x7e}},
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
{Key: PageUp, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x7e}},
{Key: PageDown, ASCIICode: []byte{0x1b, 0x5b, 0x36, 0x7e}},
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x37, 0x7e}},
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x38, 0x7e}},
{Key: Tab, ASCIICode: []byte{0x9}},
{Key: BackTab, ASCIICode: []byte{0x1b, 0x5b, 0x5a}},
{Key: Insert, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50}},
{Key: F2, ASCIICode: []byte{0x1b, 0x4f, 0x51}},
{Key: F3, ASCIICode: []byte{0x1b, 0x4f, 0x52}},
{Key: F4, ASCIICode: []byte{0x1b, 0x4f, 0x53}},
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50, 0x41}}, // Linux console
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x42}}, // Linux console
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x43}}, // Linux console
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x44}}, // Linux console
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x45}}, // Linux console
{Key: F1, ASCIICode: []byte{0x1b, 0x5b, 0x11, 0x7e}}, // rxvt-unicode
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x12, 0x7e}}, // rxvt-unicode
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x13, 0x7e}}, // rxvt-unicode
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x14, 0x7e}}, // rxvt-unicode
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x35, 0x7e}},
{Key: F6, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x37, 0x7e}},
{Key: F7, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x38, 0x7e}},
{Key: F8, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x39, 0x7e}},
{Key: F9, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x30, 0x7e}},
{Key: F10, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x31, 0x7e}},
{Key: F11, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x32, 0x7e}},
{Key: F12, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x34, 0x7e, 0x8}},
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x25, 0x7e}},
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x26, 0x7e}},
{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x28, 0x7e}},
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x29, 0x7e}},
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
// Xterm
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x50}},
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x51}},
// &ASCIICode{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x52}}, // Conflicts with CPR response
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x52}},
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x15, 0x3b, 0x32, 0x7e}},
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x17, 0x3b, 0x32, 0x7e}},
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x18, 0x3b, 0x32, 0x7e}},
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x19, 0x3b, 0x32, 0x7e}},
{Key: F21, ASCIICode: []byte{0x1b, 0x5b, 0x20, 0x3b, 0x32, 0x7e}},
{Key: F22, ASCIICode: []byte{0x1b, 0x5b, 0x21, 0x3b, 0x32, 0x7e}},
{Key: F23, ASCIICode: []byte{0x1b, 0x5b, 0x23, 0x3b, 0x32, 0x7e}},
{Key: F24, ASCIICode: []byte{0x1b, 0x5b, 0x24, 0x3b, 0x32, 0x7e}},
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x41}},
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x42}},
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x43}},
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x35, 0x44}},
{Key: ShiftUp, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x41}},
{Key: ShiftDown, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x42}},
{Key: ShiftRight, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x43}},
{Key: ShiftLeft, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x3b, 0x32, 0x44}},
// Tmux sends following keystrokes when control+arrow is pressed, but for
// Emacs ansi-term sends the same sequences for normal arrow keys. Consider
// it a normal arrow press, because that's more important.
{Key: Up, ASCIICode: []byte{0x1b, 0x4f, 0x41}},
{Key: Down, ASCIICode: []byte{0x1b, 0x4f, 0x42}},
{Key: Right, ASCIICode: []byte{0x1b, 0x4f, 0x43}},
{Key: Left, ASCIICode: []byte{0x1b, 0x4f, 0x44}},
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x41}},
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x42}},
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x43}},
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x35, 0x44}},
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x63}}, // rxvt
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x64}}, // rxvt
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x45}}, // Xterm
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console
}

@ -0,0 +1,77 @@
// +build !windows
package prompt
import (
"syscall"
"git.tcp.direct/Mirrors/go-prompt/internal/term"
"golang.org/x/sys/unix"
)
const maxReadBytes = 1024
// PosixParser is a ConsoleParser implementation for POSIX environment.
type PosixParser struct {
fd int
origTermios syscall.Termios
}
// Setup should be called before starting input
func (t *PosixParser) Setup() error {
// Set NonBlocking mode because if syscall.Read block this goroutine, it cannot receive data from stopCh.
if err := syscall.SetNonblock(t.fd, true); err != nil {
return err
}
if err := term.SetRaw(t.fd); err != nil {
return err
}
return nil
}
// TearDown should be called after stopping input
func (t *PosixParser) TearDown() error {
if err := syscall.SetNonblock(t.fd, false); err != nil {
return err
}
if err := term.Restore(); err != nil {
return err
}
return nil
}
// Read returns byte array.
func (t *PosixParser) Read() ([]byte, error) {
buf := make([]byte, maxReadBytes)
n, err := syscall.Read(t.fd, buf)
if err != nil {
return []byte{}, err
}
return buf[:n], nil
}
// GetWinSize returns WinSize object to represent width and height of terminal.
func (t *PosixParser) GetWinSize() *WinSize {
ws, err := unix.IoctlGetWinsize(t.fd, unix.TIOCGWINSZ)
if err != nil {
panic(err)
}
return &WinSize{
Row: ws.Row,
Col: ws.Col,
}
}
var _ ConsoleParser = &PosixParser{}
// NewStandardInputParser returns ConsoleParser object to read from stdin.
func NewStandardInputParser() *PosixParser {
in, err := syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
if err != nil {
panic(err)
}
return &PosixParser{
fd: in,
}
}

@ -0,0 +1,83 @@
// +build windows
package prompt
import (
"errors"
"syscall"
"unicode/utf8"
"unsafe"
tty "github.com/mattn/go-tty"
)
const maxReadBytes = 1024
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
var procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
// WindowsParser is a ConsoleParser implementation for Win32 console.
type WindowsParser struct {
tty *tty.TTY
}
// Setup should be called before starting input
func (p *WindowsParser) Setup() error {
t, err := tty.Open()
if err != nil {
return err
}
p.tty = t
return nil
}
// TearDown should be called after stopping input
func (p *WindowsParser) TearDown() error {
return p.tty.Close()
}
// Read returns byte array.
func (p *WindowsParser) Read() ([]byte, error) {
var ev uint32
r0, _, err := procGetNumberOfConsoleInputEvents.Call(p.tty.Input().Fd(), uintptr(unsafe.Pointer(&ev)))
if r0 == 0 {
return nil, err
}
if ev == 0 {
return nil, errors.New("EAGAIN")
}
r, err := p.tty.ReadRune()
if err != nil {
return nil, err
}
buf := make([]byte, maxReadBytes)
n := utf8.EncodeRune(buf[:], r)
for p.tty.Buffered() && n < maxReadBytes {
r, err := p.tty.ReadRune()
if err != nil {
break
}
n += utf8.EncodeRune(buf[n:], r)
}
return buf[:n], nil
}
// GetWinSize returns WinSize object to represent width and height of terminal.
func (p *WindowsParser) GetWinSize() *WinSize {
w, h, err := p.tty.Size()
if err != nil {
panic(err)
}
return &WinSize{
Row: uint16(h),
Col: uint16(w),
}
}
// NewStandardInputParser returns ConsoleParser object to read from stdin.
func NewStandardInputParser() *WindowsParser {
return &WindowsParser{}
}

@ -0,0 +1,15 @@
package bisect
import "sort"
// Right to locate the insertion point for v in a to maintain sorted order.
func Right(a []int, v int) int {
return bisectRightRange(a, v, 0, len(a))
}
func bisectRightRange(a []int, v int, lo, hi int) int {
s := a[lo:hi]
return sort.Search(len(s), func(i int) bool {
return s[i] > v
})
}

@ -0,0 +1,55 @@
package debug
import (
"fmt"
"os"
)
const (
envAssertPanic = "GO_PROMPT_ENABLE_ASSERT"
)
var (
enableAssert bool
)
func init() {
if e := os.Getenv(envAssertPanic); e == "true" || e == "1" {
enableAssert = true
}
}
// Assert ensures expected condition.
func Assert(cond bool, msg interface{}) {
if cond {
return
}
if enableAssert {
panic(msg)
}
writeWithSync(2, "[ASSERT] "+toString(msg))
}
func toString(v interface{}) string {
switch a := v.(type) {
case func() string:
return a()
case string:
return a
case fmt.Stringer:
return a.String()
default:
return fmt.Sprintf("unexpected type, %t", v)
}
}
// AssertNoError ensures err is nil.
func AssertNoError(err error) {
if err == nil {
return
}
if enableAssert {
panic(err)
}
writeWithSync(2, "[ASSERT] "+err.Error())
}

@ -0,0 +1,52 @@
package debug
import (
"io/ioutil"
"log"
"os"
)
const (
envEnableLog = "GO_PROMPT_ENABLE_LOG"
logFileName = "go-prompt.log"
)
var (
logfile *os.File
logger *log.Logger
)
func init() {
if e := os.Getenv(envEnableLog); e == "true" || e == "1" {
var err error
logfile, err = os.OpenFile(logFileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err == nil {
logger = log.New(logfile, "", log.Llongfile)
return
}
}
logger = log.New(ioutil.Discard, "", log.Llongfile)
}
// Teardown to close logfile
func Teardown() {
if logfile == nil {
return
}
_ = logfile.Close()
}
func writeWithSync(calldepth int, msg string) {
calldepth++
if logfile == nil {
return
}
_ = logger.Output(calldepth, msg)
_ = logfile.Sync() // immediately write msg
}
// Log to output message
func Log(msg string) {
calldepth := 2
writeWithSync(calldepth, msg)
}

@ -0,0 +1,102 @@
package strings
import "unicode/utf8"
// IndexNotByte is similar with strings.IndexByte but showing the opposite behavior.
func IndexNotByte(s string, c byte) int {
n := len(s)
for i := 0; i < n; i++ {
if s[i] != c {
return i
}
}
return -1
}
// LastIndexNotByte is similar with strings.LastIndexByte but showing the opposite behavior.
func LastIndexNotByte(s string, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] != c {
return i
}
}
return -1
}
type asciiSet [8]uint32
func (as *asciiSet) notContains(c byte) bool {
return (as[c>>5] & (1 << uint(c&31))) == 0
}
func makeASCIISet(chars string) (as asciiSet, ok bool) {
for i := 0; i < len(chars); i++ {
c := chars[i]
if c >= utf8.RuneSelf {
return as, false
}
as[c>>5] |= 1 << uint(c&31)
}
return as, true
}
// IndexNotAny is similar with strings.IndexAny but showing the opposite behavior.
func IndexNotAny(s, chars string) int {
if len(chars) > 0 {
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := 0; i < len(s); i++ {
if as.notContains(s[i]) {
return i
}
}
return -1
}
}
LabelFirstLoop:
for i, c := range s {
for j, m := range chars {
if c != m && j == len(chars)-1 {
return i
} else if c != m {
continue
} else {
continue LabelFirstLoop
}
}
}
}
return -1
}
// LastIndexNotAny is similar with strings.LastIndexAny but showing the opposite behavior.
func LastIndexNotAny(s, chars string) int {
if len(chars) > 0 {
if len(s) > 8 {
if as, isASCII := makeASCIISet(chars); isASCII {
for i := len(s) - 1; i >= 0; i-- {
if as.notContains(s[i]) {
return i
}
}
return -1
}
}
LabelFirstLoop:
for i := len(s); i > 0; {
r, size := utf8.DecodeLastRuneInString(s[:i])
i -= size
for j, m := range chars {
if r != m && j == len(chars)-1 {
return i
} else if r != m {
continue
} else {
continue LabelFirstLoop
}
}
}
}
return -1
}

@ -0,0 +1,29 @@
// +build !windows
package term
import (
"syscall"
"github.com/pkg/term/termios"
"golang.org/x/sys/unix"
)
// SetRaw put terminal into a raw mode
func SetRaw(fd int) error {
n, err := getOriginalTermios(fd)
if err != nil {
return err
}
n.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK |
syscall.ISTRIP | syscall.INLCR | syscall.IGNCR |
syscall.ICRNL | syscall.IXON
n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG | syscall.ECHONL
n.Cflag &^= syscall.CSIZE | syscall.PARENB
n.Cflag |= syscall.CS8 // Set to 8-bit wide. Typical value for displaying characters.
n.Cc[syscall.VMIN] = 1
n.Cc[syscall.VTIME] = 0
return termios.Tcsetattr(uintptr(fd), termios.TCSANOW, (*unix.Termios)(&n))
}

@ -0,0 +1,37 @@
//go:build !windows
// +build !windows
package term
import (
"sync"
"github.com/pkg/term/termios"
"golang.org/x/sys/unix"
)
var (
saveTermios unix.Termios
saveTermiosFD int
saveTermiosOnce sync.Once
)
func getOriginalTermios(fd int) (unix.Termios, error) {
var err error
saveTermiosOnce.Do(func() {
saveTermiosFD = fd
var saveTermiosPtr *unix.Termios
termios.Tcgetattr(uintptr(fd), saveTermiosPtr)
saveTermios = *saveTermiosPtr
})
return saveTermios, err
}
// Restore terminal's mode.
func Restore() error {
o, err := getOriginalTermios(saveTermiosFD)
if err != nil {
return err
}
return termios.Tcsetattr(uintptr(saveTermiosFD), termios.TCSANOW, &o)
}

@ -0,0 +1,128 @@
// Code generated by hand; DO NOT EDIT.
// This is a little bit stupid, but there are many public constants which is no value for writing godoc comment.
package prompt
// Key is the type express the key inserted from user.
//go:generate stringer -type=Key
type Key int
// ASCIICode is the type contains Key and it's ascii byte array.
type ASCIICode struct {
Key Key
ASCIICode []byte
}
const (
Escape Key = iota
ControlA
ControlB
ControlC
ControlD
ControlE
ControlF
ControlG
ControlH
ControlI
ControlJ
ControlK
ControlL
ControlM
ControlN
ControlO
ControlP
ControlQ
ControlR
ControlS
ControlT
ControlU
ControlV
ControlW
ControlX
ControlY
ControlZ
ControlSpace
ControlBackslash
ControlSquareClose
ControlCircumflex
ControlUnderscore
ControlLeft
ControlRight
ControlUp
ControlDown
Up
Down
Right
Left
ShiftLeft
ShiftUp
ShiftDown
ShiftRight
Home
End
Delete
ShiftDelete
ControlDelete
PageUp
PageDown
BackTab
Insert
Backspace
// Aliases.
Tab
Enter
// Actually Enter equals ControlM, not ControlJ,
// However, in prompt_toolkit, we made the mistake of translating
// \r into \n during the input, so everyone is now handling the
// enter key by binding ControlJ.
// From now on, it's better to bind `ASCII_SEQUENCES.Enter` everywhere,
// because that's future compatible, and will still work when we
// stop replacing \r by \n.
F1
F2
F3
F4
F5
F6
F7
F8
F9
F10
F11
F12
F13
F14
F15
F16
F17
F18
F19
F20
F21
F22
F23
F24
// Matches any key.
Any
// Special
CPRResponse
Vt100MouseEvent
WindowsMouseEvent
BracketedPaste
// Key which is ignored. (The key binding for this key should not do anything.)
Ignore
// Key is not defined
NotDefined
)

@ -0,0 +1,59 @@
package prompt
// KeyBindFunc receives buffer and processed it.
type KeyBindFunc func(*Buffer)
// KeyBind represents which key should do what operation.
type KeyBind struct {
Key Key
Fn KeyBindFunc
}
// ASCIICodeBind represents which []byte should do what operation
type ASCIICodeBind struct {
ASCIICode []byte
Fn KeyBindFunc
}
// KeyBindMode to switch a key binding flexibly.
type KeyBindMode string
const (
// CommonKeyBind is a mode without any keyboard shortcut
CommonKeyBind KeyBindMode = "common"
// EmacsKeyBind is a mode to use emacs-like keyboard shortcut
EmacsKeyBind KeyBindMode = "emacs"
)
var commonKeyBindings = []KeyBind{
// Go to the End of the line
{
Key: End,
Fn: GoLineEnd,
},
// Go to the beginning of the line
{
Key: Home,
Fn: GoLineBeginning,
},
// Delete character under the cursor
{
Key: Delete,
Fn: DeleteChar,
},
// Backspace
{
Key: Backspace,
Fn: DeleteBeforeChar,
},
// Right allow: Forward one character
{
Key: Right,
Fn: GoRightChar,
},
// Left allow: Backward one character
{
Key: Left,
Fn: GoLeftChar,
},
}

@ -0,0 +1,48 @@
package prompt
// GoLineEnd Go to the End of the line
func GoLineEnd(buf *Buffer) {
x := []rune(buf.Document().TextAfterCursor())
buf.CursorRight(len(x))
}
// GoLineBeginning Go to the beginning of the line
func GoLineBeginning(buf *Buffer) {
x := []rune(buf.Document().TextBeforeCursor())
buf.CursorLeft(len(x))
}
// DeleteChar Delete character under the cursor
func DeleteChar(buf *Buffer) {
buf.Delete(1)
}
// DeleteWord Delete word before the cursor
func DeleteWord(buf *Buffer) {
buf.DeleteBeforeCursor(len([]rune(buf.Document().TextBeforeCursor())) - buf.Document().FindStartOfPreviousWordWithSpace())
}
// DeleteBeforeChar Go to Backspace
func DeleteBeforeChar(buf *Buffer) {
buf.DeleteBeforeCursor(1)
}
// GoRightChar Forward one character
func GoRightChar(buf *Buffer) {
buf.CursorRight(1)
}
// GoLeftChar Backward one character
func GoLeftChar(buf *Buffer) {
buf.CursorLeft(1)
}
// GoRightWord Forward one word
func GoRightWord(buf *Buffer) {
buf.CursorRight(buf.Document().FindEndOfCurrentWordWithSpace())
}
// GoLeftWord Backward one word
func GoLeftWord(buf *Buffer) {
buf.CursorLeft(len([]rune(buf.Document().TextBeforeCursor())) - buf.Document().FindStartOfPreviousWordWithSpace())
}

@ -0,0 +1,16 @@
// Code generated by "stringer -type=Key"; DO NOT EDIT.
package prompt
import "strconv"
const _Key_name = "EscapeControlAControlBControlCControlDControlEControlFControlGControlHControlIControlJControlKControlLControlMControlNControlOControlPControlQControlRControlSControlTControlUControlVControlWControlXControlYControlZControlSpaceControlBackslashControlSquareCloseControlCircumflexControlUnderscoreControlLeftControlRightControlUpControlDownUpDownRightLeftShiftLeftShiftUpShiftDownShiftRightHomeEndDeleteShiftDeleteControlDeletePageUpPageDownBackTabInsertBackspaceTabEnterF1F2F3F4F5F6F7F8F9F10F11F12F13F14F15F16F17F18F19F20F21F22F23F24AnyCPRResponseVt100MouseEventWindowsMouseEventBracketedPasteIgnoreNotDefined"
var _Key_index = [...]uint16{0, 6, 14, 22, 30, 38, 46, 54, 62, 70, 78, 86, 94, 102, 110, 118, 126, 134, 142, 150, 158, 166, 174, 182, 190, 198, 206, 214, 226, 242, 260, 277, 294, 305, 317, 326, 337, 339, 343, 348, 352, 361, 368, 377, 387, 391, 394, 400, 411, 424, 430, 438, 445, 451, 460, 463, 468, 470, 472, 474, 476, 478, 480, 482, 484, 486, 489, 492, 495, 498, 501, 504, 507, 510, 513, 516, 519, 522, 525, 528, 531, 534, 545, 560, 577, 591, 597, 607}
func (i Key) String() string {
if i < 0 || i >= Key(len(_Key_index)-1) {
return "Key(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Key_name[_Key_index[i]:_Key_index[i+1]]
}

@ -0,0 +1,310 @@
package prompt
// Option is the type to replace default parameters.
// prompt.New accepts any number of options (this is functional option pattern).
type Option func(prompt *Prompt) error
// OptionParser to set a custom ConsoleParser object. An argument should implement ConsoleParser interface.
func OptionParser(x ConsoleParser) Option {
return func(p *Prompt) error {
p.in = x
return nil
}
}
// OptionWriter to set a custom ConsoleWriter object. An argument should implement ConsoleWriter interface.
func OptionWriter(x ConsoleWriter) Option {
return func(p *Prompt) error {
registerConsoleWriter(x)
p.renderer.out = x
return nil
}
}
// OptionTitle to set title displayed at the header bar of terminal.
func OptionTitle(x string) Option {
return func(p *Prompt) error {
p.renderer.title = x
return nil
}
}
// OptionPrefix to set prefix string.
func OptionPrefix(x string) Option {
return func(p *Prompt) error {
p.renderer.prefix = x
return nil
}
}
// OptionInitialBufferText to set the initial buffer text
func OptionInitialBufferText(x string) Option {
return func(p *Prompt) error {
p.buf.InsertText(x, false, true)
return nil
}
}
// OptionCompletionWordSeparator to set word separators. Enable only ' ' if empty.
func OptionCompletionWordSeparator(x string) Option {
return func(p *Prompt) error {
p.completion.wordSeparator = x
return nil
}
}
// OptionLivePrefix to change the prefix dynamically by callback function
func OptionLivePrefix(f func() (prefix string, useLivePrefix bool)) Option {
return func(p *Prompt) error {
p.renderer.livePrefixCallback = f
return nil
}
}
// OptionPrefixTextColor change a text color of prefix string
func OptionPrefixTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.prefixTextColor = x
return nil
}
}
// OptionPrefixBackgroundColor to change a background color of prefix string
func OptionPrefixBackgroundColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.prefixBGColor = x
return nil
}
}
// OptionInputTextColor to change a color of text which is input by user
func OptionInputTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.inputTextColor = x
return nil
}
}
// OptionInputBGColor to change a color of background which is input by user
func OptionInputBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.inputBGColor = x
return nil
}
}
// OptionPreviewSuggestionTextColor to change a text color which is completed
func OptionPreviewSuggestionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.previewSuggestionTextColor = x
return nil
}
}
// OptionPreviewSuggestionBGColor to change a background color which is completed
func OptionPreviewSuggestionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.previewSuggestionBGColor = x
return nil
}
}
// OptionSuggestionTextColor to change a text color in drop down suggestions.
func OptionSuggestionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.suggestionTextColor = x
return nil
}
}
// OptionSuggestionBGColor change a background color in drop down suggestions.
func OptionSuggestionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.suggestionBGColor = x
return nil
}
}
// OptionSelectedSuggestionTextColor to change a text color for completed text which is selected inside suggestions drop down box.
func OptionSelectedSuggestionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.selectedSuggestionTextColor = x
return nil
}
}
// OptionSelectedSuggestionBGColor to change a background color for completed text which is selected inside suggestions drop down box.
func OptionSelectedSuggestionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.selectedSuggestionBGColor = x
return nil
}
}
// OptionDescriptionTextColor to change a background color of description text in drop down suggestions.
func OptionDescriptionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.descriptionTextColor = x
return nil
}
}
// OptionDescriptionBGColor to change a background color of description text in drop down suggestions.
func OptionDescriptionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.descriptionBGColor = x
return nil
}
}
// OptionSelectedDescriptionTextColor to change a text color of description which is selected inside suggestions drop down box.
func OptionSelectedDescriptionTextColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.selectedDescriptionTextColor = x
return nil
}
}
// OptionSelectedDescriptionBGColor to change a background color of description which is selected inside suggestions drop down box.
func OptionSelectedDescriptionBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.selectedDescriptionBGColor = x
return nil
}
}
// OptionScrollbarThumbColor to change a thumb color on scrollbar.
func OptionScrollbarThumbColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.scrollbarThumbColor = x
return nil
}
}
// OptionScrollbarBGColor to change a background color of scrollbar.
func OptionScrollbarBGColor(x Color) Option {
return func(p *Prompt) error {
p.renderer.scrollbarBGColor = x
return nil
}
}
// OptionMaxSuggestion specify the max number of displayed suggestions.
func OptionMaxSuggestion(x uint16) Option {
return func(p *Prompt) error {
p.completion.max = x
return nil
}
}
// OptionHistory to set history expressed by string array.
func OptionHistory(x []string) Option {
return func(p *Prompt) error {
p.history.histories = x
p.history.Clear()
return nil
}
}
// OptionSwitchKeyBindMode set a key bind mode.
func OptionSwitchKeyBindMode(m KeyBindMode) Option {
return func(p *Prompt) error {
p.keyBindMode = m
return nil
}
}
// OptionCompletionOnDown allows for Down arrow key to trigger completion.
func OptionCompletionOnDown() Option {
return func(p *Prompt) error {
p.completionOnDown = true
return nil
}
}
// SwitchKeyBindMode to set a key bind mode.
// Deprecated: Please use OptionSwitchKeyBindMode.
var SwitchKeyBindMode = OptionSwitchKeyBindMode
// OptionAddKeyBind to set a custom key bind.
func OptionAddKeyBind(b ...KeyBind) Option {
return func(p *Prompt) error {
p.keyBindings = append(p.keyBindings, b...)
return nil
}
}
// OptionAddASCIICodeBind to set a custom key bind.
func OptionAddASCIICodeBind(b ...ASCIICodeBind) Option {
return func(p *Prompt) error {
p.ASCIICodeBindings = append(p.ASCIICodeBindings, b...)
return nil
}
}
// OptionShowCompletionAtStart to set completion window is open at start.
func OptionShowCompletionAtStart() Option {
return func(p *Prompt) error {
p.completion.showAtStart = true
return nil
}
}
// OptionBreakLineCallback to run a callback at every break line
func OptionBreakLineCallback(fn func(*Document)) Option {
return func(p *Prompt) error {
p.renderer.breakLineCallback = fn
return nil
}
}
// OptionSetExitCheckerOnInput set an exit function which checks if go-prompt exits its Run loop
func OptionSetExitCheckerOnInput(fn ExitChecker) Option {
return func(p *Prompt) error {
p.exitChecker = fn
return nil
}
}
// New returns a Prompt with powerful auto-completion.
func New(executor Executor, completer Completer, opts ...Option) *Prompt {
defaultWriter := NewStdoutWriter()
registerConsoleWriter(defaultWriter)
pt := &Prompt{
in: NewStandardInputParser(),
renderer: &Render{
prefix: "> ",
out: defaultWriter,
livePrefixCallback: func() (string, bool) { return "", false },
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,
inputBGColor: DefaultColor,
previewSuggestionTextColor: Green,
previewSuggestionBGColor: DefaultColor,
suggestionTextColor: White,
suggestionBGColor: Cyan,
selectedSuggestionTextColor: Black,
selectedSuggestionBGColor: Turquoise,
descriptionTextColor: Black,
descriptionBGColor: Turquoise,
selectedDescriptionTextColor: White,
selectedDescriptionBGColor: Cyan,
scrollbarThumbColor: DarkGray,
scrollbarBGColor: Cyan,
},
buf: NewBuffer(),
executor: executor,
history: NewHistory(),
completion: NewCompletionManager(completer, 6),
keyBindMode: EmacsKeyBind, // All the above assume that bash is running in the default Emacs setting
}
for _, opt := range opts {
if err := opt(pt); err != nil {
panic(err)
}
}
return pt
}

@ -0,0 +1,161 @@
package prompt
import "sync"
var (
consoleWriterMu sync.Mutex
consoleWriter ConsoleWriter
)
func registerConsoleWriter(f ConsoleWriter) {
consoleWriterMu.Lock()
defer consoleWriterMu.Unlock()
consoleWriter = f
}
// DisplayAttribute represents display attributes like Blinking, Bold, Italic and so on.
type DisplayAttribute int
const (
// DisplayReset reset all display attributes.
DisplayReset DisplayAttribute = iota
// DisplayBold set bold or increases intensity.
DisplayBold
// DisplayLowIntensity decreases intensity. Not widely supported.
DisplayLowIntensity
// DisplayItalic set italic. Not widely supported.
DisplayItalic
// DisplayUnderline set underline
DisplayUnderline
// DisplayBlink set blink (less than 150 per minute).
DisplayBlink
// DisplayRapidBlink set blink (more than 150 per minute). Not widely supported.
DisplayRapidBlink
// DisplayReverse swap foreground and background colors.
DisplayReverse
// DisplayInvisible set invisible. Not widely supported.
DisplayInvisible
// DisplayCrossedOut set characters legible, but marked for deletion. Not widely supported.
DisplayCrossedOut
// DisplayDefaultFont set primary(default) font
DisplayDefaultFont
)
// Color represents color on terminal.
type Color int
const (
// DefaultColor represents a default color.
DefaultColor Color = iota
// Low intensity
// Black represents a black.
Black
// DarkRed represents a dark red.
DarkRed
// DarkGreen represents a dark green.
DarkGreen
// Brown represents a brown.
Brown
// DarkBlue represents a dark blue.
DarkBlue
// Purple represents a purple.
Purple
// Cyan represents a cyan.
Cyan
// LightGray represents a light gray.
LightGray
// High intensity
// DarkGray represents a dark gray.
DarkGray
// Red represents a red.
Red
// Green represents a green.
Green
// Yellow represents a yellow.
Yellow
// Blue represents a blue.
Blue
// Fuchsia represents a fuchsia.
Fuchsia
// Turquoise represents a turquoise.
Turquoise
// White represents a white.
White
)
// ConsoleWriter is an interface to abstract output layer.
type ConsoleWriter interface {
/* Write */
// WriteRaw to write raw byte array.
WriteRaw(data []byte)
// Write to write safety byte array by removing control sequences.
Write(data []byte)
// WriteStr to write raw string.
WriteRawStr(data string)
// WriteStr to write safety string by removing control sequences.
WriteStr(data string)
// Flush to flush buffer.
Flush() error
/* Erasing */
// EraseScreen erases the screen with the background colour and moves the cursor to home.
EraseScreen()
// EraseUp erases the screen from the current line up to the top of the screen.
EraseUp()
// EraseDown erases the screen from the current line down to the bottom of the screen.
EraseDown()
// EraseStartOfLine erases from the current cursor position to the start of the current line.
EraseStartOfLine()
// EraseEndOfLine erases from the current cursor position to the end of the current line.
EraseEndOfLine()
// EraseLine erases the entire current line.
EraseLine()
/* Cursor */
// ShowCursor stops blinking cursor and show.
ShowCursor()
// HideCursor hides cursor.
HideCursor()
// CursorGoTo sets the cursor position where subsequent text will begin.
CursorGoTo(row, col int)
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
CursorUp(n int)
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
CursorDown(n int)
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
CursorForward(n int)
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
CursorBackward(n int)
// AskForCPR asks for a cursor position report (CPR).
AskForCPR()
// SaveCursor saves current cursor position.
SaveCursor()
// UnSaveCursor restores cursor position after a Save Cursor.
UnSaveCursor()
/* Scrolling */
// ScrollDown scrolls display down one line.
ScrollDown()
// ScrollUp scroll display up one line.
ScrollUp()
/* Title */
// SetTitle sets a title of terminal window.
SetTitle(title string)
// ClearTitle clears a title of terminal window.
ClearTitle()
/* Font */
// SetColor sets text and background colors. and specify whether text is bold.
SetColor(fg, bg Color, bold bool)
}

@ -0,0 +1,67 @@
// +build !windows
package prompt
import (
"syscall"
)
const flushMaxRetryCount = 3
// PosixWriter is a ConsoleWriter implementation for POSIX environment.
// To control terminal emulator, this outputs VT100 escape sequences.
type PosixWriter struct {
VT100Writer
fd int
}
// Flush to flush buffer
func (w *PosixWriter) Flush() error {
l := len(w.buffer)
offset := 0
retry := 0
for {
n, err := syscall.Write(w.fd, w.buffer[offset:])
if err != nil {
if retry < flushMaxRetryCount {
retry++
continue
}
return err
}
offset += n
if offset == l {
break
}
}
w.buffer = []byte{}
return nil
}
var _ ConsoleWriter = &PosixWriter{}
var (
// NewStandardOutputWriter returns ConsoleWriter object to write to stdout.
// This generates VT100 escape sequences because almost terminal emulators
// in POSIX OS built on top of a VT100 specification.
// Deprecated: Please use NewStdoutWriter
NewStandardOutputWriter = NewStdoutWriter
)
// NewStdoutWriter returns ConsoleWriter object to write to stdout.
// This generates VT100 escape sequences because almost terminal emulators
// in POSIX OS built on top of a VT100 specification.
func NewStdoutWriter() ConsoleWriter {
return &PosixWriter{
fd: syscall.Stdout,
}
}
// NewStderrWriter returns ConsoleWriter object to write to stderr.
// This generates VT100 escape sequences because almost terminal emulators
// in POSIX OS built on top of a VT100 specification.
func NewStderrWriter() ConsoleWriter {
return &PosixWriter{
fd: syscall.Stderr,
}
}

@ -0,0 +1,310 @@
package prompt
import (
"bytes"
"strconv"
)
// VT100Writer generates VT100 escape sequences.
type VT100Writer struct {
buffer []byte
}
// WriteRaw to write raw byte array
func (w *VT100Writer) WriteRaw(data []byte) {
w.buffer = append(w.buffer, data...)
}
// Write to write safety byte array by removing control sequences.
func (w *VT100Writer) Write(data []byte) {
w.WriteRaw(bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1))
}
// WriteRawStr to write raw string
func (w *VT100Writer) WriteRawStr(data string) {
w.WriteRaw([]byte(data))
}
// WriteStr to write safety string by removing control sequences.
func (w *VT100Writer) WriteStr(data string) {
w.Write([]byte(data))
}
/* Erase */
// EraseScreen erases the screen with the background colour and moves the cursor to home.
func (w *VT100Writer) EraseScreen() {
w.WriteRaw([]byte{0x1b, '[', '2', 'J'})
}
// EraseUp erases the screen from the current line up to the top of the screen.
func (w *VT100Writer) EraseUp() {
w.WriteRaw([]byte{0x1b, '[', '1', 'J'})
}
// EraseDown erases the screen from the current line down to the bottom of the screen.
func (w *VT100Writer) EraseDown() {
w.WriteRaw([]byte{0x1b, '[', 'J'})
}
// EraseStartOfLine erases from the current cursor position to the start of the current line.
func (w *VT100Writer) EraseStartOfLine() {
w.WriteRaw([]byte{0x1b, '[', '1', 'K'})
}
// EraseEndOfLine erases from the current cursor position to the end of the current line.
func (w *VT100Writer) EraseEndOfLine() {
w.WriteRaw([]byte{0x1b, '[', 'K'})
}
// EraseLine erases the entire current line.
func (w *VT100Writer) EraseLine() {
w.WriteRaw([]byte{0x1b, '[', '2', 'K'})
}
/* Cursor */
// ShowCursor stops blinking cursor and show.
func (w *VT100Writer) ShowCursor() {
w.WriteRaw([]byte{0x1b, '[', '?', '1', '2', 'l', 0x1b, '[', '?', '2', '5', 'h'})
}
// HideCursor hides cursor.
func (w *VT100Writer) HideCursor() {
w.WriteRaw([]byte{0x1b, '[', '?', '2', '5', 'l'})
}
// CursorGoTo sets the cursor position where subsequent text will begin.
func (w *VT100Writer) CursorGoTo(row, col int) {
if row == 0 && col == 0 {
// If no row/column parameters are provided (ie. <ESC>[H), the cursor will move to the home position.
w.WriteRaw([]byte{0x1b, '[', 'H'})
return
}
r := strconv.Itoa(row)
c := strconv.Itoa(col)
w.WriteRaw([]byte{0x1b, '['})
w.WriteRaw([]byte(r))
w.WriteRaw([]byte{';'})
w.WriteRaw([]byte(c))
w.WriteRaw([]byte{'H'})
}
// CursorUp moves the cursor up by 'n' rows; the default count is 1.
func (w *VT100Writer) CursorUp(n int) {
if n == 0 {
return
} else if n < 0 {
w.CursorDown(-n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, '['})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{'A'})
}
// CursorDown moves the cursor down by 'n' rows; the default count is 1.
func (w *VT100Writer) CursorDown(n int) {
if n == 0 {
return
} else if n < 0 {
w.CursorUp(-n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, '['})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{'B'})
}
// CursorForward moves the cursor forward by 'n' columns; the default count is 1.
func (w *VT100Writer) CursorForward(n int) {
if n == 0 {
return
} else if n < 0 {
w.CursorBackward(-n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, '['})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{'C'})
}
// CursorBackward moves the cursor backward by 'n' columns; the default count is 1.
func (w *VT100Writer) CursorBackward(n int) {
if n == 0 {
return
} else if n < 0 {
w.CursorForward(-n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, '['})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{'D'})
}
// AskForCPR asks for a cursor position report (CPR).
func (w *VT100Writer) AskForCPR() {
// CPR: Cursor Position Request.
w.WriteRaw([]byte{0x1b, '[', '6', 'n'})
}
// SaveCursor saves current cursor position.
func (w *VT100Writer) SaveCursor() {
w.WriteRaw([]byte{0x1b, '[', 's'})
}
// UnSaveCursor restores cursor position after a Save Cursor.
func (w *VT100Writer) UnSaveCursor() {
w.WriteRaw([]byte{0x1b, '[', 'u'})
}
/* Scrolling */
// ScrollDown scrolls display down one line.
func (w *VT100Writer) ScrollDown() {
w.WriteRaw([]byte{0x1b, 'D'})
}
// ScrollUp scroll display up one line.
func (w *VT100Writer) ScrollUp() {
w.WriteRaw([]byte{0x1b, 'M'})
}
/* Title */
// SetTitle sets a title of terminal window.
func (w *VT100Writer) SetTitle(title string) {
titleBytes := []byte(title)
patterns := []struct {
from []byte
to []byte
}{
{
from: []byte{0x13},
to: []byte{},
},
{
from: []byte{0x07},
to: []byte{},
},
}
for i := range patterns {
titleBytes = bytes.Replace(titleBytes, patterns[i].from, patterns[i].to, -1)
}
w.WriteRaw([]byte{0x1b, ']', '2', ';'})
w.WriteRaw(titleBytes)
w.WriteRaw([]byte{0x07})
}
// ClearTitle clears a title of terminal window.
func (w *VT100Writer) ClearTitle() {
w.WriteRaw([]byte{0x1b, ']', '2', ';', 0x07})
}
/* Font */
// SetColor sets text and background colors. and specify whether text is bold.
func (w *VT100Writer) SetColor(fg, bg Color, bold bool) {
if bold {
w.SetDisplayAttributes(fg, bg, DisplayBold)
} else {
// If using `DisplayDefualt`, it will be broken in some environment.
// Details are https://git.tcp.direct/Mirrors/go-prompt/pull/85
w.SetDisplayAttributes(fg, bg, DisplayReset)
}
}
// SetDisplayAttributes to set VT100 display attributes.
func (w *VT100Writer) SetDisplayAttributes(fg, bg Color, attrs ...DisplayAttribute) {
w.WriteRaw([]byte{0x1b, '['}) // control sequence introducer
defer w.WriteRaw([]byte{'m'}) // final character
var separator byte = ';'
for i := range attrs {
p, ok := displayAttributeParameters[attrs[i]]
if !ok {
continue
}
w.WriteRaw(p)
w.WriteRaw([]byte{separator})
}
f, ok := foregroundANSIColors[fg]
if !ok {
f = foregroundANSIColors[DefaultColor]
}
w.WriteRaw(f)
w.WriteRaw([]byte{separator})
b, ok := backgroundANSIColors[bg]
if !ok {
b = backgroundANSIColors[DefaultColor]
}
w.WriteRaw(b)
}
var displayAttributeParameters = map[DisplayAttribute][]byte{
DisplayReset: {'0'},
DisplayBold: {'1'},
DisplayLowIntensity: {'2'},
DisplayItalic: {'3'},
DisplayUnderline: {'4'},
DisplayBlink: {'5'},
DisplayRapidBlink: {'6'},
DisplayReverse: {'7'},
DisplayInvisible: {'8'},
DisplayCrossedOut: {'9'},
DisplayDefaultFont: {'1', '0'},
}
var foregroundANSIColors = map[Color][]byte{
DefaultColor: {'3', '9'},
// Low intensity.
Black: {'3', '0'},
DarkRed: {'3', '1'},
DarkGreen: {'3', '2'},
Brown: {'3', '3'},
DarkBlue: {'3', '4'},
Purple: {'3', '5'},
Cyan: {'3', '6'},
LightGray: {'3', '7'},
// High intensity.
DarkGray: {'9', '0'},
Red: {'9', '1'},
Green: {'9', '2'},
Yellow: {'9', '3'},
Blue: {'9', '4'},
Fuchsia: {'9', '5'},
Turquoise: {'9', '6'},
White: {'9', '7'},
}
var backgroundANSIColors = map[Color][]byte{
DefaultColor: {'4', '9'},
// Low intensity.
Black: {'4', '0'},
DarkRed: {'4', '1'},
DarkGreen: {'4', '2'},
Brown: {'4', '3'},
DarkBlue: {'4', '4'},
Purple: {'4', '5'},
Cyan: {'4', '6'},
LightGray: {'4', '7'},
// High intensity
DarkGray: {'1', '0', '0'},
Red: {'1', '0', '1'},
Green: {'1', '0', '2'},
Yellow: {'1', '0', '3'},
Blue: {'1', '0', '4'},
Fuchsia: {'1', '0', '5'},
Turquoise: {'1', '0', '6'},
White: {'1', '0', '7'},
}

@ -0,0 +1,49 @@
// +build windows
package prompt
import (
"io"
colorable "github.com/mattn/go-colorable"
)
// WindowsWriter is a ConsoleWriter implementation for Win32 console.
// Output is converted from VT100 escape sequences by mattn/go-colorable.
type WindowsWriter struct {
VT100Writer
out io.Writer
}
// Flush to flush buffer
func (w *WindowsWriter) Flush() error {
_, err := w.out.Write(w.buffer)
if err != nil {
return err
}
w.buffer = []byte{}
return nil
}
var _ ConsoleWriter = &WindowsWriter{}
var (
// NewStandardOutputWriter is Deprecated: Please use NewStdoutWriter
NewStandardOutputWriter = NewStdoutWriter
)
// NewStdoutWriter returns ConsoleWriter object to write to stdout.
// This generates win32 control sequences.
func NewStdoutWriter() ConsoleWriter {
return &WindowsWriter{
out: colorable.NewColorableStdout(),
}
}
// NewStderrWriter returns ConsoleWriter object to write to stderr.
// This generates win32 control sequences.
func NewStderrWriter() ConsoleWriter {
return &WindowsWriter{
out: colorable.NewColorableStderr(),
}
}

@ -0,0 +1,296 @@
package prompt
import (
"bytes"
"os"
"time"
"git.tcp.direct/Mirrors/go-prompt/internal/debug"
)
// Executor is called when user input something text.
type Executor func(string)
// ExitChecker is called after user input to check if prompt must stop and exit go-prompt Run loop.
// User input means: selecting/typing an entry, then, if said entry content matches the ExitChecker function criteria:
// - immediate exit (if breakline is false) without executor called
// - exit after typing <return> (meaning breakline is true), and the executor is called first, before exit.
// Exit means exit go-prompt (not the overall Go program)
type ExitChecker func(in string, breakline bool) bool
// Completer should return the suggest item from Document.
type Completer func(Document) []Suggest
// Prompt is core struct of go-prompt.
type Prompt struct {
in ConsoleParser
buf *Buffer
renderer *Render
executor Executor
history *History
completion *CompletionManager
keyBindings []KeyBind
ASCIICodeBindings []ASCIICodeBind
keyBindMode KeyBindMode
completionOnDown bool
exitChecker ExitChecker
skipTearDown bool
}
// Exec is the struct contains user input context.
type Exec struct {
input string
}
// Run starts prompt.
func (p *Prompt) Run() {
p.skipTearDown = false
defer debug.Teardown()
debug.Log("start prompt")
p.setUp()
defer p.tearDown()
if p.completion.showAtStart {
p.completion.Update(*p.buf.Document())
}
p.renderer.Render(p.buf, p.completion)
bufCh := make(chan []byte, 128)
stopReadBufCh := make(chan struct{})
go p.readBuffer(bufCh, stopReadBufCh)
exitCh := make(chan int)
winSizeCh := make(chan *WinSize)
stopHandleSignalCh := make(chan struct{})
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
for {
select {
case b := <-bufCh:
if shouldExit, e := p.feed(b); shouldExit {
p.renderer.BreakLine(p.buf)
stopReadBufCh <- struct{}{}
stopHandleSignalCh <- struct{}{}
return
} else if e != nil {
// Stop goroutine to run readBuffer function
stopReadBufCh <- struct{}{}
stopHandleSignalCh <- struct{}{}
// Unset raw mode
// Reset to Blocking mode because returned EAGAIN when still set non-blocking mode.
debug.AssertNoError(p.in.TearDown())
p.executor(e.input)
p.completion.Update(*p.buf.Document())
p.renderer.Render(p.buf, p.completion)
if p.exitChecker != nil && p.exitChecker(e.input, true) {
p.skipTearDown = true
return
}
// Set raw mode
debug.AssertNoError(p.in.Setup())
go p.readBuffer(bufCh, stopReadBufCh)
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
} else {
p.completion.Update(*p.buf.Document())
p.renderer.Render(p.buf, p.completion)
}
case w := <-winSizeCh:
p.renderer.UpdateWinSize(w)
p.renderer.Render(p.buf, p.completion)
case code := <-exitCh:
p.renderer.BreakLine(p.buf)
p.tearDown()
os.Exit(code)
default:
time.Sleep(10 * time.Millisecond)
}
}
}
func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
key := GetKey(b)
p.buf.lastKeyStroke = key
// completion
completing := p.completion.Completing()
p.handleCompletionKeyBinding(key, completing)
switch key {
case Enter, ControlJ, ControlM:
p.renderer.BreakLine(p.buf)
exec = &Exec{input: p.buf.Text()}
p.buf = NewBuffer()
if exec.input != "" {
p.history.Add(exec.input)
}
case ControlC:
p.renderer.BreakLine(p.buf)
p.buf = NewBuffer()
p.history.Clear()
case Up, ControlP:
if !completing { // Don't use p.completion.Completing() because it takes double operation when switch to selected=-1.
if newBuf, changed := p.history.Older(p.buf); changed {
p.buf = newBuf
}
}
case Down, ControlN:
if !completing { // Don't use p.completion.Completing() because it takes double operation when switch to selected=-1.
if newBuf, changed := p.history.Newer(p.buf); changed {
p.buf = newBuf
}
return
}
case ControlD:
if p.buf.Text() == "" {
shouldExit = true
return
}
case NotDefined:
if p.handleASCIICodeBinding(b) {
return
}
p.buf.InsertText(string(b), false, true)
}
shouldExit = p.handleKeyBinding(key)
return
}
func (p *Prompt) handleCompletionKeyBinding(key Key, completing bool) {
switch key {
case Down:
if completing || p.completionOnDown {
p.completion.Next()
}
case Tab, ControlI:
p.completion.Next()
case Up:
if completing {
p.completion.Previous()
}
case BackTab:
p.completion.Previous()
default:
if s, ok := p.completion.GetSelectedSuggestion(); ok {
w := p.buf.Document().GetWordBeforeCursorUntilSeparator(p.completion.wordSeparator)
if w != "" {
p.buf.DeleteBeforeCursor(len([]rune(w)))
}
p.buf.InsertText(s.Text, false, true)
}
p.completion.Reset()
}
}
func (p *Prompt) handleKeyBinding(key Key) bool {
shouldExit := false
for i := range commonKeyBindings {
kb := commonKeyBindings[i]
if kb.Key == key {
kb.Fn(p.buf)
}
}
if p.keyBindMode == EmacsKeyBind {
for i := range emacsKeyBindings {
kb := emacsKeyBindings[i]
if kb.Key == key {
kb.Fn(p.buf)
}
}
}
// Custom key bindings
for i := range p.keyBindings {
kb := p.keyBindings[i]
if kb.Key == key {
kb.Fn(p.buf)
}
}
if p.exitChecker != nil && p.exitChecker(p.buf.Text(), false) {
shouldExit = true
}
return shouldExit
}
func (p *Prompt) handleASCIICodeBinding(b []byte) bool {
checked := false
for _, kb := range p.ASCIICodeBindings {
if bytes.Equal(kb.ASCIICode, b) {
kb.Fn(p.buf)
checked = true
}
}
return checked
}
// Input just returns user input text.
func (p *Prompt) Input() string {
defer debug.Teardown()
debug.Log("start prompt")
p.setUp()
defer p.tearDown()
if p.completion.showAtStart {
p.completion.Update(*p.buf.Document())
}
p.renderer.Render(p.buf, p.completion)
bufCh := make(chan []byte, 128)
stopReadBufCh := make(chan struct{})
go p.readBuffer(bufCh, stopReadBufCh)
for {
select {
case b := <-bufCh:
if shouldExit, e := p.feed(b); shouldExit {
p.renderer.BreakLine(p.buf)
stopReadBufCh <- struct{}{}
return ""
} else if e != nil {
// Stop goroutine to run readBuffer function
stopReadBufCh <- struct{}{}
return e.input
} else {
p.completion.Update(*p.buf.Document())
p.renderer.Render(p.buf, p.completion)
}
default:
time.Sleep(10 * time.Millisecond)
}
}
}
func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
debug.Log("start reading buffer")
for {
select {
case <-stopCh:
debug.Log("stop reading buffer")
return
default:
if b, err := p.in.Read(); err == nil && !(len(b) == 1 && b[0] == 0) {
bufCh <- b
}
}
time.Sleep(10 * time.Millisecond)
}
}
func (p *Prompt) setUp() {
debug.AssertNoError(p.in.Setup())
p.renderer.Setup()
p.renderer.UpdateWinSize(p.in.GetWinSize())
}
func (p *Prompt) tearDown() {
if !p.skipTearDown {
debug.AssertNoError(p.in.TearDown())
}
p.renderer.TearDown()
}

@ -0,0 +1,287 @@
package prompt
import (
"runtime"
"git.tcp.direct/Mirrors/go-prompt/internal/debug"
runewidth "github.com/mattn/go-runewidth"
)
// Render to render prompt information from state of Buffer.
type Render struct {
out ConsoleWriter
prefix string
livePrefixCallback func() (prefix string, useLivePrefix bool)
breakLineCallback func(*Document)
title string
row uint16
col uint16
previousCursor int
// colors,
prefixTextColor Color
prefixBGColor Color
inputTextColor Color
inputBGColor Color
previewSuggestionTextColor Color
previewSuggestionBGColor Color
suggestionTextColor Color
suggestionBGColor Color
selectedSuggestionTextColor Color
selectedSuggestionBGColor Color
descriptionTextColor Color
descriptionBGColor Color
selectedDescriptionTextColor Color
selectedDescriptionBGColor Color
scrollbarThumbColor Color
scrollbarBGColor Color
}
// Setup to initialize console output.
func (r *Render) Setup() {
if r.title != "" {
r.out.SetTitle(r.title)
debug.AssertNoError(r.out.Flush())
}
}
// getCurrentPrefix to get current prefix.
// If live-prefix is enabled, return live-prefix.
func (r *Render) getCurrentPrefix() string {
if prefix, ok := r.livePrefixCallback(); ok {
return prefix
}
return r.prefix
}
func (r *Render) renderPrefix() {
r.out.SetColor(r.prefixTextColor, r.prefixBGColor, false)
r.out.WriteStr(r.getCurrentPrefix())
r.out.SetColor(DefaultColor, DefaultColor, false)
}
// TearDown to clear title and erasing.
func (r *Render) TearDown() {
r.out.ClearTitle()
r.out.EraseDown()
debug.AssertNoError(r.out.Flush())
}
func (r *Render) prepareArea(lines int) {
for i := 0; i < lines; i++ {
r.out.ScrollDown()
}
for i := 0; i < lines; i++ {
r.out.ScrollUp()
}
}
// UpdateWinSize called when window size is changed.
func (r *Render) UpdateWinSize(ws *WinSize) {
r.row = ws.Row
r.col = ws.Col
}
func (r *Render) renderWindowTooSmall() {
r.out.CursorGoTo(0, 0)
r.out.EraseScreen()
r.out.SetColor(DarkRed, White, false)
r.out.WriteStr("Your console window is too small...")
}
func (r *Render) renderCompletion(buf *Buffer, completions *CompletionManager) {
suggestions := completions.GetSuggestions()
if len(completions.GetSuggestions()) == 0 {
return
}
prefix := r.getCurrentPrefix()
formatted, width := formatSuggestions(
suggestions,
int(r.col)-runewidth.StringWidth(prefix)-1, // -1 means a width of scrollbar
)
// +1 means a width of scrollbar.
width++
windowHeight := len(formatted)
if windowHeight > int(completions.max) {
windowHeight = int(completions.max)
}
formatted = formatted[completions.verticalScroll : completions.verticalScroll+windowHeight]
r.prepareArea(windowHeight)
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(buf.Document().TextBeforeCursor())
x, _ := r.toPos(cursor)
if x+width >= int(r.col) {
cursor = r.backward(cursor, x+width-int(r.col))
}
contentHeight := len(completions.tmp)
fractionVisible := float64(windowHeight) / float64(contentHeight)
fractionAbove := float64(completions.verticalScroll) / float64(contentHeight)
scrollbarHeight := int(clamp(float64(windowHeight), 1, float64(windowHeight)*fractionVisible))
scrollbarTop := int(float64(windowHeight) * fractionAbove)
isScrollThumb := func(row int) bool {
return scrollbarTop <= row && row <= scrollbarTop+scrollbarHeight
}
selected := completions.selected - completions.verticalScroll
r.out.SetColor(White, Cyan, false)
for i := 0; i < windowHeight; i++ {
r.out.CursorDown(1)
if i == selected {
r.out.SetColor(r.selectedSuggestionTextColor, r.selectedSuggestionBGColor, true)
} else {
r.out.SetColor(r.suggestionTextColor, r.suggestionBGColor, false)
}
r.out.WriteStr(formatted[i].Text)
if i == selected {
r.out.SetColor(r.selectedDescriptionTextColor, r.selectedDescriptionBGColor, false)
} else {
r.out.SetColor(r.descriptionTextColor, r.descriptionBGColor, false)
}
r.out.WriteStr(formatted[i].Description)
if isScrollThumb(i) {
r.out.SetColor(DefaultColor, r.scrollbarThumbColor, false)
} else {
r.out.SetColor(DefaultColor, r.scrollbarBGColor, false)
}
r.out.WriteStr(" ")
r.out.SetColor(DefaultColor, DefaultColor, false)
r.lineWrap(cursor + width)
r.backward(cursor+width, width)
}
if x+width >= int(r.col) {
r.out.CursorForward(x + width - int(r.col))
}
r.out.CursorUp(windowHeight)
r.out.SetColor(DefaultColor, DefaultColor, false)
}
// Render renders to the console.
func (r *Render) Render(buffer *Buffer, completion *CompletionManager) {
// In situations where a pseudo tty is allocated (e.g. within a docker container),
// window size via TIOCGWINSZ is not immediately available and will result in 0,0 dimensions.
if r.col == 0 {
return
}
defer func() { debug.AssertNoError(r.out.Flush()) }()
r.move(r.previousCursor, 0)
line := buffer.Text()
prefix := r.getCurrentPrefix()
cursor := runewidth.StringWidth(prefix) + runewidth.StringWidth(line)
// prepare area
_, y := r.toPos(cursor)
h := y + 1 + int(completion.max)
if h > int(r.row) || completionMargin > int(r.col) {
r.renderWindowTooSmall()
return
}
// Rendering
r.out.HideCursor()
defer r.out.ShowCursor()
r.renderPrefix()
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
r.out.WriteStr(line)
r.out.SetColor(DefaultColor, DefaultColor, false)
r.lineWrap(cursor)
r.out.EraseDown()
cursor = r.backward(cursor, runewidth.StringWidth(line)-buffer.DisplayCursorPosition())
r.renderCompletion(buffer, completion)
if suggest, ok := completion.GetSelectedSuggestion(); ok {
cursor = r.backward(cursor, runewidth.StringWidth(buffer.Document().GetWordBeforeCursorUntilSeparator(completion.wordSeparator)))
r.out.SetColor(r.previewSuggestionTextColor, r.previewSuggestionBGColor, false)
r.out.WriteStr(suggest.Text)
r.out.SetColor(DefaultColor, DefaultColor, false)
cursor += runewidth.StringWidth(suggest.Text)
rest := buffer.Document().TextAfterCursor()
r.out.WriteStr(rest)
cursor += runewidth.StringWidth(rest)
r.lineWrap(cursor)
cursor = r.backward(cursor, runewidth.StringWidth(rest))
}
r.previousCursor = cursor
}
// BreakLine to break line.
func (r *Render) BreakLine(buffer *Buffer) {
// Erasing and Render
cursor := runewidth.StringWidth(buffer.Document().TextBeforeCursor()) + runewidth.StringWidth(r.getCurrentPrefix())
r.clear(cursor)
r.renderPrefix()
r.out.SetColor(r.inputTextColor, r.inputBGColor, false)
r.out.WriteStr(buffer.Document().Text + "\n")
r.out.SetColor(DefaultColor, DefaultColor, false)
debug.AssertNoError(r.out.Flush())
if r.breakLineCallback != nil {
r.breakLineCallback(buffer.Document())
}
r.previousCursor = 0
}
// clear erases the screen from a beginning of input
// even if there is line break which means input length exceeds a window's width.
func (r *Render) clear(cursor int) {
r.move(cursor, 0)
r.out.EraseDown()
}
// backward moves cursor to backward from a current cursor position
// regardless there is a line break.
func (r *Render) backward(from, n int) int {
return r.move(from, from-n)
}
// move moves cursor to specified position from the beginning of input
// even if there is a line break.
func (r *Render) move(from, to int) int {
fromX, fromY := r.toPos(from)
toX, toY := r.toPos(to)
r.out.CursorUp(fromY - toY)
r.out.CursorBackward(fromX - toX)
return to
}
// toPos returns the relative position from the beginning of the string.
func (r *Render) toPos(cursor int) (x, y int) {
col := int(r.col)
return cursor % col, cursor / col
}
func (r *Render) lineWrap(cursor int) {
if runtime.GOOS != "windows" && cursor > 0 && cursor%int(r.col) == 0 {
r.out.WriteRaw([]byte{'\n'})
}
}
func clamp(high, low, x float64) float64 {
switch {
case high < x:
return high
case x < low:
return low
default:
return x
}
}

@ -0,0 +1,43 @@
package prompt
func dummyExecutor(in string) {}
// Input get the input data from the user and return it.
func Input(prefix string, completer Completer, opts ...Option) string {
pt := New(dummyExecutor, completer)
pt.renderer.prefixTextColor = DefaultColor
pt.renderer.prefix = prefix
for _, opt := range opts {
if err := opt(pt); err != nil {
panic(err)
}
}
return pt.Input()
}
// Choose to the shortcut of input function to select from string array.
// Deprecated: Maybe anyone want to use this.
func Choose(prefix string, choices []string, opts ...Option) string {
completer := newChoiceCompleter(choices, FilterHasPrefix)
pt := New(dummyExecutor, completer)
pt.renderer.prefixTextColor = DefaultColor
pt.renderer.prefix = prefix
for _, opt := range opts {
if err := opt(pt); err != nil {
panic(err)
}
}
return pt.Input()
}
func newChoiceCompleter(choices []string, filter Filter) Completer {
s := make([]Suggest, len(choices))
for i := range choices {
s[i] = Suggest{Text: choices[i]}
}
return func(x Document) []Suggest {
return filter(s, x.GetWordBeforeCursor(), true)
}
}

@ -0,0 +1,49 @@
// +build !windows
package prompt
import (
"os"
"os/signal"
"syscall"
"git.tcp.direct/Mirrors/go-prompt/internal/debug"
)
func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
in := p.in
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGWINCH,
)
for {
select {
case <-stop:
debug.Log("stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
debug.Log("Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
debug.Log("Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
debug.Log("Catch SIGQUIT")
exitCh <- 0
case syscall.SIGWINCH:
debug.Log("Catch SIGWINCH")
winSizeCh <- in.GetWinSize()
}
}
}
}

@ -0,0 +1,44 @@
// +build windows
package prompt
import (
"os"
"os/signal"
"syscall"
"git.tcp.direct/Mirrors/go-prompt/internal/debug"
)
func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
for {
select {
case <-stop:
debug.Log("stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
debug.Log("Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
debug.Log("Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
debug.Log("Catch SIGQUIT")
exitCh <- 0
}
}
}
}

@ -0,0 +1,8 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
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 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.
For more information, please refer to http://www.wtfpl.net/about/

@ -0,0 +1,68 @@
package entropy
import (
crip "crypto/rand"
"encoding/binary"
"math/rand"
"time"
"nullprogram.com/x/rng"
)
// RandomStrChoice returns a random item from an input slice of strings.
func RandomStrChoice(choice []string) string {
if len(choice) > 0 {
return choice[RNGUint32()%uint32(len(choice))]
}
return ""
}
// GetCryptoSeed returns a random int64 derived from crypto/rand.
// This can be used as a seed for the math/rand package.
func GetCryptoSeed() int64 {
var seed int64
_ = binary.Read(crip.Reader, binary.BigEndian, &seed)
return seed
}
// GetOptimizedRand returns a pointer to a new rand.Rand which uses crypto/rand to seed a splitmix64 rng.
func GetOptimizedRand() *rand.Rand {
r := new(rng.SplitMix64)
r.Seed(GetCryptoSeed())
return rand.New(r)
}
// RNGUint32 returns a random uint32 using crypto/rand and splitmix64.
func RNGUint32() uint32 {
r := GetOptimizedRand()
return r.Uint32()
}
// RNG returns integer with a maximum amount of 'n' using crypto/rand and splitmix64.
func RNG(n int) int {
r := GetOptimizedRand()
return r.Intn(n)
}
// OneInA generates a random number with a maximum of 'million' (input int).
// If the resulting random number is equal to 1, then the result is true.
func OneInA(million int) bool {
return RNG(million) == 1
}
// RandSleepMS sleeps for a random period of time with a maximum of n milliseconds.
func RandSleepMS(n int) {
time.Sleep(time.Duration(RNG(n)) * time.Millisecond)
}
// characters used for the gerneration of random strings.
const charset = "abcdefghijklmnopqrstuvwxyz1234567890"
// RandStr generates a random alphanumeric string with a max length of size. Charset used is all lowercase.
func RandStr(size int) string {
buf := make([]byte, size)
for i := 0; i != size; i++ {
buf[i] = charset[uint32(RNG(32))%uint32(len(charset))]
}
return string(buf)
}

@ -0,0 +1,41 @@
package network
import ipa "inet.af/netaddr"
// IterateNetRange will ingest either a netaddr range or a netaddr prefix from the inet.af/netaddr package;
// returning a channel that will stream all the individual netaddr IPs within the given range or prefix.
// Alternatively, feed it a string in prefix or range format. (192.168.69.0/24) (192.168.69.0-192.168.69.254)
// Will return nil value if input is invalid.
func IterateNetRange(ips interface{}) chan ipa.IP {
var addrs ipa.IPRange
switch ips.(type) {
case string:
strefix, prefixErr := ipa.ParseIPPrefix(ips.(string))
strange, rangeErr := ipa.ParseIPRange(ips.(string))
switch {
case rangeErr == nil:
addrs = strange
case prefixErr == nil:
addrs = strefix.Range()
default:
return nil
}
case ipa.IPRange:
addrs = ips.(ipa.IPRange)
case ipa.IPPrefix:
addrs = ips.(ipa.IPPrefix).Range()
default:
return nil
}
ch := make(chan ipa.IP)
go func(ret chan ipa.IP) {
for head := addrs.From(); head != addrs.To(); head = head.Next() {
if !head.IsUnspecified() {
ret <- head
}
}
}(ch)
return ch
}

21
vendor/github.com/amimof/huego/.gitignore generated vendored Normal file

@ -0,0 +1,21 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
TODO.md
test/
# jetbrains
.idea

35
vendor/github.com/amimof/huego/CHANGELOG.md generated vendored Normal file

@ -0,0 +1,35 @@
# Changelog
## 1.0.2
* Improved test coverage
* Use of `httpmock` in tests
## 1.0.1
* Added `go.mod` for Go 1.11 module compatibility
## 1,0.0
Exiting beta and entering stable release
## 1.0.0-beta.2
Much better error handling. Whenever the bridge API returns an error, huego will return that to the user accordingly through an error struct rather than just throwing a json.UnmarshalTypeError.
## 1.0.0-beta.1
* `Sensor.State` and `Sensor.Config` is now `interface{}` since these varry depending on the type
* As of this release `Gitflow` is obsolete.
## 1.0.0-alpha.5
* Added `SetState` receivers to `Group` and `Light`.
* Renamed `SetLight` to `SetLightState` for a more consistent naming convention.
* Implemented `Capabilities`
## 1.0.0-alpha.4
* Huego now has a logo
* Changes to fulfill `golint`, `govet` and `gofmt`
## 1.0.0-alpha.3
* Added `Group` receivers: `Alert()`, `Bri()`, `Ct()`, `Effect()`, `Hue()`, `IsOn()`, `Off()`, `On()`, `Rename()`, `Sat()`, `Scene()`, `TransitionTime()` and `Xy()`
* Added `Light` receivers: `Alert()`, `Effect()` and `TransitionTime()`
* Renamed type `Action` to `State` that belongs to `Group`
## 1.0.0
Initial release

21
vendor/github.com/amimof/huego/LICENSE generated vendored Normal file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 amimof
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.

117
vendor/github.com/amimof/huego/Makefile generated vendored Normal file

@ -0,0 +1,117 @@
MODULE = $(shell env GO111MODULE=on $(GO) list -m)
DATE ?= $(shell date +%FT%T%z)
VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2> /dev/null || \
cat $(CURDIR)/.version 2> /dev/null || echo v0)
COMMIT=$(shell git rev-parse HEAD)
BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
GOVERSION=$(shell go version | awk -F\go '{print $$3}' | awk '{print $$1}')
PKGS = $(or $(PKG),$(shell env GO111MODULE=on $(GO) list ./...))
TESTPKGS = $(shell env GO111MODULE=on $(GO) list -f \
'{{ if or .TestGoFiles .XTestGoFiles }}{{ .ImportPath }}{{ end }}' \
$(PKGS))
BUILDPATH ?= $(BIN)/$(shell basename $(MODULE))
SRC_FILES=find . -name "*.go" -type f -not -path "./vendor/*" -not -path "./.git/*" -not -path "./.cache/*" -print0 | xargs -0
TBIN = $(CURDIR)/test/bin
GO = go
TIMEOUT = 15
V = 0
Q = $(if $(filter 1,$V),,@)
M = $(shell printf "\033[34;1m➜\033[0m")
export GO111MODULE=on
export CGO_ENABLED=0
# Tools
$(TBIN):
@mkdir -p $@
$(TBIN)/%: | $(TBIN) ; $(info $(M) building $(PACKAGE))
$Q tmp=$$(mktemp -d); \
env GO111MODULE=off GOPATH=$$tmp GOBIN=$(TBIN) $(GO) get $(PACKAGE) \
|| ret=$$?; \
rm -rf $$tmp ; exit $$ret
GOLINT = $(TBIN)/golint
$(BIN)/golint: PACKAGE=golang.org/x/lint/golint
GOCYCLO = $(TBIN)/gocyclo
$(TBIN)/gocyclo: PACKAGE=github.com/fzipp/gocyclo/cmd/gocyclo
INEFFASSIGN = $(TBIN)/ineffassign
$(TBIN)/ineffassign: PACKAGE=github.com/gordonklaus/ineffassign
MISSPELL = $(TBIN)/misspell
$(TBIN)/misspell: PACKAGE=github.com/client9/misspell/cmd/misspell
GOLINT = $(TBIN)/golint
$(TBIN)/golint: PACKAGE=golang.org/x/lint/golint
GOCOV = $(TBIN)/gocov
$(TBIN)/gocov: PACKAGE=github.com/axw/gocov/...
# Tests
.PHONY: lint
lint: | $(GOLINT) ; $(info $(M) running golint) @ ## Runs the golint command
$Q $(GOLINT) -set_exit_status $(PKGS)
.PHONY: gocyclo
gocyclo: | $(GOCYCLO) ; $(info $(M) running gocyclo) @ ## Calculates cyclomatic complexities of functions in Go source code
$Q $(GOCYCLO) -over 25 .
.PHONY: ineffassign
ineffassign: | $(INEFFASSIGN) ; $(info $(M) running ineffassign) @ ## Detects ineffectual assignments in Go code
$Q $(INEFFASSIGN) .
.PHONY: misspell
misspell: | $(MISSPELL) ; $(info $(M) running misspell) @ ## Finds commonly misspelled English words
$Q $(MISSPELL) .
.PHONY: test
test: ; $(info $(M) running go test) @ ## Runs unit tests
$Q $(GO) test ${PKGS}
.PHONY: fmt
fmt: ; $(info $(M) running gofmt) @ ## Formats Go code
$Q $(GO) fmt $(PKGS)
.PHONY: vet
vet: ; $(info $(M) running go vet) @ ## Examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
$Q $(GO) vet $(PKGS)
.PHONY: race
race: ; $(info $(M) running go race) @ ## Runs tests with data race detection
$Q CGO_ENABLED=1 $(GO) test -race -short $(PKGS)
.PHONY: benchmark
benchmark: ; $(info $(M) running go benchmark test) @ ## Benchmark tests to examine performance
$Q $(GO) test -run=__absolutelynothing__ -bench=. $(PKGS)
.PHONY: coverage
coverage: ; $(info $(M) running go coverage) @ ## Runs tests and generates code coverage report at ./test/coverage.out
$Q mkdir -p $(CURDIR)/test/
$Q $(GO) test -coverprofile="$(CURDIR)/test/coverage.out" $(PKGS)
.PHONY: checkfmt
checkfmt: ; $(info $(M) running checkfmt) @ ## Checks if code is formatted with go fmt and errors out if not
@test "$(shell $(SRC_FILES) gofmt -l)" = "" \
|| { echo "Code not formatted, please run 'make fmt'"; exit 2; }
# Misc
.PHONY: help
help:
@grep -hE '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m∙ %s:\033[0m %s\n", $$1, $$2}'
.PHONY: version
version: ## Print version information
@echo App: $(VERSION)
@echo Go: $(GOVERSION)
@echo Commit: $(COMMIT)
@echo Branch: $(BRANCH)
.PHONY: clean
clean: ; $(info $(M) cleaning) @ ## Cleanup everything
@rm -rfv $(TBIN)
@rm -rfv $(CURDIR)/test

50
vendor/github.com/amimof/huego/README.md generated vendored Normal file

@ -0,0 +1,50 @@
[![Go](https://github.com/amimof/huego/actions/workflows/go.yaml/badge.svg)](https://github.com/amimof/huego/actions/workflows/go.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/amimof/huego)](https://goreportcard.com/report/github.com/amimof/huego) [![codecov](https://codecov.io/gh/amimof/huego/branch/master/graph/badge.svg)](https://codecov.io/gh/amimof/huego) [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go)
# Huego
An extensive Philips Hue client library for [`Go`](https://golang.org/) with an emphasis on simplicity. It is designed to be clean, unbloated and extensible. With `Huego` you can interact with any Philips Hue bridge and its resources including `Lights`, `Groups`, `Scenes`, `Sensors`, `Rules`, `Schedules`, `Resourcelinks`, `Capabilities` and `Configuration` .
![](./logo/logo.png)
## Installation
Get the package and import it in your code.
```
go get github.com/amimof/huego
```
You may use [`New()`](https://godoc.org/github.com/amimof/huego#New) if you have already created an user and know the IP address to your bridge.
```Go
package main
import (
"github.com/amimof/huego"
"fmt"
)
func main() {
bridge := huego.New("192.168.1.59", "username")
l, err := bridge.GetLights()
if err != nil {
panic(err)
}
fmt.Printf("Found %d lights", len(l))
}
```
Or discover a bridge on your network with [`Discover()`](https://godoc.org/github.com/amimof/huego#Discover) and create a new user with [`CreateUser()`](https://godoc.org/github.com/amimof/huego#Bridge.CreateUser). To successfully create a user, the link button on your bridge must have been pressed before calling `CreateUser()` in order to authorise the request.
```Go
func main() {
bridge, _ := huego.Discover()
user, _ := bridge.CreateUser("my awesome hue app") // Link button needs to be pressed
bridge = bridge.Login(user)
light, _ := bridge.GetLight(3)
light.Off()
}
```
## Documentation
See [godoc.org/github.com/amimof/huego](https://godoc.org/github.com/amimof/huego) for the full package documentation.
## Contributing
All help in any form is highly appreciated and your are welcome participate in developing `Huego` together. To contribute submit a `Pull Request`. If you want to provide feedback, open up a Github `Issue` or contact me personally.

1954
vendor/github.com/amimof/huego/bridge.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

157
vendor/github.com/amimof/huego/bridge_test.go generated vendored Normal file

@ -0,0 +1,157 @@
package huego
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"testing"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
"golang.org/x/net/proxy"
)
func ExampleBridge_CreateUser() {
bridge, _ := Discover()
user, err := bridge.CreateUser("my awesome hue app") // Link button needs to be pressed
if err != nil {
fmt.Printf("Error creating user: %s", err.Error())
}
bridge = bridge.Login(user)
light, _ := bridge.GetLight(1)
light.Off()
}
func TestLogin(t *testing.T) {
b := New(hostname, username)
c, err := b.GetConfig()
if err != nil {
t.Fatal(err)
}
t.Logf("Logged in and got config which means that we are authorized")
t.Logf("Name: %s, SwVersion: %s", c.Name, c.SwVersion)
}
func TestLoginUnauthorized(t *testing.T) {
b := New(hostname, "")
b = b.Login("invalid_password")
_, err := b.GetLights()
if err != nil {
if strings.Contains(err.Error(), "unauthorized user") == false {
t.Fatal(err)
}
}
t.Logf("Bridge: %s, Username: %s", b.Host, b.User)
t.Log("User logged in and authenticated which isn't what we want")
}
func TestUpdateBridgeConfig(t *testing.T) {
b := New(hostname, username)
c, err := b.GetConfig()
if err != nil {
t.Fatal(err)
}
_, err = b.UpdateConfig(c)
if err != nil {
t.Fatal(err)
}
}
func TestUpdateBridgeConfigError(t *testing.T) {
b := New(badHostname, username)
_, err := b.GetConfig()
if err == nil {
t.Fatal("Expected error not to be nil")
}
}
func TestBridge_getAPIPathError(t *testing.T) {
b := New("invalid hostname", "")
_, err := b.getAPIPath("/")
assert.NotNil(t, err)
}
func TestBridge_getError(t *testing.T) {
httpmock.Deactivate()
defer httpmock.Activate()
_, err := get(context.Background(), "invalid hostname", http.DefaultClient)
assert.NotNil(t, err)
}
func TestBridge_putError(t *testing.T) {
httpmock.Deactivate()
defer httpmock.Activate()
_, err := put(context.Background(), "invalid hostname", []byte("huego"), http.DefaultClient)
assert.NotNil(t, err)
}
func TestBridge_postError(t *testing.T) {
httpmock.Deactivate()
defer httpmock.Activate()
_, err := post(context.Background(), "invalid hostname", []byte("huego"), http.DefaultClient)
assert.NotNil(t, err)
}
func TestBridge_deleteError(t *testing.T) {
httpmock.Deactivate()
defer httpmock.Activate()
_, err := del(context.Background(), "invalid hostname", http.DefaultClient)
assert.NotNil(t, err)
}
func TestNewWithClient(t *testing.T) {
newTransport := httpmock.InitialTransport.(*http.Transport).Clone()
newTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return proxy.Direct.Dial(network, address)
}
newClient := http.DefaultClient
newClient.Transport = newTransport
httpmock.ActivateNonDefault(newClient)
b := NewWithClient(hostname, username, newClient)
c, err := b.GetConfig()
if err != nil {
t.Fatal(err)
}
if c == nil {
t.Fatal("failed to get config")
}
t.Logf("Logged in and got config which means that we are authorized")
t.Logf("Name: %s, SwVersion: %s", c.Name, c.SwVersion)
}
func TestNewCustom(t *testing.T) {
newTransport := httpmock.InitialTransport.(*http.Transport).Clone()
newTransport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return proxy.Direct.Dial(network, address)
}
newClient := http.DefaultClient
newClient.Transport = newTransport
httpmock.ActivateNonDefault(newClient)
testData := `{"name":"Philips hue","datastoreversion":"125","swversion":"1952043030","apiversion":"1.52.0",
"mac":"ec:b5:00:00:00:01","bridgeid":"EC000CF00V0000223","factorynew":false,"replacesbridgeid":null,
"modelid":"BSB002","starterkitid":""}`
b, err := NewCustom([]byte(testData), hostname, newClient)
if err != nil {
t.Fatal(err)
}
b.User = username
c, err := b.GetConfig()
if err != nil {
t.Fatal(err)
}
if c == nil {
t.Fatal("failed to get config")
}
t.Logf("Logged in and got config which means that we are authorized")
t.Logf("Name: %s, SwVersion: %s", c.Name, c.SwVersion)
}

18
vendor/github.com/amimof/huego/capabilities.go generated vendored Normal file

@ -0,0 +1,18 @@
package huego
// Capabilities holds a combined model of resource capabilities on the bridge: https://developers.meethue.com/documentation/lights-api
type Capabilities struct {
Groups Capability `json:"groups,omitempty"`
Lights Capability `json:"lights,omitempty"`
Resourcelinks Capability `json:"resourcelinks,omitempty"`
Schedules Capability `json:"schedules,omitempty"`
Rules Capability `json:"rules,omitempty"`
Scenes Capability `json:"scenes,omitempty"`
Sensors Capability `json:"sensors,omitempty"`
Streaming Capability `json:"streaming,omitempty"`
}
// Capability defines the resource and subresource capabilities.
type Capability struct {
Available int `json:"available,omitempty"`
}

38
vendor/github.com/amimof/huego/capabilities_test.go generated vendored Normal file

@ -0,0 +1,38 @@
package huego
import (
"testing"
)
func TestGetCapabilities(t *testing.T) {
b := New(hostname, username)
c, err := b.GetCapabilities()
if err != nil {
t.Fatal(c)
}
t.Log("Capabilities:")
t.Log(" Groups")
t.Logf(" Available: %d", c.Groups.Available)
t.Log(" Lights")
t.Logf(" Available: %d", c.Lights.Available)
t.Log(" Resourcelinks")
t.Logf(" Available: %d", c.Resourcelinks.Available)
t.Log(" Schedules")
t.Logf(" Available: %d", c.Schedules.Available)
t.Log(" Rules")
t.Logf(" Available: %d", c.Rules.Available)
t.Log(" Scenes")
t.Logf(" Available: %d", c.Scenes.Available)
t.Log(" Sensors")
t.Logf(" Available: %d", c.Sensors.Available)
t.Log(" Streaming")
t.Logf(" Available: %d", c.Streaming.Available)
}
func TestGetCapabilitiesError(t *testing.T) {
b := New(badHostname, username)
_, err := b.GetCapabilities()
if err == nil {
t.Fatal("Expected error not to be nil")
}
}

104
vendor/github.com/amimof/huego/config.go generated vendored Normal file

@ -0,0 +1,104 @@
package huego
// Config holds the bridge hardware configuration
type Config struct {
Name string `json:"name,omitempty"`
SwUpdate SwUpdate `json:"swupdate"`
SwUpdate2 SwUpdate2 `json:"swupdate2"`
WhitelistMap map[string]Whitelist `json:"whitelist"`
Whitelist []Whitelist `json:"-"`
PortalState PortalState `json:"portalstate"`
APIVersion string `json:"apiversion,omitempty"`
SwVersion string `json:"swversion,omitempty"`
ProxyAddress string `json:"proxyaddress,omitempty"`
ProxyPort uint16 `json:"proxyport,omitempty"`
LinkButton bool `json:"linkbutton,omitempty"`
IPAddress string `json:"ipaddress,omitempty"`
Mac string `json:"mac,omitempty"`
NetMask string `json:"netmask,omitempty"`
Gateway string `json:"gateway,omitempty"`
Dhcp bool `json:"dhcp,omitempty"`
PortalServices bool `json:"portalservices,omitempty"`
UTC string `json:"UTC,omitempty"`
LocalTime string `json:"localtime,omitempty"`
TimeZone string `json:"timezone,omitempty"`
ZigbeeChannel uint8 `json:"zigbeechannel,omitempty"`
ModelID string `json:"modelid,omitempty"`
BridgeID string `json:"bridgeid,omitempty"`
FactoryNew bool `json:"factorynew,omitempty"`
ReplacesBridgeID string `json:"replacesbridgeid,omitempty"`
DatastoreVersion string `json:"datastoreversion,omitempty"`
StarterKitID string `json:"starterkitid,omitempty"`
InternetService InternetService `json:"internetservices,omitempty"`
}
// SwUpdate contains information related to software updates. Deprecated in 1.20
type SwUpdate struct {
CheckForUpdate bool `json:"checkforupdate,omitempty"`
DeviceTypes DeviceTypes `json:"devicetypes"`
UpdateState uint8 `json:"updatestate,omitempty"`
Notify bool `json:"notify,omitempty"`
URL string `json:"url,omitempty"`
Text string `json:"text,omitempty"`
}
// SwUpdate2 contains information related to software updates
type SwUpdate2 struct {
Bridge BridgeConfig `json:"bridge"`
CheckForUpdate bool `json:"checkforupdate,omitempty"`
State string `json:"state,omitempty"`
Install bool `json:"install,omitempty"`
AutoInstall AutoInstall `json:"autoinstall"`
LastChange string `json:"lastchange,omitempty"`
LastInstall string `json:"lastinstall,omitempty"`
}
// DeviceTypes details the type of updates available
type DeviceTypes struct {
Bridge bool `json:"bridge,omitempty"`
Lights []string `json:"lights,omitempty"`
Sensors []string `json:"sensors,omitempty"`
}
// BridgeConfig holds information about software updates
type BridgeConfig struct {
State string `json:"state,omitempty"`
LastInstall string `json:"lastinstall,omitempty"`
}
// AutoInstall holds automatic update configuration
type AutoInstall struct {
On bool `json:"on,omitempty"`
UpdateTime string `json:"updatetime,omitempty"`
}
// InternetService stores information about the internet connectivity to the bridge
type InternetService struct {
Internet string `json:"internet,omitempty"`
RemoteAccess string `json:"remoteaccess,omitempty"`
Time string `json:"time,omitempty"`
SwUpdate string `json:"swupdate,omitempty"`
}
// Backup holds configuration backup status information
type Backup struct {
Status string `json:"backup,omitempty"`
ErrorCode int `json:"errorcode,omitempty"`
}
// Whitelist represents a whitelist user ID in the bridge
type Whitelist struct {
Name string `json:"name"`
Username string
CreateDate string `json:"create date"`
LastUseDate string `json:"last use date"`
ClientKey string
}
// PortalState is a struct representing the portal state
type PortalState struct {
SignedOn bool `json:"signedon,omitempty"`
Incoming bool `json:"incoming,omitempty"`
Outgoing bool `json:"outgoing,omitempty"`
Communication string `json:"communication,omitempty"`
}

168
vendor/github.com/amimof/huego/config_test.go generated vendored Normal file

@ -0,0 +1,168 @@
package huego
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetConfig(t *testing.T) {
b := New(hostname, username)
config, err := b.GetConfig()
if err != nil {
t.Fatal(err)
}
t.Logf("Name: %s", config.Name)
t.Logf("SwUpdate:")
t.Logf(" CheckForUpdate: %t", config.SwUpdate.CheckForUpdate)
t.Logf(" DeviceTypes:")
t.Logf(" Bridge: %t", config.SwUpdate.DeviceTypes.Bridge)
t.Logf(" Lights (length): %d", len(config.SwUpdate.DeviceTypes.Lights))
t.Logf(" Sensors (length): %d", len(config.SwUpdate.DeviceTypes.Sensors))
t.Logf(" UpdateState: %d", config.SwUpdate.UpdateState)
t.Logf(" Notify: %t", config.SwUpdate.Notify)
t.Logf(" URL: %s", config.SwUpdate.URL)
t.Logf(" Text: %s", config.SwUpdate.Text)
t.Logf("SwUpdate2:")
t.Logf(" Bridge: %s", config.SwUpdate2.Bridge)
t.Logf(" State: %s", config.SwUpdate2.Bridge.State)
t.Logf(" LastInstall: %s", config.SwUpdate2.Bridge.LastInstall)
t.Logf(" CheckForUpdate: %t", config.SwUpdate2.CheckForUpdate)
t.Logf(" State: %s", config.SwUpdate2.State)
t.Logf(" Install: %t", config.SwUpdate2.Install)
t.Logf(" AutoInstall:")
t.Logf(" On: %t", config.SwUpdate2.AutoInstall.On)
t.Logf(" UpdateTime: %s", config.SwUpdate2.AutoInstall.UpdateTime)
t.Logf(" LastChange: %s", config.SwUpdate2.LastChange)
t.Logf(" LastInstall: %s", config.SwUpdate2.LastInstall)
t.Logf("Whitelist (length): %d", len(config.Whitelist))
t.Logf("PortalState:")
t.Logf(" SignedOn: %t", config.PortalState.SignedOn)
t.Logf(" Incoming: %t", config.PortalState.Incoming)
t.Logf(" Outgoing: %t", config.PortalState.Outgoing)
t.Logf(" Communication: %s", config.PortalState.Communication)
t.Logf("APIVersion: %s", config.APIVersion)
t.Logf("SwVersion: %s", config.SwVersion)
t.Logf("ProxyAddress: %s", config.ProxyAddress)
t.Logf("ProxyPort: %d", config.ProxyPort)
t.Logf("LinkButton: %t", config.LinkButton)
t.Logf("IPAddress: %s", config.IPAddress)
t.Logf("Mac: %s", config.Mac)
t.Logf("NetMask: %s", config.NetMask)
t.Logf("Gateway: %s", config.Gateway)
t.Logf("Dhcp: %t", config.Dhcp)
t.Logf("PortalServices: %t", config.PortalServices)
t.Logf("UTC: %s", config.UTC)
t.Logf("LocalTime: %s", config.LocalTime)
t.Logf("TimeZone: %s", config.TimeZone)
t.Logf("ZigbeeChannel: %d", config.ZigbeeChannel)
t.Logf("ModelID: %s", config.ModelID)
t.Logf("BridgeID: %s", config.BridgeID)
t.Logf("FactoryNew: %t", config.FactoryNew)
t.Logf("ReplacesBridgeID: %s", config.ReplacesBridgeID)
t.Logf("DatastoreVersion: %s", config.DatastoreVersion)
t.Logf("StarterKitID: %s", config.StarterKitID)
}
func TestGetConfigError(t *testing.T) {
b := New(badHostname, username)
_, err := b.GetConfig()
if err == nil {
t.Fatal("Expected error not to be nil")
}
}
func TestCreateUser(t *testing.T) {
b := New(hostname, "")
u, err := b.CreateUser("github.com/amimof/huego#go test")
if err != nil {
t.Fatal(err)
} else {
t.Logf("User created with username: %s", u)
}
}
func TestCreateUserError(t *testing.T) {
b := New(badHostname, username)
_, err := b.CreateUser("github.com/amimof/huego#go test")
if err == nil {
t.Fatal("Expected error not to be nil")
}
}
func TestCreateUserWithClientKey(t *testing.T) {
b := New(hostname, "")
u, err := b.CreateUserWithClientKey("github.com/amimof/huego#go test")
if err != nil {
t.Fatal(err)
} else {
t.Logf("User created with username: %s", u)
}
}
func TestCreateUserWithClientKeyError(t *testing.T) {
b := New(badHostname, username)
_, err := b.CreateUserWithClientKey("github.com/amimof/huego#go test")
if err == nil {
t.Fatal("Expected error not to be nil")
}
}
func TestGetUsers(t *testing.T) {
b := New(hostname, username)
users, err := b.GetUsers()
if err != nil {
t.Fatal(err)
}
for i, u := range users {
t.Logf("%d:", i)
t.Logf(" Name: %s", u.Name)
t.Logf(" Username: %s", u.Username)
t.Logf(" CreateDate: %s", u.CreateDate)
t.Logf(" LastUseDate: %s", u.LastUseDate)
}
contains := func(name string, ss []Whitelist) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("PhilipsHueAndroidApp#TCTALCATELONETOU", users))
assert.True(t, contains("MyApplication", users))
}
func TestGetUsersError(t *testing.T) {
b := New(badHostname, username)
_, err := b.GetUsers()
if err == nil {
t.Fatal("Expected error not to be nil")
}
}
func TestDeleteUser(t *testing.T) {
b := New(hostname, username)
err := b.DeleteUser("ffffffffe0341b1b376a2389376a2389")
if err != nil {
t.Fatal(err)
}
t.Logf("Deleted user '%s'", "ffffffffe0341b1b376a2389376a2389")
}
func TestGetFullState(t *testing.T) {
b := New(hostname, username)
_, err := b.GetFullState()
if err != nil {
t.Fatal(err)
}
}
func TestGetFullStateError(t *testing.T) {
b := New(badHostname, username)
_, err := b.GetFullState()
if err == nil {
t.Fatal("Expected error not to be nil")
}
}

9
vendor/github.com/amimof/huego/go.mod generated vendored Normal file

@ -0,0 +1,9 @@
module github.com/amimof/huego
go 1.13
require (
github.com/jarcoal/httpmock v1.0.4
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
)

20
vendor/github.com/amimof/huego/go.sum generated vendored Normal file

@ -0,0 +1,20 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jarcoal/httpmock v1.0.4 h1:jp+dy/+nonJE4g4xbVtl9QdrUNbn6/3hDT5R4nDIZnA=
github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

357
vendor/github.com/amimof/huego/group.go generated vendored Normal file

@ -0,0 +1,357 @@
package huego
import (
"context"
"errors"
"image/color"
)
// Group represents a bridge group https://developers.meethue.com/documentation/groups-api
type Group struct {
Name string `json:"name,omitempty"`
Lights []string `json:"lights,omitempty"`
Type string `json:"type,omitempty"`
GroupState *GroupState `json:"state,omitempty"`
Recycle bool `json:"recycle,omitempty"`
Class string `json:"class,omitempty"`
Stream *Stream `json:"stream,omitempty"`
Locations map[string][]float64 `json:"locations,omitempty"`
State *State `json:"action,omitempty"`
ID int `json:"-"`
bridge *Bridge
}
// GroupState defines the state on a group.
// Can be used to control the state of all lights in a group rather than controlling them individually
type GroupState struct {
AllOn bool `json:"all_on,omitempty"`
AnyOn bool `json:"any_on,omitempty"`
}
// Stream define the stream status of a group
type Stream struct {
ProxyMode string `json:"proxymode,omitempty"`
ProxyNode string `json:"proxynode,omitempty"`
ActiveRaw *bool `json:"active,omitempty"`
OwnerRaw *string `json:"owner,omitempty"`
}
// Active returns the stream active state, and will return false if ActiveRaw is nil
func (s *Stream) Active() bool {
if s.ActiveRaw == nil {
return false
}
return *s.ActiveRaw
}
// Owner returns the stream Owner, and will return an empty string if OwnerRaw is nil
func (s *Stream) Owner() string {
if s.OwnerRaw == nil {
return ""
}
return *s.OwnerRaw
}
// SetState sets the state of the group to s.
func (g *Group) SetState(s State) error {
return g.SetStateContext(context.Background(), s)
}
// SetStateContext sets the state of the group to s.
func (g *Group) SetStateContext(ctx context.Context, s State) error {
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, s)
if err != nil {
return err
}
g.State = &s
return nil
}
// Rename sets the name property of the group
func (g *Group) Rename(new string) error {
return g.RenameContext(context.Background(), new)
}
// RenameContext sets the name property of the group
func (g *Group) RenameContext(ctx context.Context, new string) error {
update := Group{Name: new}
_, err := g.bridge.UpdateGroupContext(ctx, g.ID, update)
if err != nil {
return err
}
g.Name = new
return nil
}
// Off sets the On state of one group to false, turning all lights in the group off
func (g *Group) Off() error {
return g.OffContext(context.Background())
}
// OffContext sets the On state of one group to false, turning all lights in the group off
func (g *Group) OffContext(ctx context.Context) error {
state := State{On: false}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, state)
if err != nil {
return err
}
g.State.On = false
return nil
}
// On sets the On state of one group to true, turning all lights in the group on
func (g *Group) On() error {
return g.OnContext(context.Background())
}
// OnContext sets the On state of one group to true, turning all lights in the group on
func (g *Group) OnContext(ctx context.Context) error {
state := State{On: true}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, state)
if err != nil {
return err
}
g.State.On = true
return nil
}
// IsOn returns true if light state On property is true
func (g *Group) IsOn() bool {
return g.State.On
}
// Bri sets the light brightness state property
func (g *Group) Bri(new uint8) error {
return g.BriContext(context.Background(), new)
}
// BriContext sets the light brightness state property
func (g *Group) BriContext(ctx context.Context, new uint8) error {
update := State{On: true, Bri: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Bri = new
g.State.On = true
return nil
}
// Hue sets the light hue state property (0-65535)
func (g *Group) Hue(new uint16) error {
return g.HueContext(context.Background(), new)
}
// HueContext sets the light hue state property (0-65535)
func (g *Group) HueContext(ctx context.Context, new uint16) error {
update := State{On: true, Hue: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Hue = new
g.State.On = true
return nil
}
// Sat sets the light saturation state property (0-254)
func (g *Group) Sat(new uint8) error {
return g.SatContext(context.Background(), new)
}
// SatContext sets the light saturation state property (0-254)
func (g *Group) SatContext(ctx context.Context, new uint8) error {
update := State{On: true, Sat: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Sat = new
g.State.On = true
return nil
}
// Xy sets the x and y coordinates of a color in CIE color space. (0-1 per value)
func (g *Group) Xy(new []float32) error {
return g.XyContext(context.Background(), new)
}
// XyContext sets the x and y coordinates of a color in CIE color space. (0-1 per value)
func (g *Group) XyContext(ctx context.Context, new []float32) error {
update := State{On: true, Xy: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Xy = new
g.State.On = true
return nil
}
// Ct sets the light color temperature state property
func (g *Group) Ct(new uint16) error {
return g.CtContext(context.Background(), new)
}
// CtContext sets the light color temperature state property
func (g *Group) CtContext(ctx context.Context, new uint16) error {
update := State{On: true, Ct: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Ct = new
g.State.On = true
return nil
}
// Col sets the light color as RGB (will be converted to xy)
func (g *Group) Col(new color.Color) error {
return g.ColContext(context.Background(), new)
}
// ColContext sets the light color as RGB (will be converted to xy)
func (g *Group) ColContext(ctx context.Context, new color.Color) error {
xy, bri := ConvertRGBToXy(new)
update := State{On: true, Xy: xy, Bri: bri}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Xy = xy
g.State.Bri = bri
g.State.On = true
return nil
}
// Scene sets the scene by it's identifier of the scene you wish to recall
func (g *Group) Scene(scene string) error {
return g.SceneContext(context.Background(), scene)
}
// SceneContext sets the scene by it's identifier of the scene you wish to recall
func (g *Group) SceneContext(ctx context.Context, scene string) error {
update := State{On: true, Scene: scene}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Scene = scene
g.State.On = true
return nil
}
// TransitionTime sets the duration of the transition from the lights current state to the new state
func (g *Group) TransitionTime(new uint16) error {
return g.TransitionTimeContext(context.Background(), new)
}
// TransitionTimeContext sets the duration of the transition from the lights current state to the new state
func (g *Group) TransitionTimeContext(ctx context.Context, new uint16) error {
update := State{On: g.State.On, TransitionTime: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.TransitionTime = new
return nil
}
// Effect the dynamic effect of the lights in the group, currently “none” and “colorloop” are supported
func (g *Group) Effect(new string) error {
return g.EffectContext(context.Background(), new)
}
// EffectContext the dynamic effect of the lights in the group, currently “none” and “colorloop” are supported
func (g *Group) EffectContext(ctx context.Context, new string) error {
update := State{On: true, Effect: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Effect = new
g.State.On = true
return nil
}
// Alert makes the lights in the group blink in its current color. Supported values are:
// “none” The light is not performing an alert effect.
// “select” The light is performing one breathe cycle.
// “lselect” The light is performing breathe cycles for 15 seconds or until alert is set to "none".
func (g *Group) Alert(new string) error {
return g.AlertContext(context.Background(), new)
}
// AlertContext makes the lights in the group blink in its current color. Supported values are:
// “none” The light is not performing an alert effect.
// “select” The light is performing one breathe cycle.
// “lselect” The light is performing breathe cycles for 15 seconds or until alert is set to "none".
func (g *Group) AlertContext(ctx context.Context, new string) error {
update := State{On: true, Alert: new}
_, err := g.bridge.SetGroupStateContext(ctx, g.ID, update)
if err != nil {
return err
}
g.State.Effect = new
g.State.On = true
return nil
}
// EnableStreaming enables streaming for the group by setting the Stream Active property to true
func (g *Group) EnableStreaming() error {
return g.EnableStreamingContext(context.Background())
}
// EnableStreamingContext enables streaming for the group by setting the Stream Active property to true
func (g *Group) EnableStreamingContext(ctx context.Context) error {
if g.Type != "Entertainment" {
return errors.New("must be an entertainment group to enable streaming")
}
active := true
update := Group{
Stream: &Stream{
ActiveRaw: &active,
},
}
_, err := g.bridge.UpdateGroupContext(ctx, g.ID, update)
if err != nil {
return err
}
g.Stream.ActiveRaw = &active
g.Stream.OwnerRaw = &g.bridge.User
return nil
}
// DisableStreaming disabled streaming for the group by setting the Stream Active property to false
func (g *Group) DisableStreaming() error {
return g.DisableStreamingContext(context.Background())
}
// DisableStreamingContext disabled streaming for the group by setting the Stream Active property to false
func (g *Group) DisableStreamingContext(ctx context.Context) error {
if g.Type != "Entertainment" {
return errors.New("must be an entertainment group to disable streaming")
}
active := false
update := Group{
Stream: &Stream{
ActiveRaw: &active,
},
}
_, err := g.bridge.UpdateGroupContext(ctx, g.ID, update)
if err != nil {
return err
}
g.Stream.ActiveRaw = &active
g.Stream.OwnerRaw = nil
return nil
}

568
vendor/github.com/amimof/huego/group_test.go generated vendored Normal file

@ -0,0 +1,568 @@
package huego
import (
"image/color"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetGroups(t *testing.T) {
b := New(hostname, username)
groups, err := b.GetGroups()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d groups", len(groups))
for i, g := range groups {
t.Logf("%d:", i)
t.Logf(" Name: %s", g.Name)
t.Logf(" Lights: %s", g.Lights)
t.Logf(" Type: %s", g.Type)
t.Logf(" GroupState:")
t.Logf(" AllOn: %t", g.GroupState.AllOn)
t.Logf(" AnyOn: %t", g.GroupState.AnyOn)
t.Logf(" Recycle: %t", g.Recycle)
t.Logf(" Class: %s", g.Class)
t.Logf(" State:")
t.Logf(" On: %t", g.State.On)
t.Logf(" Bri: %d", g.State.Bri)
t.Logf(" Hue: %d", g.State.Hue)
t.Logf(" Sat: %d", g.State.Sat)
t.Logf(" Xy: %b", g.State.Xy)
t.Logf(" Ct: %d", g.State.Ct)
t.Logf(" Alert: %s", g.State.Alert)
t.Logf(" Effect: %s", g.State.Effect)
t.Logf(" TransitionTime: %d", g.State.TransitionTime)
t.Logf(" BriInc: %d", g.State.BriInc)
t.Logf(" SatInc: %d", g.State.SatInc)
t.Logf(" HueInc: %d", g.State.HueInc)
t.Logf(" CtInc: %d", g.State.CtInc)
t.Logf(" XyInc: %d", g.State.XyInc)
t.Logf(" ColorMode: %s", g.State.ColorMode)
t.Logf(" Reachable: %t", g.State.Reachable)
t.Logf(" ID: %d", g.ID)
}
contains := func(name string, ss []Group) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Group 1", groups))
assert.True(t, contains("Group 2", groups))
b.Host = badHostname
_, err = b.GetGroups()
assert.NotNil(t, err)
}
func TestGetGroup(t *testing.T) {
b := New(hostname, username)
g, err := b.GetGroup(1)
if err != nil {
t.Fatal(err)
}
t.Logf("Name: %s", g.Name)
t.Logf("Lights: %s", g.Lights)
t.Logf("Type: %s", g.Type)
t.Logf("GroupState:")
t.Logf(" AllOn: %t", g.GroupState.AllOn)
t.Logf(" AnyOn: %t", g.GroupState.AnyOn)
t.Logf("Recycle: %t", g.Recycle)
t.Logf("Class: %s", g.Class)
t.Logf("State:")
t.Logf(" On: %t", g.State.On)
t.Logf(" Bri: %d", g.State.Bri)
t.Logf(" Hue: %d", g.State.Hue)
t.Logf(" Sat: %d", g.State.Sat)
t.Logf(" Xy: %b", g.State.Xy)
t.Logf(" Ct: %d", g.State.Ct)
t.Logf(" Alert: %s", g.State.Alert)
t.Logf(" Effect: %s", g.State.Effect)
t.Logf(" TransitionTime: %d", g.State.TransitionTime)
t.Logf(" BriInc: %d", g.State.BriInc)
t.Logf(" SatInc: %d", g.State.SatInc)
t.Logf(" HueInc: %d", g.State.HueInc)
t.Logf(" CtInc: %d", g.State.CtInc)
t.Logf(" XyInc: %d", g.State.XyInc)
t.Logf(" ColorMode: %s", g.State.ColorMode)
t.Logf(" Reachable: %t", g.State.Reachable)
t.Logf("ID: %d", g.ID)
b.Host = badHostname
_, err = b.GetGroup(1)
assert.NotNil(t, err)
}
func TestGetEntertainmentGroup(t *testing.T) {
b := New(hostname, username)
g, err := b.GetGroup(3)
if err != nil {
t.Fatal(err)
}
if g.Stream.Active() {
t.Fatal("group stream should be inactive")
}
if owner := g.Stream.Owner(); owner != "" {
t.Fatalf("group stream should have no owner. got: %s", owner)
}
b = New(hostname, username)
g, err = b.GetGroup(4)
if err != nil {
t.Fatal(err)
}
if !g.Stream.Active() {
t.Fatal("group stream should be active")
}
if want, owner := "QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB", g.Stream.Owner(); owner != want {
t.Fatalf("group stream should have owner. got: %s, want :%s", owner, want)
}
b = New(hostname, username)
g, err = b.GetGroup(2)
if err != nil {
t.Fatal(err)
}
g.Stream = &Stream{}
if g.Stream.Active() {
t.Fatal("group stream should be inactive")
}
if owner := g.Stream.Owner(); owner != "" {
t.Fatalf("group stream should have no owner. got: %s", owner)
}
}
func TestCreateGroup(t *testing.T) {
b := New(hostname, username)
group := Group{
Name: "TestGroup",
Type: "Room",
Class: "Office",
Lights: []string{},
}
resp, err := b.CreateGroup(group)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Group created")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.CreateGroup(group)
assert.NotNil(t, err)
}
func TestUpdateGroup(t *testing.T) {
b := New(hostname, username)
id := 1
group := Group{
Name: "TestGroup (Updated)",
Class: "Office",
}
resp, err := b.UpdateGroup(id, group)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Updated group with id %d", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.UpdateGroup(id, group)
assert.NotNil(t, err)
}
func TestSetGroupState(t *testing.T) {
b := New(hostname, username)
id := 1
state := State{
On: true,
Bri: 150,
Sat: 210,
}
resp, err := b.SetGroupState(id, state)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Group state set")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.SetGroupState(id, state)
assert.NotNil(t, err)
}
func TestRenameGroup(t *testing.T) {
bridge := New(hostname, username)
id := 1
group, err := bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
newName := "MyGroup (renamed)"
err = group.Rename(newName)
if err != nil {
t.Fatal(err)
}
t.Logf("Group renamed to %s", group.Name)
bridge.Host = badHostname
err = group.Rename(newName)
assert.NotNil(t, err)
}
func TestTurnOffGroup(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Off()
if err != nil {
t.Fatal(err)
}
t.Logf("Turned off group with id %d", group.ID)
t.Logf("Group IsOn: %t", group.State.On)
b.Host = badHostname
err = group.Off()
assert.NotNil(t, err)
}
func TestTurnOnGroup(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.On()
if err != nil {
t.Fatal(err)
}
t.Logf("Turned on group with id %d", group.ID)
t.Logf("Group IsOn: %t", group.State.On)
b.Host = badHostname
err = group.On()
assert.NotNil(t, err)
}
func TestIfGroupIsOn(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
t.Logf("Is group %d on?: %t", group.ID, group.IsOn())
}
func TestSetGroupBri(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Bri(254)
if err != nil {
t.Fatal(err)
}
t.Logf("Brightness of group %d set to %d", group.ID, group.State.Bri)
b.Host = badHostname
err = group.Bri(254)
assert.NotNil(t, err)
}
func TestSetGroupHue(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Hue(65535)
if err != nil {
t.Fatal(err)
}
t.Logf("Hue of group %d set to %d", group.ID, group.State.Hue)
b.Host = badHostname
err = group.Hue(65535)
assert.NotNil(t, err)
}
func TestSetGroupSat(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Sat(254)
if err != nil {
t.Fatal(err)
}
t.Logf("Sat of group %d set to %d", group.ID, group.State.Sat)
b.Host = badHostname
err = group.Sat(254)
assert.NotNil(t, err)
}
func TestSetGroupXy(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
xy := []float32{0.1, 0.5}
err = group.Xy(xy)
if err != nil {
t.Fatal(err)
}
t.Logf("Xy of group %d set to %v", group.ID, group.State.Xy)
b.Host = badHostname
err = group.Xy(xy)
assert.NotNil(t, err)
}
func TestSetGroupCol(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
color := color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xFF}
err = group.Col(color)
if err != nil {
t.Fatal(err)
}
t.Logf("Col of group %d set to xy: %v", group.ID, group.State.Xy)
b.Host = badHostname
err = group.Col(color)
assert.NotNil(t, err)
}
func TestSetGroupCt(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Ct(16)
if err != nil {
t.Fatal(err)
}
t.Logf("Ct of group %d set to %d", group.ID, group.State.Ct)
b.Host = badHostname
err = group.Ct(16)
assert.NotNil(t, err)
}
func TestSetGroupScene(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
scene := "2hgE1nGaITvy9VQ"
err = group.Scene(scene)
if err != nil {
t.Fatal(err)
}
t.Logf("Scene of group %d set to %s", group.ID, group.State.Scene)
b.Host = badHostname
err = group.Scene(scene)
assert.NotNil(t, err)
}
func TestSetGroupTransitionTime(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.TransitionTime(10)
if err != nil {
t.Fatal(err)
}
t.Logf("TransitionTime of group %d set to %d", group.ID, group.State.TransitionTime)
b.Host = badHostname
err = group.TransitionTime(10)
assert.NotNil(t, err)
}
func TestSetGroupEffect(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Effect("colorloop")
if err != nil {
t.Fatal(err)
}
t.Logf("Effect of group %d set to %s", group.ID, group.State.Effect)
b.Host = badHostname
err = group.Effect("colorloop")
assert.NotNil(t, err)
}
func TestSetGroupAlert(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.Alert("lselect")
if err != nil {
t.Fatal(err)
}
t.Logf("Alert of group %d set to %s", group.ID, group.State.Alert)
b.Host = badHostname
err = group.Alert("lselect")
assert.NotNil(t, err)
}
func TestSetStateGroup(t *testing.T) {
b := New(hostname, username)
id := 1
group, err := b.GetGroup(id)
if err != nil {
t.Fatal(err)
}
state := State{
On: true,
Bri: 254,
}
err = group.SetState(state)
if err != nil {
t.Fatal(err)
}
t.Logf("State set successfully on group %d", id)
b.Host = badHostname
err = group.SetState(state)
assert.NotNil(t, err)
}
func TestDeleteGroup(t *testing.T) {
b := New(hostname, username)
id := 1
err := b.DeleteGroup(id)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Deleted group with id: %d", id)
}
}
func TestEnableStreamingGroup(t *testing.T) {
bridge := New(hostname, username)
id := 3
group, err := bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.EnableStreaming()
if err != nil {
t.Fatal(err)
}
id = 2
group, err = bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.EnableStreaming()
if err == nil {
t.Fatal("error was nil")
} else if errString := err.Error(); errString != "must be an entertainment group to enable streaming" {
t.Fatalf("incorrect error: %s", errString)
}
id = 5
group, err = bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.EnableStreaming()
if err == nil {
t.Fatal("error was nil")
} else if errString := err.Error(); errString != "ERROR 307 [/groups/5/stream/active]: \"Cannot claim stream ownership\"" {
t.Fatalf("incorrect error: %s", errString)
}
}
func TestDisableStreamingGroup(t *testing.T) {
bridge := New(hostname, username)
id := 3
group, err := bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.DisableStreaming()
if err != nil {
t.Fatal(err)
}
id = 2
group, err = bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.DisableStreaming()
if err == nil {
t.Fatal("error was nil")
} else if errString := err.Error(); errString != "must be an entertainment group to disable streaming" {
t.Fatalf("incorrect error %s", errString)
}
id = 6
group, err = bridge.GetGroup(id)
if err != nil {
t.Fatal(err)
}
err = group.DisableStreaming()
if err == nil {
t.Fatal("error was nil")
} else if errString := err.Error(); errString != "ERROR 999 [/groups/6/stream/active]: \"unspecified error\"" {
t.Fatalf("incorrect error %s", errString)
}
}

302
vendor/github.com/amimof/huego/huego.go generated vendored Normal file

@ -0,0 +1,302 @@
// Package huego provides an extensive, easy to use interface to the Philips Hue bridge.
package huego
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
)
const (
applicationJSON = "application/json"
contentType = "Content-Type"
)
// APIResponse holds the response data returned form the bridge after a request has been made.
type APIResponse struct {
Success map[string]interface{} `json:"success,omitempty"`
Error *APIError `json:"error,omitempty"`
}
// APIError defines the error response object returned from the bridge after an invalid API request.
type APIError struct {
Type int
Address string
Description string
}
// Response is a wrapper struct of the success response returned from the bridge after a successful API call.
type Response struct {
Success map[string]interface{}
}
// UnmarshalJSON makes sure that types are correct when unmarshalling. Implements package encoding/json
func (a *APIError) UnmarshalJSON(data []byte) error {
var aux map[string]interface{}
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
a.Type = int(aux["type"].(float64))
a.Address = aux["address"].(string)
a.Description = aux["description"].(string)
return nil
}
// Error returns an error string
func (a *APIError) Error() string {
return fmt.Sprintf("ERROR %d [%s]: \"%s\"", a.Type, a.Address, a.Description)
}
func handleResponse(a []*APIResponse) (*Response, error) {
success := map[string]interface{}{}
for _, r := range a {
if r.Success != nil {
for k, v := range r.Success {
success[k] = v
}
}
if r.Error != nil {
return nil, r.Error
}
}
resp := &Response{Success: success}
return resp, nil
}
// unmarshal will try to unmarshal data into APIResponse so that we can
// return the actual error returned by the bridge http API as an error struct.
func unmarshal(data []byte, v interface{}) error {
err := json.Unmarshal(data, &v)
if err != nil {
var a []*APIResponse
err = json.Unmarshal(data, &a)
if err != nil {
return err
}
_, err = handleResponse(a)
if err != nil {
return err
}
}
return nil
}
func get(ctx context.Context, url string, client *http.Client) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return body, nil
}
func put(ctx context.Context, url string, data []byte, client *http.Client) ([]byte, error) {
body := strings.NewReader(string(data))
req, err := http.NewRequest(http.MethodPut, url, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Set(contentType, applicationJSON)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
result, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return result, nil
}
func post(ctx context.Context, url string, data []byte, client *http.Client) ([]byte, error) {
body := strings.NewReader(string(data))
req, err := http.NewRequest(http.MethodPost, url, body)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Set(contentType, applicationJSON)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
result, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return result, nil
}
func del(ctx context.Context, url string, client *http.Client) ([]byte, error) {
req, err := http.NewRequest(http.MethodDelete, url, nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
req.Header.Set(contentType, applicationJSON)
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
result, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
return result, nil
}
// DiscoverAll performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// DiscoverAll returns a list of Bridge objects.
func DiscoverAll() ([]Bridge, error) {
return DiscoverAllContext(context.Background())
}
// DiscoverAllContext performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// DiscoverAllContext returns a list of Bridge objects.
func DiscoverAllContext(ctx context.Context) ([]Bridge, error) {
req, err := http.NewRequest(http.MethodGet, "https://discovery.meethue.com", nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
client := http.DefaultClient
res, err := client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
d, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var bridges []Bridge
err = json.Unmarshal(d, &bridges)
if err != nil {
return nil, err
}
return bridges, nil
}
// Discover performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// Discover uses DiscoverAll() but only returns the first instance in the array of bridges if any.
func Discover() (*Bridge, error) {
return DiscoverContext(context.Background())
}
// DiscoverContext performs a discovery on the network looking for bridges using https://www.meethue.com/api/nupnp service.
// DiscoverContext uses DiscoverAllContext() but only returns the first instance in the array of bridges if any.
func DiscoverContext(ctx context.Context) (*Bridge, error) {
b := &Bridge{}
bridges, err := DiscoverAllContext(ctx)
if err != nil {
return nil, err
}
if len(bridges) > 0 {
b = &bridges[0]
}
return b, nil
}
// New instantiates and returns a new Bridge. New accepts hostname/ip address to the bridge (h) as well as an username (u).
// h may or may not be prefixed with http(s)://. For example http://192.168.1.20/ or 192.168.1.20.
// u is a username known to the bridge. Use Discover() and CreateUser() to create a user.
func New(h, u string) *Bridge {
return &Bridge{
Host: h,
User: u,
ID: "",
client: http.DefaultClient,
}
}
/*NewWithClient instantiates and returns a new Bridge with a custom HTTP client.
NewWithClient accepts the same parameters as New, but with an additional acceptance of an http.Client.
- h may or may not be prefixed with http(s)://. For example http://192.168.1.20/ or 192.168.1.20.
- u is a username known to the bridge. Use Discover() and CreateUser() to create a user.
- Difference between New and NewWithClient being the ability to implement your own http.RoundTripper for proxying.*/
func NewWithClient(h, u string, client *http.Client) *Bridge {
return &Bridge{
Host: h,
User: u,
ID: "",
client: client,
}
}
/*NewCustom instantiates and returns a new Bridge. NewCustom accepts:
- a raw JSON []byte slice as input for substantiating the Bridge type
- a custom HTTP client like NewWithClient that will be used to make API requests
Note that this is for advanced users, the other "New" functions may suit you better.*/
func NewCustom(raw []byte, host string, client *http.Client) (*Bridge, error) {
br := &Bridge{}
if err := json.Unmarshal(raw, br); err != nil {
return nil, err
}
br.Host = host
br.client = client
return br, nil
}

479
vendor/github.com/amimof/huego/huego_test.go generated vendored Normal file

@ -0,0 +1,479 @@
package huego
import (
"fmt"
"os"
"path"
"testing"
"github.com/jarcoal/httpmock"
"github.com/stretchr/testify/assert"
)
// I'm too lazy to have this elsewhere
// export HUE_USERNAME=9D6iHMbM-Bt7Kd0Cwh9Quo4tE02FMnmrNrnFAdAq
// export HUE_HOSTNAME=192.168.1.59
var username string
var hostname string
var badHostname = "bad-hue-config"
func init() {
hostname = os.Getenv("HUE_HOSTNAME")
username = os.Getenv("HUE_USERNAME")
tests := []struct {
method string
path string
data string
url string
}{
// DISCOVERY
{
method: "GET",
url: "https://discovery.meethue.com",
data: `[{"id":"001788fffe73ff19","internalipaddress":"192.168.13.112"}]`,
},
// CONFIG
{
method: "GET",
path: "/config",
data: `{"name":"Philipshue","zigbeechannel":15,"mac":"00:17:88:00:00:00","dhcp":true,"ipaddress":"192.168.1.7","netmask":"255.255.255.0","gateway":"192.168.1.1","proxyaddress":"none","proxyport":0,"UTC":"2014-07-17T09:27:35","localtime":"2014-07-17T11:27:35","timezone":"Europe/Madrid","whitelist":{"ffffffffe0341b1b376a2389376a2389":{"lastusedate":"2014-07-17T07:21:38","createdate":"2014-04-08T08:55:10","name":"PhilipsHueAndroidApp#TCTALCATELONETOU"},"pAtwdCV8NZId25Gk":{"lastusedate":"2014-05-07T18:28:29","createdate":"2014-04-09T17:29:16","name":"MyApplication"},"gDN3IaPYSYNPWa2H":{"lastusedate":"2014-05-07T09:15:21","createdate":"2014-05-07T09:14:38","name":"iPhoneWeb1"}},"swversion":"01012917","apiversion":"1.3.0","swupdate":{"updatestate":2,"checkforupdate":false,"devicetypes":{"bridge":true,"lights":["1","2","3"], "sensors":["4","5","6"]},"url":"","text":"010000000","notify":false},"linkbutton":false,"portalservices":false,"portalconnection":"connected","portalstate":{"signedon":true,"incoming":false,"outgoing":true,"communication":"disconnected"}}`,
},
{
method: "PUT",
path: "/config",
data: `[{"success":{"/config/name":"My bridge"}}]`,
},
{
method: "GET",
path: path.Join("/invalid_password", "/lights"),
data: `[{"error":{"type":1,"address":"/lights","description":"unauthorized user"}}]`,
},
{
method: "GET",
path: "",
data: `{"lights":{"1":{"state":{"on":false,"bri":0,"hue":0,"sat":0,"xy":[0.0000,0.0000],"ct":0,"alert":"none","effect":"none","colormode":"hs","reachable":true},"type":"Extendedcolorlight","name":"HueLamp1","modelid":"LCT001","swversion":"65003148"},"2":{"state":{"on":true,"bri":254,"hue":33536,"sat":144,"xy":[0.3460,0.3568],"ct":201,"alert":"none","effect":"none","colormode":"hs","reachable":true},"type":"Extendedcolorlight","name":"HueLamp2","modelid":"LCT001","swversion":"65003148"}},"groups":{"1":{"action":{"on":true,"bri":254,"hue":33536,"sat":144,"xy":[0.3460,0.3568],"ct":201,"effect":"none","colormode":"xy"},"lights":["1","2"],"name":"Group1"}},"config":{"name":"Philipshue","mac":"00:00:88:00:bb:ee","dhcp":true,"ipaddress":"192.168.1.74","netmask":"255.255.255.0","gateway":"192.168.1.254","proxyaddress":"","proxyport":0,"UTC":"2012-10-29T12:00:00","whitelist":{"1028d66426293e821ecfd9ef1a0731df":{"lastusedate":"2012-10-29T12:00:00","createdate":"2012-10-29T12:00:00","name":"testuser"}},"swversion":"01003372","swupdate":{"updatestate":2,"checkforupdate":false,"devicetypes":{"bridge":true,"lights":["1","2","3"], "sensors":["4","5","6"]},"url":"","text":"010000000","notify":false},"swupdate2":{"checkforupdate":false,"lastchange":"2017-06-21T19:44:36","bridge":{"state":"noupdates","lastinstall":"2017-06-21T19:44:18"},"state":"noupdates","autoinstall":{"updatetime":"T14:00:00","on":false}},"schedules":{"1":{"name":"schedule","description":"","command":{"address":"/api/<username>/groups/0/action","body":{"on":true},"method":"PUT"},"time":"2012-10-29T12:00:00"}}}}`,
},
{
method: "POST",
path: "",
data: `[{"success":{"username": "83b7780291a6ceffbe0bd049104df", "clientkey": "33DDF493992908E3D97AAAA5A6F189E1"}}]`,
},
{
method: "DELETE",
path: "/config/whitelist/ffffffffe0341b1b376a2389376a2389",
data: `[{"success": "/config/whitelist/ffffffffe0341b1b376a2389376a2389 deleted."}]`,
},
// LIGHT
{
method: "GET",
path: path.Join(username, "/lights"),
data: `{"1":{"state":{"on":false,"bri":1,"hue":33761,"sat":254,"effect":"none","xy":[0.3171,0.3366],"ct":159,"alert":"none","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2017-10-15T12:07:34"},"type":"Extendedcolorlight","name":"Huecolorlamp7","modelid":"LCT001","manufacturername":"Philips","productname":"Huecolorlamp","capabilities":{"certified":true,"control":{"mindimlevel":5000,"maxlumen":600,"colorgamuttype":"B","colorgamut":[[0.675,0.322],[0.409,0.518],[0.167,0.04]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":false}},"config":{"archetype":"sultanbulb","function":"mixed","direction":"omnidirectional"},"uniqueid":"00:17:88:01:00:bd:c7:b9-0b","swversion":"5.105.0.21169"},"2":{"state":{"on":false,"bri":1,"hue":35610,"sat":237,"effect":"none","xy":[0.1768,0.395],"ct":153,"alert":"none","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2017-10-18T12:50:40"},"type":"Extendedcolorlight","name":"Huelightstripplus1","modelid":"LST002","manufacturername":"Philips","productname":"Huelightstripplus","capabilities":{"certified":true,"control":{"mindimlevel":40,"maxlumen":1600,"colorgamuttype":"C","colorgamut":[[0.6915,0.3083],[0.17,0.7],[0.1532,0.0475]],"ct":{"min":153,"max":500}},"streaming":{"renderer":true,"proxy":true}},"config":{"archetype":"huelightstrip","function":"mixed","direction":"omnidirectional"},"uniqueid":"00:17:88:01:02:15:97:46-0b","swversion":"5.105.0.21169"}}`,
},
{
method: "GET",
path: path.Join(username, "/lights/1"),
data: `{"state":{"on":false,"bri":1,"hue":12594,"sat":251,"effect":"none","xy":[0.5474,0.4368],"alert":"none","colormode":"xy","mode":"homeautomation","reachable":true},"swupdate":{"state":"noupdates","lastinstall":"2017-12-13T01:59:13"},"type":"Colorlight","name":"Huebloom1","modelid":"LLC011","manufacturername":"Philips","productname":"Huebloom","capabilities":{"certified":true,"control":{"mindimlevel":10000,"maxlumen":120,"colorgamuttype":"A","colorgamut":[[0.704,0.296],[0.2151,0.7106],[0.138,0.08]]},"streaming":{"renderer":true,"proxy":false}},"config":{"archetype":"huebloom","function":"decorative","direction":"upwards"},"uniqueid":"00:17:88:01:00:c5:3b:e3-0b","swversion":"5.105.1.21778"}`,
},
{
method: "GET",
path: path.Join(username, "/lights/new"),
data: `{"7":{"name":"HueLamp7"},"8":{"name":"HueLamp8"},"lastscan":"2012-10-29T12:00:00"}`,
},
{
method: "POST",
path: path.Join(username, "/lights"),
data: `[{"success":{"/lights":"Searching for new devices"}}]`,
},
{
method: "PUT",
path: path.Join(username, "/lights/1/state"),
data: `[{"success":{"/lights/1/state/bri":200}},{"success":{"/lights/1/state/on":true}},{"success":{"/lights/1/state/hue":50000}}]`,
},
{
// Second route for identifying light testing
method: "PUT",
path: path.Join(username, "/lights/2/state"),
data: `[{"success":{"/lights/2/state/alert":"select"}}]`,
},
{
method: "PUT",
path: path.Join(username, "/lights/1"),
data: `[{"success":{"/lights/1/name":"Bedroom Light"}}]`,
},
{
method: "DELETE",
path: path.Join(username, "/lights/1"),
data: `[{"success":"/lights/<id> deleted"}]`,
},
// GROUP
{
method: "GET",
path: "/groups",
data: `{"1":{"name":"Group 1","lights":["1","2"],"type":"LightGroup","state":{"all_on":true,"any_on":true},"action":{"on":true,"bri":254,"hue":10000,"sat":254,"effect":"none","xy":[0.5,0.5],"ct":250,"alert":"select","colormode":"ct"}},"2":{"name":"Group 2","lights":["3","4","5"],"type":"LightGroup","state":{"all_on":true,"any_on":true},"action":{"on":true,"bri":153,"hue":4345,"sat":254,"effect":"none","xy":[0.5,0.5],"ct":250,"alert":"select","colormode":"ct"}},"3":{"name":"Group 3","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":false,"owner":null},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}}`,
},
{
method: "GET",
path: "/groups/1",
data: `{"action":{"on":true,"hue":0,"effect":"none","bri":100,"sat":100,"ct":500,"xy":[0.5,0.5]},"lights":["1","2"],"state":{"any_on":true,"all_on":true},"type":"Room","class":"Bedroom","name":"Masterbedroom"}`,
},
{
method: "PUT",
path: "/groups/1",
data: `[{"success":{"/groups/1/lights":["1"]}},{"success":{"/groups/1/name":"Bedroom"}}]`,
},
{
method: "PUT",
path: "/groups/1/action",
data: `[{"success":{"address":"/groups/1/action/on","value":true}},{"success":{"address":"/groups/1/action/effect","value":"colorloop"}},{"success":{"address":"/groups/1/action/hue","value":6000}}]`,
},
{
method: "POST",
path: "/groups",
data: `[{"success":{"id":"1"}}]`,
},
{
method: "DELETE",
path: "/groups/1",
data: `[{"success":"/groups/1 deleted."}]`,
},
// NON-ENTERTAINMENT GROUP
{
method: "GET",
path: "/groups/2",
data: `{"name":"Office","lights":["4","5","1","2","3"],"sensors":[],"type":"Room","state":{"all_on":true,"any_on":true},"recycle":false,"class":"Office","action":{"on":true,"bri":92,"hue":53702,"sat":82,"effect":"none","xy":[ 0.3693, 0.3006],"ct":233,"alert":"select","colormode":"xy"}}`,
},
{
method: "PUT",
path: "/groups/2",
data: `[{"error":{"type":6,"address":"/groups/2/stream","description":"parameter, /groups/2/stream, not available"}}]`,
},
// INACTIVE ENTERTAINMENT GROUP
{
method: "GET",
path: "/groups/3",
data: `{"name":"Group 3","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":false,"owner":null},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
},
{
method: "PUT",
path: "/groups/3",
data: `[{"success":{"/groups/3/stream/active":true}}]`,
},
// ACTIVE ENTERTAINMENT GROUP
{
method: "GET",
path: "/groups/4",
data: `{"name":"Group 4","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":true,"owner":"QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB"},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
},
{
method: "PUT",
path: "/groups/4",
data: `[{"success":{"/groups/3/stream/active":false}}]`,
},
// ACTIVE ENTERTAINMENT GROUP FOR ENABLE ERROR
{
method: "GET",
path: "/groups/5",
data: `{"name":"Group 5","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":true,"owner":"QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB"},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
},
{
method: "PUT",
path: "/groups/5",
data: `[{"error":{"type":307,"address":"/groups/5/stream/active","description":"Cannot claim stream ownership"}}]`,
},
// ACTIVE ENTERTAINMENT GROUP FOR DISABLE ERROR
{
method: "GET",
path: "/groups/6",
data: `{"name":"Group 6","lights":["1","2","3","4","5"],"sensors":[],"type":"Entertainment","state":{"all_on":true,"any_on":true},"recycle":false,"class":"TV","stream":{"proxymode":"auto","proxynode":"/lights/3","active":true,"owner":"QZTPWY1ADZDM8IG188LBVOB5YV5O5OPZNCKTQPQB"},"locations":{"1":[0.93,-0.92,0.00],"2":[0.13,-0.85,1.00],"3":[-0.03,-0.86,1.00],"4":[-0.40,1.00,0.00],"5":[0.43,1.00,0.00]},"action":{"on":true,"bri":62,"hue":43749,"sat":189,"effect":"none","xy":[0.2133,0.2075],"ct":153,"alert":"select","colormode":"xy"}}`,
},
{
method: "PUT",
path: "/groups/6",
data: `[{"error":{"type":999,"address":"/groups/6/stream/active","description":"unspecified error"}}]`,
},
// SCENE
{
method: "GET",
path: "/scenes",
data: `{"4e1c6b20e-on-0":{"name":"Kathyon1449133269486","lights":["2","3"],"owner":"ffffffffe0341b1b376a2389376a2389","recycle":true,"locked":false,"appdata":{},"picture":"","lastupdated":"2015-12-03T08:57:13","version":1},"3T2SvsxvwteNNys":{"name":"Cozydinner","type":"GroupScene","group":"1","lights":["1","2"],"owner":"ffffffffe0341b1b376a2389376a2389","recycle":true,"locked":false,"appdata":{"version":1,"data":"myAppData"},"picture":"","lastupdated":"2015-12-03T10:09:22","version":2}}`,
},
{
method: "GET",
path: "/scenes/4e1c6b20e-on-0",
data: `{"name":"Cozy dinner","type":"GroupScene","group":"1","lights":["1"],"owner":"newdeveloper","recycle":true,"locked":false,"appdata":{},"picture":"","lastupdated":"2015-12-03T10:09:22","version":2,"lightstates":{"1":{"on":true,"bri":237,"xy":[0.5806,0.3903]}}}`,
},
{
method: "POST",
path: "/scenes",
data: `[{"success":{"address":"/scenes/ab341ef24/name","value":"Romanticdinner"}},{"success":{"address":"/scenes/ab3C41ef24/lights","value":["1","2"]}}]`,
},
{
method: "PUT",
path: "/scenes/4e1c6b20e-on-0",
data: `[{"success":{"/scenes/74bc26d5f-on-0/name":"Cozydinner"}},{"success":{"/scenes/74bc26d5f-on-0/storelightstate":true}},{"success":{"/scenes/74bc26d5f-on-0/lights":["2","3"]}}]`,
},
{
method: "PUT",
path: "/scenes/4e1c6b20e-on-0/lightstates/1",
data: `[{"success":{"address":"/scenes/ab341ef24/lights/1/state/on","value":true}},{"success":{"address":"/scenes/ab341ef24/lights/1/state/ct","value":200}}]`,
},
{
method: "DELETE",
path: "/scenes/4e1c6b20e-on-0",
data: `[{"success":"/scenes/3T2SvsxvwteNNys deleted"}]`,
},
// RULE
{
method: "GET",
path: "/rules",
data: `{ "1": { "name": "Wall Switch Rule", "lasttriggered": "2013-10-17T01:23:20", "creationtime": "2013-10-10T21:11:45", "timestriggered": 27, "owner": "78H56B12BA", "status": "enabled", "conditions": [ { "address": "/sensors/2/state/buttonevent", "operator": "eq", "value": "16" }, { "address": "/sensors/2/state/lastupdated", "operator": "dx" } ], "actions": [ { "address": "/groups/0/action", "method": "PUT", "body": { "scene": "S3" } } ] }, "2": { "name": "Wall Switch Rule 2" }} `,
},
{
method: "GET",
path: "/rules/1",
data: `{ "name": "Wall Switch Rule", "owner": "ruleOwner", "created": "2014-07-23T15:02:56", "lasttriggered": "none", "timestriggered": 0, "status": "enabled", "conditions": [ { "address": "/sensors/2/state/buttonevent", "operator": "eq", "value": "16" }, { "address": "/sensors/2/state/lastupdated", "operator": "dx" } ], "actions": [ { "address": "/groups/0/action", "method": "PUT", "body": { "scene": "S3" } } ] }`,
},
{
method: "POST",
path: "/rules",
data: `[{"success":{"id": "3"}}]`,
},
{
method: "PUT",
path: "/rules/1",
data: `[ { "success": { "/rules/1/actions": [ { "address": "/groups/0/action", "method": "PUT", "body": { "scene": "S3" } } ] } } ]`,
},
{
method: "DELETE",
path: "/rules/1",
data: `[{"success": "/rules/1 deleted."}]`,
},
// SCHEDULE
{
method: "GET",
path: "/schedules",
data: `{ "1": { "name": "Timer", "description": "", "command": { "address": "/api/s95jtYH8HUVWNkCO/groups/0/action", "body": { "scene": "02b12e930-off-0" }, "method": "PUT" }, "time": "PT00:01:00", "created": "2014-06-23T13:39:16", "status": "disabled", "autodelete": false, "starttime": "2014-06-23T13:39:16" }, "2": { "name": "Alarm", "description": "", "command": { "address": "/api/s95jtYH8HUVWNkCO/groups/0/action", "body": { "scene": "02b12e930-off-0" }, "method": "PUT" }, "localtime": "2014-06-23T19:52:00", "time": "2014-06-23T13:52:00", "created": "2014-06-23T13:38:57", "status": "disabled", "autodelete": false } }`,
},
{
method: "GET",
path: "/schedules/1",
data: `{ "name": "Wake up", "description": "My wake up alarm", "command": { "address": "/api/<username>/groups/1/action", "method": "PUT", "body": { "on": true } }, "time": "W124/T06:00:00" }`,
},
{
method: "POST",
path: "/schedules",
data: `[{"success":{"id": "2"}}]`,
},
{
method: "PUT",
path: "/schedules/1",
data: `[{ "success": {"/schedules/1/name": "Wake up"}}]`,
},
{
method: "DELETE",
path: "/schedules/1",
data: `[{"success": "/schedules/1 deleted."}]`,
},
// SENSOR
{
method: "GET",
path: "/sensors",
data: `{ "1": { "state": { "daylight": false, "lastupdated": "2014-06-27T07:38:51" }, "config": { "on": true, "long": "none", "lat": "none", "sunriseoffset": 50, "sunsetoffset": 50 }, "name": "Daylight", "type": "Daylight", "modelid": "PHDL00", "manufacturername": "Philips", "swversion": "1.0" }, "2": { "state": { "buttonevent": 0, "lastupdated": "none" }, "config": { "on": true }, "name": "Tap Switch 2", "type": "ZGPSwitch", "modelid": "ZGPSWITCH", "manufacturername": "Philips", "uniqueid": "00:00:00:00:00:40:03:50-f2" } }`,
},
{
method: "GET",
path: "/sensors/1",
data: `{ "state":{ "buttonevent": 34, "lastupdated":"2013-03-25T13:32:34" }, "name": "Wall tap 1", "modelid":"ZGPSWITCH", "uniqueid":"01:23:45:67:89:AB-12", "manufacturername": "Philips", "swversion":"1.0", "type": "ZGPSwitch" }`,
},
{
method: "POST",
path: "/sensors",
data: `[ { "success": { "/sensors": "Searching for new devices"}}]`,
},
{
method: "POST",
path: "/sensors",
data: `[{"success":{"id": "4"}}]`,
},
{
method: "GET",
path: "/sensors/new",
data: `{ "7": {"name": "Hue Tap 1"}, "8": {"name": "Button 3"}, "lastscan":"2013-05-22T10:24:00" }`,
},
{
method: "PUT",
path: "/sensors/1",
data: `[{"success":{"/sensors/2/name":"Bedroom Tap"}}]`,
},
{
method: "DELETE",
path: "/sensors/1",
data: `[{"success": "/sensors/1 deleted."}]`,
},
{
method: "PUT",
path: "/sensors/1/config",
data: `[{"success":{"/sensors/2/config/on":true}}]`,
},
{
method: "PUT",
path: "/sensors/1/state",
data: `[{"success":{"/sensors/1/state/presence": false}}]`,
},
// CAPABILITIES
{
method: "GET",
path: "/capabilities",
data: `{ "lights":{ "available": 10 }, "sensors":{ "available": 60, "clip": { "available": 60 }, "zll": { "available": 60 }, "zgp": { "available": 60 } }, "groups": {}, "scenes": { "available": 100, "lightstates": { "available": 1500 } }, "rules": {}, "schedules": {}, "resourcelinks": {}, "whitelists": {}, "timezones": { "values":[ "Africa/Abidjan", "Africa/Accra", "Pacific/Wallis", "US/Pacific-New" ] } }`,
},
// RESOURCELINK
{
method: "GET",
path: "/resourcelinks",
data: `{ "1": { "name": "Sunrise", "description": "Carla's wakeup experience", "class": 1, "owner": "78H56B12BAABCDEF", "links": ["/schedules/2", "/schedules/3", "/scenes/ABCD", "/scenes/EFGH", "/groups/8"] }, "2": { "name": "Sunrise 2" } }`,
},
{
method: "GET",
path: "/resourcelinks/1",
data: `{ "name": "Sunrise", "description": "Carla's wakeup experience", "type":"Link", "class": 1, "owner": "78H56B12BAABCDEF", "links": ["/schedules/2", "/schedules/3", "/scenes/ABCD", "/scences/EFGH", "/groups/8"] }`,
},
{
method: "POST",
path: "/resourcelinks",
data: `[{"success":{"id": "3"}}]`,
},
{
method: "PUT",
path: "/resourcelinks/1",
data: `[{ "success": { "/resourcelinks/1/name": "Sunrise" } }, { "success": { "/resourcelinks/1/description": "Carla's wakeup experience" } }]`,
},
{
method: "DELETE",
path: "/resourcelinks/1",
data: `[{"success": "/resourcelinks/1 deleted."}]`,
},
}
httpmock.Activate()
for _, test := range tests {
if test.url == "" {
test.url = fmt.Sprintf("http://%s/api%s", hostname, test.path)
}
httpmock.RegisterResponder(test.method, test.url, httpmock.NewStringResponder(200, test.data))
}
// Register a responder for bad requests
paths := []string{
"",
"config",
"/config",
"/config",
"/config/whitelist/ffffffffe0341b1b376a2389376a2389",
path.Join("/invalid_password", "/lights"),
path.Join(username, "/lights"),
path.Join(username, "/lights/1"),
path.Join(username, "/lights/new"),
path.Join(username, "/lights"),
path.Join(username, "/lights/1/state"),
path.Join(username, "/lights/1"),
path.Join(username, "/lights/1"),
"/groups",
"/groups/1",
"/groups/1",
"/groups/1/action",
"/groups",
"/groups/1",
"/scenes",
"/scenes/4e1c6b20e-on-0",
"/scenes",
"/scenes/4e1c6b20e-on-0",
"/scenes/4e1c6b20e-on-0/lightstates/1",
"/scenes/4e1c6b20e-on-0",
"/rules",
"/rules/1",
"/rules",
"/rules/1",
"/rules/1",
"/schedules",
"/schedules/1",
"/schedules",
"/schedules/1",
"/schedules/1",
"/sensors",
"/sensors/1",
"/sensors",
"/sensors",
"/sensors/new",
"/sensors/1",
"/sensors/1",
"/sensors/1/config",
"/sensors/1/state",
"/capabilities",
"/resourcelinks",
"/resourcelinks/1",
"/resourcelinks",
"/resourcelinks/1",
"/resourcelinks/1",
}
// Register responder for errors
for _, p := range paths {
response := []byte("not json")
httpmock.RegisterResponder("GET", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
httpmock.RegisterResponder("POST", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
httpmock.RegisterResponder("PUT", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
httpmock.RegisterResponder("DELETE", fmt.Sprintf("http://%s/api%s", badHostname, p), httpmock.NewBytesResponder(200, response))
}
}
func TestDiscoverAndLogin(t *testing.T) {
bridge, err := Discover()
if err != nil {
t.Fatal(err)
}
bridge = bridge.Login(username)
t.Logf("Successfully logged in to bridge")
}
func TestDiscoverAllBridges(t *testing.T) {
bridges, err := DiscoverAll()
if err != nil {
t.Fatal(err)
}
t.Logf("Discovered %d bridge(s)", len(bridges))
for i, bridge := range bridges {
t.Logf("%d: ", i)
t.Logf(" Host: %s", bridge.Host)
t.Logf(" User: %s", bridge.User)
t.Logf(" ID: %s", bridge.ID)
}
}
func Test_unmarshalError(t *testing.T) {
s := struct {
Name string `json:"name"`
}{
Name: "amimof",
}
err := unmarshal([]byte(`not json`), s)
assert.NotNil(t, err)
}

309
vendor/github.com/amimof/huego/light.go generated vendored Normal file

@ -0,0 +1,309 @@
package huego
import (
"context"
"image/color"
"math"
)
// Light represents a bridge light https://developers.meethue.com/documentation/lights-api
type Light struct {
State *State `json:"state,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
ModelID string `json:"modelid,omitempty"`
ManufacturerName string `json:"manufacturername,omitempty"`
UniqueID string `json:"uniqueid,omitempty"`
SwVersion string `json:"swversion,omitempty"`
SwConfigID string `json:"swconfigid,omitempty"`
ProductName string `json:"productname,omitempty"`
ID int `json:"-"`
bridge *Bridge
}
// State defines the attributes and properties of a light
type State struct {
On bool `json:"on"`
Bri uint8 `json:"bri,omitempty"`
Hue uint16 `json:"hue,omitempty"`
Sat uint8 `json:"sat,omitempty"`
Xy []float32 `json:"xy,omitempty"`
Ct uint16 `json:"ct,omitempty"`
Alert string `json:"alert,omitempty"`
Effect string `json:"effect,omitempty"`
TransitionTime uint16 `json:"transitiontime,omitempty"`
BriInc int `json:"bri_inc,omitempty"`
SatInc int `json:"sat_inc,omitempty"`
HueInc int `json:"hue_inc,omitempty"`
CtInc int `json:"ct_inc,omitempty"`
XyInc int `json:"xy_inc,omitempty"`
ColorMode string `json:"colormode,omitempty"`
Reachable bool `json:"reachable,omitempty"`
Scene string `json:"scene,omitempty"`
}
// NewLight defines a list of lights discovered the last time the bridge performed a light discovery.
// Also stores the timestamp the last time a discovery was performed.
type NewLight struct {
Lights []string
LastScan string `json:"lastscan"`
}
// SetState sets the state of the light to s.
func (l *Light) SetState(s State) error {
return l.SetStateContext(context.Background(), s)
}
// SetStateContext sets the state of the light to s.
func (l *Light) SetStateContext(ctx context.Context, s State) error {
_, err := l.bridge.SetLightStateContext(ctx, l.ID, s)
if err != nil {
return err
}
l.State = &s
return nil
}
// Off sets the On state of one light to false, turning it off
func (l *Light) Off() error {
return l.OffContext(context.Background())
}
// OffContext sets the On state of one light to false, turning it off
func (l *Light) OffContext(ctx context.Context) error {
state := State{On: false}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, state)
if err != nil {
return err
}
l.State.On = false
return nil
}
// On sets the On state of one light to true, turning it on
func (l *Light) On() error {
return l.OnContext(context.Background())
}
// OnContext sets the On state of one light to true, turning it on
func (l *Light) OnContext(ctx context.Context) error {
state := State{On: true}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, state)
if err != nil {
return err
}
l.State.On = true
return nil
}
// IsOn returns true if light state On property is true
func (l *Light) IsOn() bool {
return l.State.On
}
// Rename sets the name property of the light
func (l *Light) Rename(new string) error {
return l.RenameContext(context.Background(), new)
}
// RenameContext sets the name property of the light
func (l *Light) RenameContext(ctx context.Context, new string) error {
update := Light{Name: new}
_, err := l.bridge.UpdateLightContext(ctx, l.ID, update)
if err != nil {
return err
}
l.Name = new
return nil
}
// Bri sets the light brightness state property
func (l *Light) Bri(new uint8) error {
return l.BriContext(context.Background(), new)
}
// BriContext sets the light brightness state property
func (l *Light) BriContext(ctx context.Context, new uint8) error {
update := State{On: true, Bri: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Bri = new
l.State.On = true
return nil
}
// Hue sets the light hue state property (0-65535)
func (l *Light) Hue(new uint16) error {
return l.HueContext(context.Background(), new)
}
// HueContext sets the light hue state property (0-65535)
func (l *Light) HueContext(ctx context.Context, new uint16) error {
update := State{On: true, Hue: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Hue = new
l.State.On = true
return nil
}
// Sat sets the light saturation state property (0-254)
func (l *Light) Sat(new uint8) error {
return l.SatContext(context.Background(), new)
}
// SatContext sets the light saturation state property (0-254)
func (l *Light) SatContext(ctx context.Context, new uint8) error {
update := State{On: true, Sat: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Sat = new
l.State.On = true
return nil
}
// Xy sets the x and y coordinates of a color in CIE color space. (0-1 per value)
func (l *Light) Xy(new []float32) error {
return l.XyContext(context.Background(), new)
}
// XyContext sets the x and y coordinates of a color in CIE color space. (0-1 per value)
func (l *Light) XyContext(ctx context.Context, new []float32) error {
update := State{On: true, Xy: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Xy = new
l.State.On = true
return nil
}
// Ct sets the light color temperature state property
func (l *Light) Ct(new uint16) error {
return l.CtContext(context.Background(), new)
}
// CtContext sets the light color temperature state property
func (l *Light) CtContext(ctx context.Context, new uint16) error {
update := State{On: true, Ct: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Ct = new
l.State.On = true
return nil
}
// Col sets the light color as RGB (will be converted to xy)
func (l *Light) Col(new color.Color) error {
return l.ColContext(context.Background(), new)
}
// ColContext sets the light color as RGB (will be converted to xy)
func (l *Light) ColContext(ctx context.Context, new color.Color) error {
xy, bri := ConvertRGBToXy(new)
update := State{On: true, Xy: xy, Bri: bri}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Xy = xy
l.State.Bri = bri
l.State.On = true
return nil
}
// TransitionTime sets the duration of the transition from the lights current state to the new state
func (l *Light) TransitionTime(new uint16) error {
return l.TransitionTimeContext(context.Background(), new)
}
// TransitionTimeContext sets the duration of the transition from the lights current state to the new state
func (l *Light) TransitionTimeContext(ctx context.Context, new uint16) error {
update := State{On: l.State.On, TransitionTime: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.TransitionTime = new
return nil
}
// Effect the dynamic effect of the light, currently “none” and “colorloop” are supported
func (l *Light) Effect(new string) error {
return l.EffectContext(context.Background(), new)
}
// EffectContext the dynamic effect of the light, currently “none” and “colorloop” are supported
func (l *Light) EffectContext(ctx context.Context, new string) error {
update := State{On: true, Effect: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Effect = new
l.State.On = true
return nil
}
// Alert makes the light blink in its current color. Supported values are:
// “none” The light is not performing an alert effect.
// “select” The light is performing one breathe cycle.
// “lselect” The light is performing breathe cycles for 15 seconds or until alert is set to "none".
func (l *Light) Alert(new string) error {
return l.AlertContext(context.Background(), new)
}
// AlertContext makes the light blink in its current color. Supported values are:
// “none” The light is not performing an alert effect.
// “select” The light is performing one breathe cycle.
// “lselect” The light is performing breathe cycles for 15 seconds or until alert is set to "none".
func (l *Light) AlertContext(ctx context.Context, new string) error {
update := State{On: true, Alert: new}
_, err := l.bridge.SetLightStateContext(ctx, l.ID, update)
if err != nil {
return err
}
l.State.Effect = new
l.State.On = true
return nil
}
// ConvertRGBToXy converts a given RGB color to the xy color of the ligth.
// implemented as in https://developers.meethue.com/develop/application-design-guidance/color-conversion-formulas-rgb-to-xy-and-back/
func ConvertRGBToXy(newcolor color.Color) ([]float32, uint8) {
r, g, b, _ := newcolor.RGBA()
rf := float64(r) / 65536.0
gf := float64(g) / 65536.0
bf := float64(b) / 65536.0
rf = gammaCorrect(rf)
gf = gammaCorrect(gf)
bf = gammaCorrect(bf)
X := float32(rf*0.649926 + gf*0.103455 + bf*0.197109)
Y := float32(rf*0.234327 + gf*0.743075 + bf*0.022598)
Z := float32(rf*0.0000000 + gf*0.053077 + bf*1.035763)
x := X / (X + Y + Z)
y := Y / (X + Y + Z)
xy := []float32{x, y}
return xy, uint8(Y * 254)
}
func gammaCorrect(value float64) float64 {
if value > 0.04045 {
return math.Pow((value+0.055)/(1.0+0.055), 2.4)
}
return (value / 12.92)
}

431
vendor/github.com/amimof/huego/light_test.go generated vendored Normal file

@ -0,0 +1,431 @@
package huego
import (
"image/color"
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetLights(t *testing.T) {
b := New(hostname, username)
lights, err := b.GetLights()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d lights", len(lights))
for _, l := range lights {
t.Logf("ID: %d", l.ID)
t.Logf(" Type: %s", l.Type)
t.Logf(" Name: %s", l.Name)
t.Logf(" ModelID: %s", l.ModelID)
t.Logf(" ManufacturerName: %s", l.ManufacturerName)
t.Logf(" UniqueID: %s", l.UniqueID)
t.Logf(" SwVersion: %s", l.SwVersion)
t.Logf(" SwConfigID: %s", l.SwConfigID)
t.Logf(" ProductName: %s", l.ProductName)
}
contains := func(name string, ss []Light) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Huecolorlamp7", lights))
assert.True(t, contains("Huelightstripplus1", lights))
b.Host = badHostname
_, err = b.GetLights()
assert.NotNil(t, err)
}
func TestGetLight(t *testing.T) {
b := New(hostname, username)
l, err := b.GetLight(1)
if err != nil {
t.Fatal(err)
} else {
t.Logf("ID: %d", l.ID)
t.Logf("Type: %s", l.Type)
t.Logf("Name: %s", l.Name)
t.Logf("ModelID: %s", l.ModelID)
t.Logf("ManufacturerName: %s", l.ManufacturerName)
t.Logf("UniqueID: %s", l.UniqueID)
t.Logf("SwVersion: %s", l.SwVersion)
t.Logf("SwConfigID: %s", l.SwConfigID)
t.Logf("ProductName: %s", l.ProductName)
t.Logf("State:")
t.Logf(" On: %t", l.State.On)
t.Logf(" Bri: %d", l.State.Bri)
t.Logf(" Hue: %d", l.State.Hue)
t.Logf(" Sat: %d", l.State.Sat)
t.Logf(" Xy: %b", l.State.Xy)
t.Logf(" Ct: %d", l.State.Ct)
t.Logf(" Alert: %s", l.State.Alert)
t.Logf(" Effect: %s", l.State.Effect)
t.Logf(" TransitionTime: %d", l.State.TransitionTime)
t.Logf(" BriInc: %d", l.State.BriInc)
t.Logf(" SatInc: %d", l.State.SatInc)
t.Logf(" HueInc: %d", l.State.HueInc)
t.Logf(" CtInc: %d", l.State.CtInc)
t.Logf(" XyInc: %d", l.State.XyInc)
t.Logf(" ColorMode: %s", l.State.ColorMode)
t.Logf(" Reachable: %t", l.State.Reachable)
}
b.Host = badHostname
_, err = b.GetLight(1)
assert.NotNil(t, err)
}
func TestIdentifyLight(t *testing.T) {
b := New(hostname, username)
id := 2
resp, err := b.IdentifyLight(id)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Light %d identified", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.IdentifyLight(id)
assert.NotNil(t, err)
}
func TestSetLight(t *testing.T) {
b := New(hostname, username)
id := 1
state := State{
On: true,
Bri: 254,
}
resp, err := b.SetLightState(id, state)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Light %d state updated", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.SetLightState(id, state)
assert.NotNil(t, err)
}
func TestFindLights(t *testing.T) {
b := New(hostname, username)
resp, err := b.FindLights()
if err != nil {
t.Fatal(err)
} else {
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.FindLights()
assert.NotNil(t, err)
}
func TestUpdateLight(t *testing.T) {
b := New(hostname, username)
id := 1
light := Light{
Name: "New Light",
}
resp, err := b.UpdateLight(id, light)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Light %d updated", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.UpdateLight(id, light)
assert.NotNil(t, err)
}
func TestTurnOffLight(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Off()
if err != nil {
t.Fatal(err)
}
assert.False(t, light.IsOn())
t.Logf("Turned off light with id %d", light.ID)
b.Host = badHostname
err = light.Off()
assert.NotNil(t, err)
}
func TestTurnOnLight(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.On()
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Turned on light with id %d", light.ID)
b.Host = badHostname
err = light.On()
assert.NotNil(t, err)
}
func TestIfLightIsOn(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
t.Logf("Is light %d on?: %t", light.ID, light.IsOn())
}
func TestRenameLight(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
name := "Color Lamp 1"
err = light.Rename(name)
if err != nil {
t.Fatal(err)
}
t.Logf("Renamed light to '%s'", light.Name)
b.Host = badHostname
err = light.Rename(name)
assert.NotNil(t, err)
}
func TestSetLightBri(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Bri(254)
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Brightness of light %d set to %d", light.ID, light.State.Bri)
b.Host = badHostname
err = light.Bri(254)
assert.NotNil(t, err)
}
func TestSetLightHue(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Hue(65535)
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Hue of light %d set to %d", light.ID, light.State.Hue)
b.Host = badHostname
err = light.Hue(65535)
assert.NotNil(t, err)
}
func TestSetLightSat(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Sat(254)
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Sat of light %d set to %d", light.ID, light.State.Sat)
b.Host = badHostname
err = light.Sat(254)
assert.NotNil(t, err)
}
func TestSetLightXy(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
xy := []float32{0.1, 0.5}
err = light.Xy(xy)
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Xy of light %d set to %+v", light.ID, light.State.Xy)
b.Host = badHostname
err = light.Xy(xy)
assert.NotNil(t, err)
}
func TestSetLightCt(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Ct(16)
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Ct of light %d set to %d", light.ID, light.State.Ct)
b.Host = badHostname
err = light.Ct(16)
assert.NotNil(t, err)
}
func TestSetLightColor(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
color := color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xFF}
err = light.Col(color)
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Col of light %d set to xy: %+v", light.ID, light.State.Xy)
b.Host = badHostname
err = light.Col(color)
assert.NotNil(t, err)
}
func TestSetLightTransitionTime(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.TransitionTime(10)
if err != nil {
t.Fatal(err)
}
t.Logf("TransitionTime of light %d set to %d", light.ID, light.State.TransitionTime)
b.Host = badHostname
err = light.TransitionTime(10)
assert.NotNil(t, err)
}
func TestSetLightEffect(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Effect("colorloop")
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Effect of light %d set to %s", light.ID, light.State.Effect)
b.Host = badHostname
err = light.Effect("colorloop")
assert.NotNil(t, err)
}
func TestSetLightAlert(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
err = light.Alert("lselect")
if err != nil {
t.Fatal(err)
}
assert.True(t, light.IsOn())
t.Logf("Alert of light %d set to %s", light.ID, light.State.Alert)
b.Host = badHostname
err = light.Alert("lselect")
assert.NotNil(t, err)
}
func TestSetStateLight(t *testing.T) {
b := New(hostname, username)
id := 1
light, err := b.GetLight(id)
if err != nil {
t.Fatal(err)
}
state := State{
On: true,
Bri: 254,
}
err = light.SetState(state)
if err != nil {
t.Fatal(err)
}
t.Logf("State set successfully on light %d", id)
b.Host = badHostname
err = light.SetState(state)
assert.NotNil(t, err)
}
func TestConvertRGBToXY(t *testing.T) {
color := color.RGBA{R: 0xff, G: 0x00, B: 0x00, A: 0xFF}
xy, brightness := ConvertRGBToXy(color)
assert.Greater(t, xy[0], float32(0))
assert.Greater(t, xy[1], float32(0))
assert.Greater(t, brightness, uint8(0))
t.Logf("Xy of light %+v set to xy: %+v, bright: %d ", color, xy, brightness)
}

BIN
vendor/github.com/amimof/huego/logo/logo.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

229
vendor/github.com/amimof/huego/logo/logo.svg generated vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 136 KiB

12
vendor/github.com/amimof/huego/resourcelink.go generated vendored Normal file

@ -0,0 +1,12 @@
package huego
// Resourcelink represents a bridge resourcelink https://developers.meethue.com/documentation/resourcelinks-api
type Resourcelink struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Type string `json:"type,omitempty"`
ClassID uint16 `json:"classid,omitempty"`
Owner string `json:"owner,omitempty"`
Links []string `json:"links,omitempty"`
ID int `json:",omitempty"`
}

115
vendor/github.com/amimof/huego/resourcelink_test.go generated vendored Normal file

@ -0,0 +1,115 @@
package huego
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetResourcelinks(t *testing.T) {
b := New(hostname, username)
resourcelinks, err := b.GetResourcelinks()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d resourcelinks", len(resourcelinks))
for i, resourcelink := range resourcelinks {
t.Logf("%d", i)
t.Logf(" Name: %s", resourcelink.Name)
t.Logf(" Description: %s", resourcelink.Description)
t.Logf(" Type: %s", resourcelink.Type)
t.Logf(" ClassID: %d", resourcelink.ClassID)
t.Logf(" Owner: %s", resourcelink.Owner)
t.Logf(" Links: %s", resourcelink.Links)
t.Logf(" ID: %d", resourcelink.ID)
}
contains := func(name string, ss []*Resourcelink) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Sunrise", resourcelinks))
assert.True(t, contains("Sunrise 2", resourcelinks))
b.Host = badHostname
_, err = b.GetResourcelinks()
assert.NotNil(t, err)
}
func TestGetResourcelink(t *testing.T) {
b := New(hostname, username)
l, err := b.GetResourcelink(1)
if err != nil {
t.Fatal(err)
}
t.Logf("Name: %s", l.Name)
t.Logf("Description: %s", l.Description)
t.Logf("Type: %s", l.Type)
t.Logf("ClassID: %d", l.ClassID)
t.Logf("Owner: %s", l.Owner)
t.Logf("Links: %s", l.Links)
t.Logf("ID: %d", l.ID)
b.Host = badHostname
_, err = b.GetResourcelink(1)
assert.NotNil(t, err)
}
func TestCreateResourcelink(t *testing.T) {
b := New(hostname, username)
resourcelink := &Resourcelink{
Name: "Huego Test Resourcelink",
Description: "Amir's wakeup experience",
Type: "Link",
ClassID: 1,
Owner: "78H56B12BAABCDEF",
Links: []string{"/schedules/2", "/schedules/3", "/scenes/ABCD"},
}
resp, err := b.CreateResourcelink(resourcelink)
if err != nil {
t.Fatal(err)
}
t.Logf("Resourcelink created")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.CreateResourcelink(resourcelink)
assert.NotNil(t, err)
}
func TestUpdateResourcelink(t *testing.T) {
b := New(hostname, username)
id := 1
rl := &Resourcelink{
Name: "New Resourcelink",
Description: "Updated Attribute",
}
resp, err := b.UpdateResourcelink(id, rl)
if err != nil {
t.Fatal(err)
}
t.Logf("Resourcelink %d updated", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.UpdateResourcelink(id, rl)
assert.NotNil(t, err)
}
func TestDeleteResourcelink(t *testing.T) {
b := New(hostname, username)
id := 1
err := b.DeleteResourcelink(1)
if err != nil {
t.Fatal(err)
}
t.Logf("Resourcelink %d deleted", id)
}

28
vendor/github.com/amimof/huego/rule.go generated vendored Normal file

@ -0,0 +1,28 @@
package huego
// Rule represents a bridge rule https://developers.meethue.com/documentation/rules-api
type Rule struct {
Name string `json:"name,omitempty"`
LastTriggered string `json:"lasttriggered,omitempty"`
CreationTime string `json:"creationtime,omitempty"`
TimesTriggered int `json:"timestriggered,omitempty"`
Owner string `json:"owner,omitempty"`
Status string `json:"status,omitempty"`
Conditions []*Condition `json:"conditions,omitempty"`
Actions []*RuleAction `json:"actions,omitempty"`
ID int `json:",omitempty"`
}
// Condition defines the condition of a rule
type Condition struct {
Address string `json:"address,omitempty"`
Operator string `json:"operator,omitempty"`
Value string `json:"value,omitempty"`
}
// RuleAction defines the rule to execute when a rule triggers
type RuleAction struct {
Address string `json:"address,omitempty"`
Method string `json:"method,omitempty"`
Body interface{} `json:"body,omitempty"`
}

121
vendor/github.com/amimof/huego/rule_test.go generated vendored Normal file

@ -0,0 +1,121 @@
package huego
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetRules(t *testing.T) {
b := New(hostname, username)
rules, err := b.GetRules()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d rules", len(rules))
for _, rule := range rules {
t.Log(rule)
}
contains := func(name string, ss []*Rule) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Wall Switch Rule", rules))
assert.True(t, contains("Wall Switch Rule 2", rules))
b.Host = badHostname
_, err = b.GetRules()
assert.NotNil(t, err)
}
func TestGetRule(t *testing.T) {
b := New(hostname, username)
l, err := b.GetRule(1)
if err != nil {
t.Fatal(err)
} else {
t.Log(l)
}
b.Host = badHostname
_, err = b.GetRule(1)
assert.NotNil(t, err)
}
func TestCreateRule(t *testing.T) {
b := New(hostname, username)
conditions := []*Condition{
{
Address: "/sensors/2/state/buttonevent",
Operator: "eq",
Value: "16",
},
}
actions := []*RuleAction{
{
Address: "/groups/0/action",
Method: "PUT",
Body: &State{On: true},
},
}
rule := &Rule{
Name: "Huego Test Rule",
Conditions: conditions,
Actions: actions,
}
resp, err := b.CreateRule(rule)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Rule created")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.CreateRule(rule)
assert.NotNil(t, err)
}
func TestUpdateRule(t *testing.T) {
b := New(hostname, username)
id := 1
rule := &Rule{
Actions: []*RuleAction{
{
Address: "/groups/1/action",
Method: "PUT",
Body: &State{On: true},
},
},
}
resp, err := b.UpdateRule(id, rule)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Rule %d updated", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.UpdateRule(id, rule)
assert.NotNil(t, err)
}
func TestDeleteRule(t *testing.T) {
b := New(hostname, username)
id := 1
err := b.DeleteRule(id)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Rule %d deleted", id)
}
}

37
vendor/github.com/amimof/huego/scene.go generated vendored Normal file

@ -0,0 +1,37 @@
package huego
import "context"
// Scene represents a bridge scene https://developers.meethue.com/documentation/scenes-api
type Scene struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Group string `json:"group,omitempty"`
Lights []string `json:"lights,omitempty"`
Owner string `json:"owner,omitempty"`
Recycle bool `json:"recycle"`
Locked bool `json:"locked,omitempty"`
AppData interface{} `json:"appdata,omitempty"`
Picture string `json:"picture,omitempty"`
LastUpdated string `json:"lastupdated,omitempty"`
Version int `json:"version,omitempty"`
StoreLightState bool `json:"storelightstate,omitempty"`
LightStates map[int]State `json:"lightstates,omitempty"`
TransitionTime uint16 `json:"transitiontime,omitempty"`
ID string `json:"-"`
bridge *Bridge
}
// Recall will recall the scene in the group identified by id
func (s *Scene) Recall(id int) error {
return s.RecallContext(context.Background(), id)
}
// RecallContext will recall the scene in the group identified by id
func (s *Scene) RecallContext(ctx context.Context, id int) error {
_, err := s.bridge.RecallSceneContext(ctx, s.ID, id)
if err != nil {
return err
}
return nil
}

198
vendor/github.com/amimof/huego/scene_test.go generated vendored Normal file

@ -0,0 +1,198 @@
package huego
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetScenes(t *testing.T) {
b := New(hostname, username)
scenes, err := b.GetScenes()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d scenes", len(scenes))
for i, scene := range scenes {
t.Logf("%d", i)
t.Logf(" Name: %s", scene.Name)
t.Logf(" Type: %s", scene.Type)
t.Logf(" Group: %s", scene.Group)
t.Logf(" Lights: %s", scene.Lights)
t.Logf(" Owner: %s", scene.Owner)
t.Logf(" Recycle: %t", scene.Recycle)
t.Logf(" Locked: %t", scene.Locked)
t.Logf(" AppData: %s", scene.AppData)
t.Logf(" Picture: %s", scene.Picture)
t.Logf(" LastUpdated: %s", scene.LastUpdated)
t.Logf(" Version: %d", scene.Version)
t.Logf(" StoreLightState: %t", scene.StoreLightState)
t.Logf(" ID: %s", scene.ID)
}
contains := func(name string, ss []Scene) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Kathyon1449133269486", scenes))
assert.True(t, contains("Cozydinner", scenes))
b.Host = badHostname
_, err = b.GetScenes()
assert.NotNil(t, err)
}
func TestGetScene(t *testing.T) {
b := New(hostname, username)
id := "4e1c6b20e-on-0"
s, err := b.GetScene(id)
if err != nil {
t.Fatal(err)
}
t.Logf(" Name: %s", s.Name)
t.Logf(" Type: %s", s.Type)
t.Logf(" Group: %s", s.Group)
t.Logf(" Lights: %s", s.Lights)
t.Logf(" Owner: %s", s.Owner)
t.Logf(" Recycle: %t", s.Recycle)
t.Logf(" Locked: %t", s.Locked)
t.Logf(" AppData: %s", s.AppData)
t.Logf(" Picture: %s", s.Picture)
t.Logf(" LastUpdated: %s", s.LastUpdated)
t.Logf(" Version: %d", s.Version)
t.Logf(" StoreLightState: %t", s.StoreLightState)
t.Logf(" ID: %s", s.ID)
t.Logf(" LightStates: %d", len(s.LightStates))
for k := range s.LightStates {
t.Logf(" Light %d: %+v", k, s.LightStates[k])
}
b.Host = badHostname
_, err = b.GetScene(id)
assert.NotNil(t, err)
}
func TestCreateScene(t *testing.T) {
b := New(hostname, username)
scene := &Scene{
Name: "New Scene",
Lights: []string{"4", "5"},
Recycle: true,
}
resp, err := b.CreateScene(scene)
if err != nil {
t.Fatal(err)
}
t.Logf("Scene created")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.CreateScene(scene)
assert.NotNil(t, err)
}
func TestUpdateScene(t *testing.T) {
b := New(hostname, username)
scene, err := b.GetScene("4e1c6b20e-on-0")
if err != nil {
t.Fatal(err)
}
newscene := &Scene{
Name: "New Scene",
Lights: []string{},
}
resp, err := b.UpdateScene(scene.ID, newscene)
if err != nil {
t.Fatal(err)
}
t.Logf("Scene '%s' (%s) updated", scene.Name, scene.ID)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.UpdateScene(scene.ID, newscene)
assert.NotNil(t, err)
}
func TestSetSceneLightState(t *testing.T) {
b := New(hostname, username)
scene, err := b.GetScene("4e1c6b20e-on-0")
if err != nil {
t.Fatal(err)
}
light := 1
state := &State{
On: true,
Bri: 255,
}
t.Logf("Name: %s", scene.Name)
t.Logf("ID: %s", scene.ID)
t.Logf("LightStates: %+v", scene.LightStates)
_, err = b.SetSceneLightState(scene.ID, light, state)
if err != nil {
t.Fatal(err)
}
t.Logf("Successfully set the state of light %d of scene '%s'", light, scene.Name)
b.Host = badHostname
_, err = b.SetSceneLightState(scene.ID, light, state)
assert.NotNil(t, err)
}
func TestDeleteScene(t *testing.T) {
b := New(hostname, username)
scene, err := b.GetScene("4e1c6b20e-on-0")
if err != nil {
t.Fatal(err)
}
err = b.DeleteScene(scene.ID)
if err != nil {
t.Fatal(err)
}
t.Logf("Scene %s (%s) deleted", scene.Name, scene.ID)
}
func TestRecallScene(t *testing.T) {
b := New(hostname, username)
scene := "4e1c6b20e-on-0"
group := 1
resp, err := b.RecallScene("HcK1mEcgS7gcVcT", group)
if err != nil {
t.Fatal(err)
}
t.Logf("Scene %s recalled in group %d", scene, group)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.RecallScene("HcK1mEcgS7gcVcT", group)
assert.NotNil(t, err)
}
func TestRecallScene2(t *testing.T) {
b := New(hostname, username)
group := 1
scene, err := b.GetScene("4e1c6b20e-on-0")
if err != nil {
t.Fatal(err)
}
err = scene.Recall(group)
if err != nil {
t.Fatal(err)
}
t.Logf("Scene %s (%s) recalled in group %d", scene.Name, scene.ID, group)
b.Host = badHostname
err = scene.Recall(group)
assert.NotNil(t, err)
}

21
vendor/github.com/amimof/huego/schedule.go generated vendored Normal file

@ -0,0 +1,21 @@
package huego
// Schedule represents a bridge schedule https://developers.meethue.com/documentation/schedules-api-0
type Schedule struct {
Name string `json:"name"`
Description string `json:"description"`
Command *Command `json:"command"`
Time string `json:"time,omitempty"`
LocalTime string `json:"localtime"`
StartTime string `json:"starttime,omitempty"`
Status string `json:"status,omitempty"`
AutoDelete bool `json:"autodelete,omitempty"`
ID int `json:"-"`
}
// Command defines the request to be made when the schedule occurs
type Command struct {
Address string `json:"address"`
Method string `json:"method"`
Body interface{} `json:"body"`
}

129
vendor/github.com/amimof/huego/schedule_test.go generated vendored Normal file

@ -0,0 +1,129 @@
package huego
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetSchedules(t *testing.T) {
b := New(hostname, username)
schedules, err := b.GetSchedules()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d schedules", len(schedules))
for i, schedule := range schedules {
t.Logf("%d:", i)
t.Logf(" Name: %s", schedule.Name)
t.Logf(" Description: %s", schedule.Description)
t.Logf(" Command:")
t.Logf(" Address: %s", schedule.Command.Address)
t.Logf(" Method: %s", schedule.Command.Method)
t.Logf(" Body: %s", schedule.Command.Body)
t.Logf(" Time: %s", schedule.Time)
t.Logf(" LocalTime: %s", schedule.LocalTime)
t.Logf(" StartTime: %s", schedule.StartTime)
t.Logf(" Status: %s", schedule.Status)
t.Logf(" AutoDelete: %t", schedule.AutoDelete)
t.Logf(" ID: %d", schedule.ID)
}
contains := func(name string, ss []*Schedule) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Timer", schedules))
assert.True(t, contains("Alarm", schedules))
b.Host = badHostname
_, err = b.GetSchedules()
assert.NotNil(t, err)
}
func TestGetSchedule(t *testing.T) {
b := New(hostname, username)
s, err := b.GetSchedule(1)
if err != nil {
t.Fatal(err)
}
t.Logf("Time: %s", s.Time)
t.Logf("LocalTime: %s", s.LocalTime)
t.Logf("StartTime: %s", s.StartTime)
t.Logf("Status: %s", s.Status)
t.Logf("AutoDelete: %t", s.AutoDelete)
t.Logf("ID: %d", s.ID)
b.Host = badHostname
_, err = b.GetSchedule(1)
assert.NotNil(t, err)
}
func TestCreateSchedule(t *testing.T) {
b := New(hostname, username)
command := &Command{
Address: "/api/" + username + "/lights/0",
Body: &struct {
on bool
}{
false,
},
Method: "PUT",
}
schedule := &Schedule{
Name: "TestSchedule",
Description: "Huego test schedule",
Command: command,
LocalTime: "2019-09-22T13:37:00",
}
resp, err := b.CreateSchedule(schedule)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Schedule created")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.CreateSchedule(schedule)
assert.NotNil(t, err)
}
func TestUpdateSchedule(t *testing.T) {
b := New(hostname, username)
id := 1
schedule := &Schedule{
Name: "New Scehdule",
Description: "Updated parameter",
}
resp, err := b.UpdateSchedule(id, schedule)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Schedule %d updated", id)
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
}
b.Host = badHostname
_, err = b.UpdateSchedule(id, schedule)
assert.NotNil(t, err)
}
func TestDeleteSchedule(t *testing.T) {
b := New(hostname, username)
id := 1
err := b.DeleteSchedule(id)
if err != nil {
t.Fatal(err)
} else {
t.Logf("Schedule %d deleted", id)
}
}

21
vendor/github.com/amimof/huego/sensor.go generated vendored Normal file

@ -0,0 +1,21 @@
package huego
// Sensor represents a bridge sensor https://developers.meethue.com/documentation/sensors-api
type Sensor struct {
State map[string]interface{} `json:"state,omitempty"`
Config map[string]interface{} `json:"config,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
ModelID string `json:"modelid,omitempty"`
ManufacturerName string `json:"manufacturername,omitempty"`
UniqueID string `json:"uniqueid,omitempty"`
SwVersion string `json:"swversion,omitempty"`
ID int `json:",omitempty"`
}
// NewSensor defines a list of sensors discovered the last time the bridge performed a sensor discovery.
// Also stores the timestamp the last time a discovery was performed.
type NewSensor struct {
Sensors []*Sensor
LastScan string `json:"lastscan"`
}

179
vendor/github.com/amimof/huego/sensor_test.go generated vendored Normal file

@ -0,0 +1,179 @@
package huego
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestGetSensors(t *testing.T) {
b := New(hostname, username)
sensors, err := b.GetSensors()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d sensors", len(sensors))
for _, sensor := range sensors {
t.Logf("State:")
t.Logf(" Interface: %+v", sensor.State)
t.Logf("Config:")
t.Logf(" On: %+v", sensor.Config)
t.Logf("Name: %s", sensor.Name)
t.Logf("Type: %s", sensor.Type)
t.Logf("ModelID: %s", sensor.ModelID)
t.Logf("ManufacturerName: %s", sensor.ManufacturerName)
t.Logf("UniqueID: %s", sensor.UniqueID)
t.Logf("SwVersion: %s", sensor.SwVersion)
t.Logf("ID: %d", sensor.ID)
}
contains := func(name string, ss []Sensor) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Daylight", sensors))
assert.True(t, contains("Tap Switch 2", sensors))
b.Host = badHostname
_, err = b.GetSensors()
assert.NotNil(t, err)
}
func TestGetSensor(t *testing.T) {
b := New(hostname, username)
sensor, err := b.GetSensor(1)
if err != nil {
t.Fatal(err)
}
t.Logf("State:")
t.Logf(" Interface: %+v", sensor.State)
t.Logf("Config:")
t.Logf(" On: %+v", sensor.Config)
t.Logf("Name: %s", sensor.Name)
t.Logf("Type: %s", sensor.Type)
t.Logf("ModelID: %s", sensor.ModelID)
t.Logf("ManufacturerName: %s", sensor.ManufacturerName)
t.Logf("UniqueID: %s", sensor.UniqueID)
t.Logf("SwVersion: %s", sensor.SwVersion)
t.Logf("ID: %d", sensor.ID)
b.Host = badHostname
_, err = b.GetSensor(1)
assert.NotNil(t, err)
}
func TestCreateSensor(t *testing.T) {
b := New(hostname, username)
sensor := &Sensor{
Name: "New Sensor",
}
resp, err := b.CreateSensor(sensor)
if err != nil {
t.Fatal(err)
}
t.Logf("Sensor created")
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.CreateSensor(sensor)
assert.NotNil(t, err)
}
func TestFindSensors(t *testing.T) {
b := New(hostname, username)
resp, err := b.FindSensors()
if err != nil {
t.Fatal(err)
}
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.FindSensors()
assert.NotNil(t, err)
}
func TestGetNewSensors(t *testing.T) {
b := New(hostname, username)
newSensors, err := b.GetNewSensors()
if err != nil {
t.Fatal(err)
}
t.Logf("Sensors:")
for _, sensor := range newSensors.Sensors {
t.Logf("State:")
t.Logf(" Interface: %+v", sensor.State)
t.Logf("Config:")
t.Logf(" On: %+v", sensor.Config)
t.Logf("Name: %s", sensor.Name)
t.Logf("Type: %s", sensor.Type)
t.Logf("ModelID: %s", sensor.ModelID)
t.Logf("ManufacturerName: %s", sensor.ManufacturerName)
t.Logf("UniqueID: %s", sensor.UniqueID)
t.Logf("SwVersion: %s", sensor.SwVersion)
t.Logf("ID: %d", sensor.ID)
}
contains := func(name string, ss []*Sensor) bool {
for _, s := range ss {
if s.Name == name {
return true
}
}
return false
}
assert.True(t, contains("Hue Tap 1", newSensors.Sensors))
assert.True(t, contains("Button 3", newSensors.Sensors))
}
func TestUpdateSensor(t *testing.T) {
b := New(hostname, username)
id := 1
sensor := &Sensor{
Name: "New Sensor",
}
resp, err := b.UpdateSensor(id, sensor)
if err != nil {
t.Fatal(err)
}
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.UpdateSensor(id, sensor)
assert.NotNil(t, err)
}
func TestUpdateSensorConfig(t *testing.T) {
b := New(hostname, username)
id := 1
resp, err := b.UpdateSensorConfig(id, "test")
if err != nil {
t.Fatal(err)
}
for k, v := range resp.Success {
t.Logf("%v: %s", k, v)
}
b.Host = badHostname
_, err = b.UpdateSensorConfig(id, "test")
assert.NotNil(t, err)
}
func TestDeleteSensor(t *testing.T) {
b := New(hostname, username)
id := 1
err := b.DeleteSensor(id)
if err != nil {
t.Fatal(err)
}
t.Logf("Sensor %d deleted", id)
}

1
vendor/github.com/chzyer/readline/.gitignore generated vendored Normal file

@ -0,0 +1 @@
.vscode/*

8
vendor/github.com/chzyer/readline/.travis.yml generated vendored Normal file

@ -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

58
vendor/github.com/chzyer/readline/CHANGELOG.md generated vendored Normal file

@ -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 `<Delete>`/`<Home>`/`<End>`
* 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

22
vendor/github.com/chzyer/readline/LICENSE generated vendored Normal file

@ -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.

114
vendor/github.com/chzyer/readline/README.md generated vendored Normal file

@ -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)
<p align="center">
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo.png" />
<a href="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m" target="_blank"><img src="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m.png" width="654"/></a>
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" />
</p>
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!<br />
[[Become a backer](https://opencollective.com/readline#backer)]
<a href="https://opencollective.com/readline/backer/0/website" target="_blank"><img src="https://opencollective.com/readline/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/1/website" target="_blank"><img src="https://opencollective.com/readline/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/2/website" target="_blank"><img src="https://opencollective.com/readline/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/3/website" target="_blank"><img src="https://opencollective.com/readline/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/4/website" target="_blank"><img src="https://opencollective.com/readline/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/5/website" target="_blank"><img src="https://opencollective.com/readline/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/6/website" target="_blank"><img src="https://opencollective.com/readline/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/7/website" target="_blank"><img src="https://opencollective.com/readline/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/8/website" target="_blank"><img src="https://opencollective.com/readline/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/9/website" target="_blank"><img src="https://opencollective.com/readline/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/10/website" target="_blank"><img src="https://opencollective.com/readline/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/11/website" target="_blank"><img src="https://opencollective.com/readline/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/12/website" target="_blank"><img src="https://opencollective.com/readline/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/13/website" target="_blank"><img src="https://opencollective.com/readline/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/14/website" target="_blank"><img src="https://opencollective.com/readline/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/15/website" target="_blank"><img src="https://opencollective.com/readline/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/16/website" target="_blank"><img src="https://opencollective.com/readline/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/17/website" target="_blank"><img src="https://opencollective.com/readline/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/18/website" target="_blank"><img src="https://opencollective.com/readline/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/19/website" target="_blank"><img src="https://opencollective.com/readline/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/20/website" target="_blank"><img src="https://opencollective.com/readline/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/21/website" target="_blank"><img src="https://opencollective.com/readline/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/22/website" target="_blank"><img src="https://opencollective.com/readline/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/23/website" target="_blank"><img src="https://opencollective.com/readline/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/24/website" target="_blank"><img src="https://opencollective.com/readline/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/25/website" target="_blank"><img src="https://opencollective.com/readline/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/26/website" target="_blank"><img src="https://opencollective.com/readline/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/27/website" target="_blank"><img src="https://opencollective.com/readline/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/28/website" target="_blank"><img src="https://opencollective.com/readline/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/readline/backer/29/website" target="_blank"><img src="https://opencollective.com/readline/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)]
<a href="https://opencollective.com/readline/sponsor/0/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/1/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/2/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/3/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/4/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/5/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/6/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/7/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/8/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/9/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/10/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/11/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/12/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/13/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/14/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/15/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/16/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/17/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/18/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/19/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/20/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/21/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/22/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/23/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/24/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/25/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/26/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/27/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/28/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/readline/sponsor/29/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/29/avatar.svg"></a>

249
vendor/github.com/chzyer/readline/ansi_windows.go generated vendored Normal file

@ -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)),
)
}

285
vendor/github.com/chzyer/readline/complete.go generated vendored Normal file

@ -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()
}

165
vendor/github.com/chzyer/readline/complete_helper.go generated vendored Normal file

@ -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
}

82
vendor/github.com/chzyer/readline/complete_segment.go generated vendored Normal file

@ -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
}

330
vendor/github.com/chzyer/readline/history.go generated vendored Normal file

@ -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
}

531
vendor/github.com/chzyer/readline/operation.go generated vendored Normal file

@ -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
}

33
vendor/github.com/chzyer/readline/password.go generated vendored Normal file

@ -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,
}
}

125
vendor/github.com/chzyer/readline/rawreader_windows.go generated vendored Normal file

@ -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
}

326
vendor/github.com/chzyer/readline/readline.go generated vendored Normal file

@ -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()
}

475
vendor/github.com/chzyer/readline/remote.go generated vendored Normal file

@ -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()
}

629
vendor/github.com/chzyer/readline/runebuf.go generated vendored Normal file

@ -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)
}

223
vendor/github.com/chzyer/readline/runes.go generated vendored Normal file

@ -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:]
}

164
vendor/github.com/chzyer/readline/search.go generated vendored Normal file

@ -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())
}

197
vendor/github.com/chzyer/readline/std.go generated vendored Normal file

@ -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
}

9
vendor/github.com/chzyer/readline/std_windows.go generated vendored Normal file

@ -0,0 +1,9 @@
// +build windows
package readline
func init() {
Stdin = NewRawReader()
Stdout = NewANSIWriter(Stdout)
Stderr = NewANSIWriter(Stderr)
}

123
vendor/github.com/chzyer/readline/term.go generated vendored Normal file

@ -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
}

29
vendor/github.com/chzyer/readline/term_bsd.go generated vendored Normal file

@ -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
}

33
vendor/github.com/chzyer/readline/term_linux.go generated vendored Normal file

@ -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
}

32
vendor/github.com/chzyer/readline/term_solaris.go generated vendored Normal file

@ -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))
}

24
vendor/github.com/chzyer/readline/term_unix.go generated vendored Normal file

@ -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
}

171
vendor/github.com/chzyer/readline/term_windows.go generated vendored Normal file

@ -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
}

238
vendor/github.com/chzyer/readline/terminal.go generated vendored Normal file

@ -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
}

277
vendor/github.com/chzyer/readline/utils.go generated vendored Normal file

@ -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()
}

83
vendor/github.com/chzyer/readline/utils_unix.go generated vendored Normal file

@ -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()
}
}()
})
}

41
vendor/github.com/chzyer/readline/utils_windows.go generated vendored Normal file

@ -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()) {
}

Some files were not shown because too many files have changed in this diff Show More