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