| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- package gexpect
- import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "os"
- "os/exec"
- "regexp"
- "time"
- "unicode/utf8"
- shell "github.com/kballard/go-shellquote"
- "github.com/kr/pty"
- )
- type ExpectSubprocess struct {
- Cmd *exec.Cmd
- buf *buffer
- outputBuffer []byte
- }
- type buffer struct {
- f *os.File
- b bytes.Buffer
- collect bool
- collection bytes.Buffer
- }
- func (buf *buffer) StartCollecting() {
- buf.collect = true
- }
- func (buf *buffer) StopCollecting() (result string) {
- result = string(buf.collection.Bytes())
- buf.collect = false
- buf.collection.Reset()
- return result
- }
- func (buf *buffer) Read(chunk []byte) (int, error) {
- nread := 0
- if buf.b.Len() > 0 {
- n, err := buf.b.Read(chunk)
- if err != nil {
- return n, err
- }
- if n == len(chunk) {
- return n, nil
- }
- nread = n
- }
- fn, err := buf.f.Read(chunk[nread:])
- return fn + nread, err
- }
- func (buf *buffer) ReadRune() (r rune, size int, err error) {
- l := buf.b.Len()
- chunk := make([]byte, utf8.UTFMax)
- if l > 0 {
- n, err := buf.b.Read(chunk)
- if err != nil {
- return 0, 0, err
- }
- if utf8.FullRune(chunk) {
- r, rL := utf8.DecodeRune(chunk)
- if n > rL {
- buf.PutBack(chunk[rL:n])
- }
- if buf.collect {
- buf.collection.WriteRune(r)
- }
- return r, rL, nil
- }
- }
- // else add bytes from the file, then try that
- for l < utf8.UTFMax {
- fn, err := buf.f.Read(chunk[l : l+1])
- if err != nil {
- return 0, 0, err
- }
- l = l + fn
- if utf8.FullRune(chunk) {
- r, rL := utf8.DecodeRune(chunk)
- if buf.collect {
- buf.collection.WriteRune(r)
- }
- return r, rL, nil
- }
- }
- return 0, 0, errors.New("File is not a valid UTF=8 encoding")
- }
- func (buf *buffer) PutBack(chunk []byte) {
- if len(chunk) == 0 {
- return
- }
- if buf.b.Len() == 0 {
- buf.b.Write(chunk)
- return
- }
- d := make([]byte, 0, len(chunk)+buf.b.Len())
- d = append(d, chunk...)
- d = append(d, buf.b.Bytes()...)
- buf.b.Reset()
- buf.b.Write(d)
- }
- func SpawnAtDirectory(command string, directory string) (*ExpectSubprocess, error) {
- expect, err := _spawn(command)
- if err != nil {
- return nil, err
- }
- expect.Cmd.Dir = directory
- return _start(expect)
- }
- func Command(command string) (*ExpectSubprocess, error) {
- expect, err := _spawn(command)
- if err != nil {
- return nil, err
- }
- return expect, nil
- }
- func (expect *ExpectSubprocess) Start() error {
- _, err := _start(expect)
- return err
- }
- func Spawn(command string) (*ExpectSubprocess, error) {
- expect, err := _spawn(command)
- if err != nil {
- return nil, err
- }
- return _start(expect)
- }
- func (expect *ExpectSubprocess) Close() error {
- return expect.Cmd.Process.Kill()
- }
- func (expect *ExpectSubprocess) AsyncInteractChannels() (send chan string, receive chan string) {
- receive = make(chan string)
- send = make(chan string)
- go func() {
- for {
- str, err := expect.ReadLine()
- if err != nil {
- close(receive)
- return
- }
- receive <- str
- }
- }()
- go func() {
- for {
- select {
- case sendCommand, exists := <-send:
- {
- if !exists {
- return
- }
- err := expect.Send(sendCommand)
- if err != nil {
- receive <- "gexpect Error: " + err.Error()
- return
- }
- }
- }
- }
- }()
- return
- }
- func (expect *ExpectSubprocess) ExpectRegex(regex string) (bool, error) {
- return regexp.MatchReader(regex, expect.buf)
- }
- func (expect *ExpectSubprocess) expectRegexFind(regex string, output bool) ([]string, string, error) {
- re, err := regexp.Compile(regex)
- if err != nil {
- return nil, "", err
- }
- expect.buf.StartCollecting()
- pairs := re.FindReaderSubmatchIndex(expect.buf)
- stringIndexedInto := expect.buf.StopCollecting()
- l := len(pairs)
- numPairs := l / 2
- result := make([]string, numPairs)
- for i := 0; i < numPairs; i += 1 {
- result[i] = stringIndexedInto[pairs[i*2]:pairs[i*2+1]]
- }
- // convert indexes to strings
- if len(result) == 0 {
- err = fmt.Errorf("ExpectRegex didn't find regex '%v'.", regex)
- }
- return result, stringIndexedInto, err
- }
- func (expect *ExpectSubprocess) expectTimeoutRegexFind(regex string, timeout time.Duration) (result []string, out string, err error) {
- t := make(chan bool)
- go func() {
- result, out, err = expect.ExpectRegexFindWithOutput(regex)
- t <- false
- }()
- go func() {
- time.Sleep(timeout)
- err = fmt.Errorf("ExpectRegex timed out after %v finding '%v'.\nOutput:\n%s", timeout, regex, expect.Collect())
- t <- true
- }()
- <-t
- return result, out, err
- }
- func (expect *ExpectSubprocess) ExpectRegexFind(regex string) ([]string, error) {
- result, _, err := expect.expectRegexFind(regex, false)
- return result, err
- }
- func (expect *ExpectSubprocess) ExpectTimeoutRegexFind(regex string, timeout time.Duration) ([]string, error) {
- result, _, err := expect.expectTimeoutRegexFind(regex, timeout)
- return result, err
- }
- func (expect *ExpectSubprocess) ExpectRegexFindWithOutput(regex string) ([]string, string, error) {
- return expect.expectRegexFind(regex, true)
- }
- func (expect *ExpectSubprocess) ExpectTimeoutRegexFindWithOutput(regex string, timeout time.Duration) ([]string, string, error) {
- return expect.expectTimeoutRegexFind(regex, timeout)
- }
- func buildKMPTable(searchString string) []int {
- pos := 2
- cnd := 0
- length := len(searchString)
- var table []int
- if length < 2 {
- length = 2
- }
- table = make([]int, length)
- table[0] = -1
- table[1] = 0
- for pos < len(searchString) {
- if searchString[pos-1] == searchString[cnd] {
- cnd += 1
- table[pos] = cnd
- pos += 1
- } else if cnd > 0 {
- cnd = table[cnd]
- } else {
- table[pos] = 0
- pos += 1
- }
- }
- return table
- }
- func (expect *ExpectSubprocess) ExpectTimeout(searchString string, timeout time.Duration) (e error) {
- result := make(chan error)
- go func() {
- result <- expect.Expect(searchString)
- }()
- select {
- case e = <-result:
- case <-time.After(timeout):
- e = fmt.Errorf("Expect timed out after %v waiting for '%v'.\nOutput:\n%s", timeout, searchString, expect.Collect())
- }
- return e
- }
- func (expect *ExpectSubprocess) Expect(searchString string) (e error) {
- chunk := make([]byte, len(searchString)*2)
- target := len(searchString)
- if expect.outputBuffer != nil {
- expect.outputBuffer = expect.outputBuffer[0:]
- }
- m := 0
- i := 0
- // Build KMP Table
- table := buildKMPTable(searchString)
- for {
- n, err := expect.buf.Read(chunk)
- if err != nil {
- return err
- }
- if expect.outputBuffer != nil {
- expect.outputBuffer = append(expect.outputBuffer, chunk[:n]...)
- }
- offset := m + i
- for m+i-offset < n {
- if searchString[i] == chunk[m+i-offset] {
- i += 1
- if i == target {
- unreadIndex := m + i - offset
- if len(chunk) > unreadIndex {
- expect.buf.PutBack(chunk[unreadIndex:])
- }
- return nil
- }
- } else {
- m += i - table[i]
- if table[i] > -1 {
- i = table[i]
- } else {
- i = 0
- }
- }
- }
- }
- }
- func (expect *ExpectSubprocess) Send(command string) error {
- _, err := io.WriteString(expect.buf.f, command)
- return err
- }
- func (expect *ExpectSubprocess) Capture() {
- if expect.outputBuffer == nil {
- expect.outputBuffer = make([]byte, 0)
- }
- }
- func (expect *ExpectSubprocess) Collect() []byte {
- collectOutput := make([]byte, len(expect.outputBuffer))
- copy(collectOutput, expect.outputBuffer)
- expect.outputBuffer = nil
- return collectOutput
- }
- func (expect *ExpectSubprocess) SendLine(command string) error {
- _, err := io.WriteString(expect.buf.f, command+"\r\n")
- return err
- }
- func (expect *ExpectSubprocess) Interact() {
- defer expect.Cmd.Wait()
- io.Copy(os.Stdout, &expect.buf.b)
- go io.Copy(os.Stdout, expect.buf.f)
- go io.Copy(expect.buf.f, os.Stdin)
- }
- func (expect *ExpectSubprocess) ReadUntil(delim byte) ([]byte, error) {
- join := make([]byte, 1, 512)
- chunk := make([]byte, 255)
- for {
- n, err := expect.buf.Read(chunk)
- if err != nil {
- return join, err
- }
- for i := 0; i < n; i++ {
- if chunk[i] == delim {
- if len(chunk) > i+1 {
- expect.buf.PutBack(chunk[i+1:])
- }
- return join, nil
- } else {
- join = append(join, chunk[i])
- }
- }
- }
- }
- func (expect *ExpectSubprocess) Wait() error {
- return expect.Cmd.Wait()
- }
- func (expect *ExpectSubprocess) ReadLine() (string, error) {
- str, err := expect.ReadUntil('\n')
- if err != nil {
- return "", err
- }
- return string(str), nil
- }
- func _start(expect *ExpectSubprocess) (*ExpectSubprocess, error) {
- f, err := pty.Start(expect.Cmd)
- if err != nil {
- return nil, err
- }
- expect.buf.f = f
- return expect, nil
- }
- func _spawn(command string) (*ExpectSubprocess, error) {
- wrapper := new(ExpectSubprocess)
- wrapper.outputBuffer = nil
- splitArgs, err := shell.Split(command)
- if err != nil {
- return nil, err
- }
- numArguments := len(splitArgs) - 1
- if numArguments < 0 {
- return nil, errors.New("gexpect: No command given to spawn")
- }
- path, err := exec.LookPath(splitArgs[0])
- if err != nil {
- return nil, err
- }
- if numArguments >= 1 {
- wrapper.Cmd = exec.Command(path, splitArgs[1:]...)
- } else {
- wrapper.Cmd = exec.Command(path)
- }
- wrapper.buf = new(buffer)
- return wrapper, nil
- }
|