github.com/bhameyie/otto@v0.2.1-0.20160406174117-16052efa52ec/helper/packer/ui.go (about)

     1  package packer
     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 Packer.
    18  type Output struct {
    19  	Timestamp string
    20  	Target    string
    21  	Type      string
    22  	Data      []string
    23  }
    24  
    25  // packerUi is an implementation of ui.Ui that we pass to helper/exec
    26  // that parses the machine-readable output of Packer and calls callbacks
    27  // for the various entries.
    28  type packerUi 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 *packerUi) 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 *packerUi) Header(string)                       {}
    46  func (u *packerUi) Message(string)                      {}
    47  func (u *packerUi) Input(*ui.InputOpts) (string, error) { return "", nil }
    48  
    49  func (u *packerUi) 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  
    75  		// If we have more data, clear msg to that point. If we have
    76  		// no more data, then just set message to empty and we'll break
    77  		// on the next loop iteration
    78  		if idx < len(msg) {
    79  			msg = msg[idx+1:]
    80  		} else {
    81  			msg = ""
    82  		}
    83  
    84  		// Combine the data from the buffer up to the newline so we
    85  		// have the full line, and split that by the commas.
    86  		parts := strings.Split(buf, ",")
    87  		if len(parts) < 3 {
    88  			// Uh, invalid event?
    89  			log.Printf("[ERROR] Invalid Packer event line: %s", buf)
    90  			return
    91  		}
    92  
    93  		// Look for the callback
    94  		cb, ok := u.Callbacks[parts[2]]
    95  		if !ok {
    96  			// No callback registered for this type, drop it
    97  			return
    98  		}
    99  
   100  		// We have a callback, construct the output!
   101  		var data []string
   102  		if len(parts) > 3 {
   103  			data = make([]string, len(parts)-3)
   104  			for i, raw := range parts[3:] {
   105  				data[i] = strings.Replace(
   106  					strings.Replace(
   107  						strings.Replace(raw, "%!(PACKER_COMMA)", ",", -1),
   108  						"\\n", "\n", -1),
   109  					"\\r", "\r", -1)
   110  			}
   111  		}
   112  
   113  		// Callback
   114  		cb(&Output{
   115  			Timestamp: parts[0],
   116  			Target:    parts[1],
   117  			Type:      parts[2],
   118  			Data:      data,
   119  		})
   120  	}
   121  }
   122  
   123  func (u *packerUi) init() {
   124  	// Allocating the circular buffer. The circular buffer should only
   125  	// keep track up to the point that there is a \n found so it doesn't
   126  	// need to be huge, but it also limits the max length of an event.
   127  	var err error
   128  	u.buf, err = circbuf.NewBuffer(4096)
   129  	if err != nil {
   130  		panic(err)
   131  	}
   132  }
   133  
   134  // For testing
   135  func (o *Output) GoString() string {
   136  	return fmt.Sprintf("*%#v", *o)
   137  }