1package readline 2 3import ( 4 "io" 5 "os" 6 "sync" 7 "sync/atomic" 8) 9 10var ( 11 Stdin io.ReadCloser = os.Stdin 12 Stdout io.WriteCloser = os.Stdout 13 Stderr io.WriteCloser = os.Stderr 14) 15 16var ( 17 std *Instance 18 stdOnce sync.Once 19) 20 21// global instance will not submit history automatic 22func getInstance() *Instance { 23 stdOnce.Do(func() { 24 std, _ = NewEx(&Config{ 25 DisableAutoSaveHistory: true, 26 }) 27 }) 28 return std 29} 30 31// let readline load history from filepath 32// and try to persist history into disk 33// set fp to "" to prevent readline persisting history to disk 34// so the `AddHistory` will return nil error forever. 35func SetHistoryPath(fp string) { 36 ins := getInstance() 37 cfg := ins.Config.Clone() 38 cfg.HistoryFile = fp 39 ins.SetConfig(cfg) 40} 41 42// set auto completer to global instance 43func SetAutoComplete(completer AutoCompleter) { 44 ins := getInstance() 45 cfg := ins.Config.Clone() 46 cfg.AutoComplete = completer 47 ins.SetConfig(cfg) 48} 49 50// add history to global instance manually 51// raise error only if `SetHistoryPath` is set with a non-empty path 52func AddHistory(content string) error { 53 ins := getInstance() 54 return ins.SaveHistory(content) 55} 56 57func Password(prompt string) ([]byte, error) { 58 ins := getInstance() 59 return ins.ReadPassword(prompt) 60} 61 62// readline with global configs 63func Line(prompt string) (string, error) { 64 ins := getInstance() 65 ins.SetPrompt(prompt) 66 return ins.Readline() 67} 68 69type CancelableStdin struct { 70 r io.Reader 71 mutex sync.Mutex 72 stop chan struct{} 73 closed int32 74 notify chan struct{} 75 data []byte 76 read int 77 err error 78} 79 80func NewCancelableStdin(r io.Reader) *CancelableStdin { 81 c := &CancelableStdin{ 82 r: r, 83 notify: make(chan struct{}), 84 stop: make(chan struct{}), 85 } 86 go c.ioloop() 87 return c 88} 89 90func (c *CancelableStdin) ioloop() { 91loop: 92 for { 93 select { 94 case <-c.notify: 95 c.read, c.err = c.r.Read(c.data) 96 select { 97 case c.notify <- struct{}{}: 98 case <-c.stop: 99 break loop 100 } 101 case <-c.stop: 102 break loop 103 } 104 } 105} 106 107func (c *CancelableStdin) Read(b []byte) (n int, err error) { 108 c.mutex.Lock() 109 defer c.mutex.Unlock() 110 if atomic.LoadInt32(&c.closed) == 1 { 111 return 0, io.EOF 112 } 113 114 c.data = b 115 select { 116 case c.notify <- struct{}{}: 117 case <-c.stop: 118 return 0, io.EOF 119 } 120 select { 121 case <-c.notify: 122 return c.read, c.err 123 case <-c.stop: 124 return 0, io.EOF 125 } 126} 127 128func (c *CancelableStdin) Close() error { 129 if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { 130 close(c.stop) 131 } 132 return nil 133} 134 135// FillableStdin is a stdin reader which can prepend some data before 136// reading into the real stdin 137type FillableStdin struct { 138 sync.Mutex 139 stdin io.Reader 140 stdinBuffer io.ReadCloser 141 buf []byte 142 bufErr error 143} 144 145// NewFillableStdin gives you FillableStdin 146func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) { 147 r, w := io.Pipe() 148 s := &FillableStdin{ 149 stdinBuffer: r, 150 stdin: stdin, 151 } 152 s.ioloop() 153 return s, w 154} 155 156func (s *FillableStdin) ioloop() { 157 go func() { 158 for { 159 bufR := make([]byte, 100) 160 var n int 161 n, s.bufErr = s.stdinBuffer.Read(bufR) 162 if s.bufErr != nil { 163 if s.bufErr == io.ErrClosedPipe { 164 break 165 } 166 } 167 s.Lock() 168 s.buf = append(s.buf, bufR[:n]...) 169 s.Unlock() 170 } 171 }() 172} 173 174// Read will read from the local buffer and if no data, read from stdin 175func (s *FillableStdin) Read(p []byte) (n int, err error) { 176 s.Lock() 177 i := len(s.buf) 178 if len(p) < i { 179 i = len(p) 180 } 181 if i > 0 { 182 n := copy(p, s.buf) 183 s.buf = s.buf[:0] 184 cerr := s.bufErr 185 s.bufErr = nil 186 s.Unlock() 187 return n, cerr 188 } 189 s.Unlock() 190 n, err = s.stdin.Read(p) 191 return n, err 192} 193 194func (s *FillableStdin) Close() error { 195 s.stdinBuffer.Close() 196 return nil 197} 198