github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/vagrant/ui.go (about)

     1  package vagrant
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"sync"
     8  
     9  	"github.com/armon/circbuf"
    10  	"github.com/hashicorp/otto/ui"
    11  )
    12  
    13  // OutputCallback is the type that is called when there is a matching
    14  // machine-readable output line for the UI.
    15  type OutputCallback func(*Output)
    16  
    17  // Output is a single line of machine-readable output from Vagrant.
    18  type Output struct {
    19  	Timestamp string
    20  	Target    string
    21  	Type      string
    22  	Data      []string
    23  }
    24  
    25  // vagrantUi is an implementation of ui.Ui that we pass to helper/exec
    26  // that parses the machine-readable output of Vagrant and calls callbacks
    27  // for the various entries.
    28  type vagrantUi struct {
    29  	Callbacks map[string]OutputCallback
    30  
    31  	buf  *circbuf.Buffer
    32  	once sync.Once
    33  }
    34  
    35  // Finish should be called when you're done to force the final newline
    36  // to parse the final event.
    37  func (u *vagrantUi) Finish() {
    38  	// We only need to do anything if we have stuff in our buffer
    39  	if u.buf != nil && u.buf.TotalWritten() > 0 {
    40  		u.Raw("\n")
    41  	}
    42  }
    43  
    44  // Ignore these because we don't use them from helper/exec
    45  func (u *vagrantUi) Header(string)                       {}
    46  func (u *vagrantUi) Message(string)                      {}
    47  func (u *vagrantUi) Input(*ui.InputOpts) (string, error) { return "", nil }
    48  
    49  func (u *vagrantUi) Raw(msg string) {
    50  	if msg == "" {
    51  		// Not sure how this would happen, but there is nothing to do here.
    52  		return
    53  	}
    54  
    55  	u.once.Do(u.init)
    56  
    57  	// We loop while we have a newline in the message
    58  	for {
    59  		// Get the index for the newline if there is one
    60  		idx := strings.IndexRune(msg, '\n')
    61  		if idx == -1 {
    62  			// The newline isn't there, write it to the circular buffer
    63  			// and wait longer.
    64  			u.buf.Write([]byte(msg))
    65  			break
    66  		}
    67  
    68  		// We got a newline! Grab the contents from the circular buffer and
    69  		// copy it so we can clear the buffer.
    70  		bufRaw := u.buf.Bytes()
    71  		buf := string(bufRaw) + msg[:idx]
    72  		bufRaw = nil
    73  		u.buf.Reset()
    74  		log.Printf("[DEBUG] Vagrant output line: %s", buf)
    75  
    76  		// If we have more data, clear msg to that point. If we have
    77  		// no more data, then just set message to empty and we'll break
    78  		// on the next loop iteration
    79  		if idx < len(msg) {
    80  			msg = msg[idx+1:]
    81  		} else {
    82  			msg = ""
    83  		}
    84  
    85  		// Combine the data from the buffer up to the newline so we
    86  		// have the full line, and split that by the commas.
    87  		parts := strings.Split(buf, ",")
    88  		if len(parts) < 3 {
    89  			// Uh, invalid event?
    90  			log.Printf("[ERROR] Invalid Vagrant event line: %s", buf)
    91  
    92  			// Make the parts for an invalid event
    93  			parts = []string{
    94  				"",
    95  				"",
    96  				"invalid",
    97  				buf,
    98  			}
    99  		}
   100  
   101  		// Look for the callback
   102  		cb, ok := u.Callbacks[parts[2]]
   103  		if !ok {
   104  			// No callback registered for this type, drop it
   105  			continue
   106  		}
   107  
   108  		// We have a callback, construct the output!
   109  		var data []string
   110  		if len(parts) > 3 {
   111  			data = make([]string, len(parts)-3)
   112  			for i, raw := range parts[3:] {
   113  				data[i] = strings.Replace(
   114  					strings.Replace(
   115  						strings.Replace(raw, "%!(VAGRANT_COMMA)", ",", -1),
   116  						"\\n", "\n", -1),
   117  					"\\r", "\r", -1)
   118  			}
   119  		}
   120  
   121  		// Callback
   122  		cb(&Output{
   123  			Timestamp: parts[0],
   124  			Target:    parts[1],
   125  			Type:      parts[2],
   126  			Data:      data,
   127  		})
   128  	}
   129  }
   130  
   131  func (u *vagrantUi) init() {
   132  	// Allocating the circular buffer. The circular buffer should only
   133  	// keep track up to the point that there is a \n found so it doesn't
   134  	// need to be huge, but it also limits the max length of an event.
   135  	var err error
   136  	u.buf, err = circbuf.NewBuffer(4096)
   137  	if err != nil {
   138  		panic(err)
   139  	}
   140  }
   141  
   142  // For testing
   143  func (o *Output) GoString() string {
   144  	return fmt.Sprintf("*%#v", *o)
   145  }