github.com/haraldrudell/parl@v0.4.176/mains/keystrokes.go (about) 1 /* 2 © 2018–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package mains 7 8 import ( 9 "sync/atomic" 10 11 "github.com/haraldrudell/parl" 12 "github.com/haraldrudell/parl/perrors" 13 ) 14 15 const ( 16 // do not echo to standard error on [os.Stdin] closing 17 // optional argument to [Keystrokes.Launch] 18 SilentClose = true 19 ) 20 21 // didLaunch ensures multiple keystrokesThread are not running 22 var didLaunch atomic.Bool 23 24 // Keystrokes reads line-wise from standard input 25 // - [os.File.Read] from [os.Stdin] cannot be aborted because 26 // Stdin cannot be closed 27 // - therefore, on process exit or [Keystrokes.CloseNow], keystrokesThread thread is left blocked in Read 28 // - — 29 // - -verbose='mains...Keystrokes|mains.keystrokesThread' “github.com/haraldrudell/parl/mains.(*Keystrokes)” 30 // - this object is released on process exit. 31 // Remaining items due to stdin cannot be closed are: 32 // - the stdin unbound channel 33 // - optional addError 34 // - those items along with [KeyStrokesThread] and [StdinReader] are released 35 // on process exit or next keypress 36 type Keystrokes struct { 37 // unbound locked combined channel and slice type 38 stdin parl.NBChan[string] 39 } 40 41 // NewKeystrokes returns an object reading lines from standard input 42 // - [Keystrokes.Launch] launches a thread reading from [os.Stdin] 43 // - [Keystrokes.Ch] provides a channel sending strings on each return key-press 44 // - [Keystrokes.CloseNow] closes the channel discarding buffered characters 45 // - Ch also closes on Stdin closing or thread runtime error 46 // 47 // Usage: 48 // 49 // var err error 50 // … 51 // var keystrokes = NewKeystrokes() 52 // defer keystrokes.Launch().CloseNow(&err) 53 // for line := range keystrokes.Ch() { 54 func NewKeystrokes() (keystrokes *Keystrokes) { return &Keystrokes{} } 55 56 // Launch starts reading stdin for keystrokes 57 // - can only be invoked once per process or panic 58 // - supports functional chaining 59 // - silent [SilentClose] does not echo anything on [os.Stdin] closing 60 // - addError if present receives errors from [os.Stdin.Read] 61 func (k *Keystrokes) Launch(addError parl.AddError, silent ...bool) (keystrokes *Keystrokes) { 62 keystrokes = k 63 64 // ensure only launched once 65 if !didLaunch.CompareAndSwap(false, true) { 66 var err = perrors.ErrorfPF("invoked multiple times") 67 parl.Log(err.Error()) 68 panic(err) // terminates the process 69 } 70 71 var isSilent bool 72 if len(silent) > 0 { 73 isSilent = silent[0] 74 } 75 76 go keystrokesThread(isSilent, addError, &k.stdin) 77 78 return 79 } 80 81 // Ch returns a possibly closing receive-only channel sending lines from the keyboard on each return press 82 // - Ch sends strings with return character removed 83 // - the channel closes upon: 84 // - — [Keystrokes.CloseNow] or 85 // - — [os.Stdin] closing or 86 // - — thread runtime error 87 func (k *Keystrokes) Ch() (ch <-chan string) { return k.stdin.Ch() } 88 89 // CloseNow closes the string-sending channel discarding any pending characters 90 func (k *Keystrokes) CloseNow(errp *error) { k.stdin.CloseNow(errp) }