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