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