diff options
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/gohcl/encode.go')
-rw-r--r-- | vendor/github.com/hashicorp/hcl2/gohcl/encode.go | 191 |
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 @@ | |||
1 | package gohcl | ||
2 | |||
3 | import ( | ||
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. | ||
36 | func 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. | ||
60 | func 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 | |||
85 | func 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 | } | ||