github.com/hashicorp/hcl/v2@v2.20.0/gohcl/encode.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package gohcl 5 6 import ( 7 "fmt" 8 "reflect" 9 "sort" 10 11 "github.com/hashicorp/hcl/v2/hclwrite" 12 "github.com/zclconf/go-cty/cty/gocty" 13 ) 14 15 // EncodeIntoBody replaces the contents of the given hclwrite Body with 16 // attributes and blocks derived from the given value, which must be a 17 // struct value or a pointer to a struct value with the struct tags defined 18 // in this package. 19 // 20 // This function can work only with fully-decoded data. It will ignore any 21 // fields tagged as "remain", any fields that decode attributes into either 22 // hcl.Attribute or hcl.Expression values, and any fields that decode blocks 23 // into hcl.Attributes values. This function does not have enough information 24 // to complete the decoding of these types. 25 // 26 // Any fields tagged as "label" are ignored by this function. Use EncodeAsBlock 27 // to produce a whole hclwrite.Block including block labels. 28 // 29 // As long as a suitable value is given to encode and the destination body 30 // is non-nil, this function will always complete. It will panic in case of 31 // any errors in the calling program, such as passing an inappropriate type 32 // or a nil body. 33 // 34 // The layout of the resulting HCL source is derived from the ordering of 35 // the struct fields, with blank lines around nested blocks of different types. 36 // Fields representing attributes should usually precede those representing 37 // blocks so that the attributes can group togather in the result. For more 38 // control, use the hclwrite API directly. 39 func EncodeIntoBody(val interface{}, dst *hclwrite.Body) { 40 rv := reflect.ValueOf(val) 41 ty := rv.Type() 42 if ty.Kind() == reflect.Ptr { 43 rv = rv.Elem() 44 ty = rv.Type() 45 } 46 if ty.Kind() != reflect.Struct { 47 panic(fmt.Sprintf("value is %s, not struct", ty.Kind())) 48 } 49 50 tags := getFieldTags(ty) 51 populateBody(rv, ty, tags, dst) 52 } 53 54 // EncodeAsBlock creates a new hclwrite.Block populated with the data from 55 // the given value, which must be a struct or pointer to struct with the 56 // struct tags defined in this package. 57 // 58 // If the given struct type has fields tagged with "label" tags then they 59 // will be used in order to annotate the created block with labels. 60 // 61 // This function has the same constraints as EncodeIntoBody and will panic 62 // if they are violated. 63 func EncodeAsBlock(val interface{}, blockType string) *hclwrite.Block { 64 rv := reflect.ValueOf(val) 65 ty := rv.Type() 66 if ty.Kind() == reflect.Ptr { 67 rv = rv.Elem() 68 ty = rv.Type() 69 } 70 if ty.Kind() != reflect.Struct { 71 panic(fmt.Sprintf("value is %s, not struct", ty.Kind())) 72 } 73 74 tags := getFieldTags(ty) 75 labels := make([]string, len(tags.Labels)) 76 for i, lf := range tags.Labels { 77 lv := rv.Field(lf.FieldIndex) 78 // We just stringify whatever we find. It should always be a string 79 // but if not then we'll still do something reasonable. 80 labels[i] = fmt.Sprintf("%s", lv.Interface()) 81 } 82 83 block := hclwrite.NewBlock(blockType, labels) 84 populateBody(rv, ty, tags, block.Body()) 85 return block 86 } 87 88 func populateBody(rv reflect.Value, ty reflect.Type, tags *fieldTags, dst *hclwrite.Body) { 89 nameIdxs := make(map[string]int, len(tags.Attributes)+len(tags.Blocks)) 90 namesOrder := make([]string, 0, len(tags.Attributes)+len(tags.Blocks)) 91 for n, i := range tags.Attributes { 92 nameIdxs[n] = i 93 namesOrder = append(namesOrder, n) 94 } 95 for n, i := range tags.Blocks { 96 nameIdxs[n] = i 97 namesOrder = append(namesOrder, n) 98 } 99 sort.SliceStable(namesOrder, func(i, j int) bool { 100 ni, nj := namesOrder[i], namesOrder[j] 101 return nameIdxs[ni] < nameIdxs[nj] 102 }) 103 104 dst.Clear() 105 106 prevWasBlock := false 107 for _, name := range namesOrder { 108 fieldIdx := nameIdxs[name] 109 field := ty.Field(fieldIdx) 110 fieldTy := field.Type 111 fieldVal := rv.Field(fieldIdx) 112 113 if fieldTy.Kind() == reflect.Ptr { 114 fieldTy = fieldTy.Elem() 115 fieldVal = fieldVal.Elem() 116 } 117 118 if _, isAttr := tags.Attributes[name]; isAttr { 119 120 if exprType.AssignableTo(fieldTy) || attrType.AssignableTo(fieldTy) { 121 continue // ignore undecoded fields 122 } 123 if !fieldVal.IsValid() { 124 continue // ignore (field value is nil pointer) 125 } 126 if fieldTy.Kind() == reflect.Ptr && fieldVal.IsNil() { 127 continue // ignore 128 } 129 if prevWasBlock { 130 dst.AppendNewline() 131 prevWasBlock = false 132 } 133 134 valTy, err := gocty.ImpliedType(fieldVal.Interface()) 135 if err != nil { 136 panic(fmt.Sprintf("cannot encode %T as HCL expression: %s", fieldVal.Interface(), err)) 137 } 138 139 val, err := gocty.ToCtyValue(fieldVal.Interface(), valTy) 140 if err != nil { 141 // This should never happen, since we should always be able 142 // to decode into the implied type. 143 panic(fmt.Sprintf("failed to encode %T as %#v: %s", fieldVal.Interface(), valTy, err)) 144 } 145 146 dst.SetAttributeValue(name, val) 147 148 } else { // must be a block, then 149 elemTy := fieldTy 150 isSeq := false 151 if elemTy.Kind() == reflect.Slice || elemTy.Kind() == reflect.Array { 152 isSeq = true 153 elemTy = elemTy.Elem() 154 } 155 156 if bodyType.AssignableTo(elemTy) || attrsType.AssignableTo(elemTy) { 157 continue // ignore undecoded fields 158 } 159 prevWasBlock = false 160 161 if isSeq { 162 l := fieldVal.Len() 163 for i := 0; i < l; i++ { 164 elemVal := fieldVal.Index(i) 165 if !elemVal.IsValid() { 166 continue // ignore (elem value is nil pointer) 167 } 168 if elemTy.Kind() == reflect.Ptr && elemVal.IsNil() { 169 continue // ignore 170 } 171 block := EncodeAsBlock(elemVal.Interface(), name) 172 if !prevWasBlock { 173 dst.AppendNewline() 174 prevWasBlock = true 175 } 176 dst.AppendBlock(block) 177 } 178 } else { 179 if !fieldVal.IsValid() { 180 continue // ignore (field value is nil pointer) 181 } 182 if elemTy.Kind() == reflect.Ptr && fieldVal.IsNil() { 183 continue // ignore 184 } 185 block := EncodeAsBlock(fieldVal.Interface(), name) 186 if !prevWasBlock { 187 dst.AppendNewline() 188 prevWasBlock = true 189 } 190 dst.AppendBlock(block) 191 } 192 } 193 } 194 }