github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/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 }