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 }