Last active
January 6, 2021 14:34
-
-
Save raylee/ef44aac10dc1fc0068f5073210f62e17 to your computer and use it in GitHub Desktop.
wrapper for https://github.com/google/goexpect which gives it a simpler interface, plus a multiWriteCloser implementation for logging.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Interact wraps google/goexpect with a simpler interface | |
package main | |
import ( | |
"io" | |
"log" | |
"regexp" | |
"time" | |
expect "github.com/google/goexpect" | |
) | |
type Interact struct { | |
ex *expect.GExpect | |
done chan struct{} | |
timeout time.Duration | |
} | |
// ctrl converts an A-Z character to its ASCII control equivalent if it exists, or | |
// the original rune if it doesn't. | |
func ctrl(r rune) rune { | |
if r >= 'A' && r <= 'Z' { | |
return rune(r - 'A' + 1) | |
} | |
if r >= 'a' && r <= 'z' { | |
return rune(r - 'a' + 1) | |
} | |
return r | |
} | |
// Control sends a control character to the remote. | |
func (i *Interact) Control(r rune) { | |
i.ex.Send(string(ctrl(r))) | |
} | |
// Send a string to the remote. | |
func (i *Interact) Send(s string) { | |
i.ex.Send(s) | |
} | |
// SendLine appends a line terminator when sending the string s. | |
func (i *Interact) SendLine(s string) { | |
i.ex.Send(s + "\n") | |
} | |
// Expect will wait for the string s. | |
func (i *Interact) Expect(s string) { | |
re := regexp.MustCompile(s) | |
i.ex.Expect(re, i.timeout) | |
} | |
// ExpectRE will wait for the regular expression re. | |
func (i *Interact) ExpectRE(re *regexp.Regexp) { | |
i.ex.Expect(re, i.timeout) | |
} | |
// Close shuts down the expect machinery. | |
func (i *Interact) Close() { | |
close(i.done) | |
i.ex.Close() | |
} | |
// NewInteract starts goexpect on s, with a logger for the IO and a default timeout for the operations. | |
func NewInteract(s io.ReadWriteCloser, logger io.WriteCloser, timeout time.Duration) Interact { | |
done := make(chan struct{}) | |
streamControl := &expect.GenOptions{ | |
In: s, | |
Out: s, | |
Wait: func() error { <-done; return nil }, | |
Close: func() error { return s.Close() }, | |
Check: func() bool { return true }, | |
} | |
ex, _, err := expect.SpawnGeneric( | |
streamControl, | |
-1, // timeout | |
expect.Tee(logger), | |
expect.CheckDuration(100*time.Millisecond), | |
expect.SendTimeout(timeout), | |
) | |
if err != nil { | |
log.Fatal(err) | |
} | |
// wrap the vendor's package with a simpler interface | |
return Interact{ex, done, timeout} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import "io" | |
// multiWriteCloser adds the io.Closer interface to the standard library's | |
// io.MultiWriter. | |
type multiWriteCloser struct { | |
writers []io.WriteCloser | |
io.Writer | |
} | |
// Close closes all underlying streams. Satisfies the io.Closer and | |
// io.WriteCloser interfaces. | |
func (t *multiWriteCloser) Close() error { | |
var err error | |
for _, w := range t.writers { | |
e := w.Close() | |
if err != nil { | |
err = e | |
} | |
} | |
return err | |
} | |
// MultiWriteCloser creates a writer that duplicates its writes to all the | |
// provided writers, similar to the Unix tee(1) command. Close closes | |
// all streams. | |
// | |
// Each write is written to each listed writer, one at a time. | |
// If a listed writer returns an error, that overall write operation | |
// stops and returns the error; it does not continue down the list. | |
func MultiWriteCloser(writers ...io.WriteCloser) io.WriteCloser { | |
allClosers := make([]io.WriteCloser, 0, len(writers)) | |
allWriters := make([]io.Writer, 0, len(writers)) | |
for _, w := range writers { | |
if mw, ok := w.(*multiWriteCloser); ok { | |
allClosers = append(allClosers, mw.writers...) | |
} else { | |
allClosers = append(allClosers, w) | |
} | |
allWriters = append(allWriters, w.(io.Writer)) | |
} | |
return &multiWriteCloser{allClosers, io.MultiWriter(allWriters...)} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage is something like the below, which controls a serial port: