github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/command/hcl_printer.go (about)

     1  package command
     2  
     3  // Marshal an object as an hcl value.
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"regexp"
     8  
     9  	"github.com/hashicorp/hcl/hcl/printer"
    10  )
    11  
    12  // This will only work operate on []interface{}, map[string]interface{}, and
    13  // primitive types.
    14  func encodeHCL(i interface{}) ([]byte, error) {
    15  	state := &encodeState{}
    16  	err := state.encode(i)
    17  	if err != nil {
    18  		return nil, err
    19  	}
    20  
    21  	hcl := state.Bytes()
    22  	if len(hcl) == 0 {
    23  		return hcl, nil
    24  	}
    25  
    26  	// the HCL parser requires an assignment. Strip it off again later
    27  	fakeAssignment := append([]byte("X = "), hcl...)
    28  
    29  	// use the real hcl parser to verify our output, and format it canonically
    30  	hcl, err = printer.Format(fakeAssignment)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	// now strip that first assignment off
    36  	eq := regexp.MustCompile(`=\s+`).FindIndex(hcl)
    37  
    38  	// strip of an extra \n if it's there
    39  	end := len(hcl)
    40  	if hcl[end-1] == '\n' {
    41  		end -= 1
    42  	}
    43  
    44  	return hcl[eq[1]:end], nil
    45  }
    46  
    47  type encodeState struct {
    48  	bytes.Buffer
    49  }
    50  
    51  func (e *encodeState) encode(i interface{}) error {
    52  	switch v := i.(type) {
    53  	case []interface{}:
    54  		return e.encodeList(v)
    55  
    56  	case map[string]interface{}:
    57  		return e.encodeMap(v)
    58  
    59  	case int, int8, int32, int64, uint8, uint32, uint64:
    60  		return e.encodeInt(i)
    61  
    62  	case float32, float64:
    63  		return e.encodeFloat(i)
    64  
    65  	case string:
    66  		return e.encodeString(v)
    67  
    68  	case nil:
    69  		return nil
    70  
    71  	default:
    72  		return fmt.Errorf("invalid type %T", i)
    73  	}
    74  
    75  }
    76  
    77  func (e *encodeState) encodeList(l []interface{}) error {
    78  	e.WriteString("[")
    79  	for i, v := range l {
    80  		err := e.encode(v)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		if i < len(l)-1 {
    85  			e.WriteString(", ")
    86  		}
    87  	}
    88  	e.WriteString("]")
    89  	return nil
    90  }
    91  
    92  func (e *encodeState) encodeMap(m map[string]interface{}) error {
    93  	e.WriteString("{\n")
    94  	for i, k := range sortedKeys(m) {
    95  		v := m[k]
    96  
    97  		e.WriteString(fmt.Sprintf("%q = ", k))
    98  		err := e.encode(v)
    99  		if err != nil {
   100  			return err
   101  		}
   102  		if i < len(m)-1 {
   103  			e.WriteString("\n")
   104  		}
   105  	}
   106  	e.WriteString("}")
   107  	return nil
   108  }
   109  
   110  func (e *encodeState) encodeInt(i interface{}) error {
   111  	_, err := fmt.Fprintf(e, "%d", i)
   112  	return err
   113  }
   114  
   115  func (e *encodeState) encodeFloat(f interface{}) error {
   116  	_, err := fmt.Fprintf(e, "%g", f)
   117  	return err
   118  }
   119  
   120  func (e *encodeState) encodeString(s string) error {
   121  	e.Write(quoteHCLString(s))
   122  	return nil
   123  }
   124  
   125  // Quote an HCL string, which may contain interpolations.
   126  // Since the string was already parsed from HCL, we have to assume the
   127  // required characters are sanely escaped. All we need to do is escape double
   128  // quotes in the string, unless they are in an interpolation block.
   129  func quoteHCLString(s string) []byte {
   130  	out := make([]byte, 0, len(s))
   131  	out = append(out, '"')
   132  
   133  	// our parse states
   134  	var (
   135  		outer  = 1 // the starting state for the string
   136  		dollar = 2 // look for '{' in the next character
   137  		interp = 3 // inside an interpolation block
   138  		escape = 4 // take the next character and pop back to prev state
   139  	)
   140  
   141  	// we could have nested interpolations
   142  	state := stack{}
   143  	state.push(outer)
   144  
   145  	for i := 0; i < len(s); i++ {
   146  		switch state.peek() {
   147  		case outer:
   148  			switch s[i] {
   149  			case '"':
   150  				out = append(out, '\\')
   151  			case '$':
   152  				state.push(dollar)
   153  			case '\\':
   154  				state.push(escape)
   155  			}
   156  		case dollar:
   157  			state.pop()
   158  			switch s[i] {
   159  			case '{':
   160  				state.push(interp)
   161  			case '\\':
   162  				state.push(escape)
   163  			}
   164  		case interp:
   165  			switch s[i] {
   166  			case '}':
   167  				state.pop()
   168  			}
   169  		case escape:
   170  			state.pop()
   171  		}
   172  
   173  		out = append(out, s[i])
   174  	}
   175  
   176  	out = append(out, '"')
   177  
   178  	return out
   179  }
   180  
   181  type stack []int
   182  
   183  func (s *stack) push(i int) {
   184  	*s = append(*s, i)
   185  }
   186  
   187  func (s *stack) pop() int {
   188  	last := len(*s) - 1
   189  	i := (*s)[last]
   190  	*s = (*s)[:last]
   191  	return i
   192  }
   193  
   194  func (s *stack) peek() int {
   195  	return (*s)[len(*s)-1]
   196  }