aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/hcl2/gohcl
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/hcl2/gohcl')
-rw-r--r--vendor/github.com/hashicorp/hcl2/gohcl/decode.go304
-rw-r--r--vendor/github.com/hashicorp/hcl2/gohcl/doc.go49
-rw-r--r--vendor/github.com/hashicorp/hcl2/gohcl/schema.go174
-rw-r--r--vendor/github.com/hashicorp/hcl2/gohcl/types.go16
4 files changed, 543 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/hcl2/gohcl/decode.go b/vendor/github.com/hashicorp/hcl2/gohcl/decode.go
new file mode 100644
index 0000000..3a149a8
--- /dev/null
+++ b/vendor/github.com/hashicorp/hcl2/gohcl/decode.go
@@ -0,0 +1,304 @@
1package gohcl
2
3import (
4 "fmt"
5 "reflect"
6
7 "github.com/zclconf/go-cty/cty"
8
9 "github.com/hashicorp/hcl2/hcl"
10 "github.com/zclconf/go-cty/cty/convert"
11 "github.com/zclconf/go-cty/cty/gocty"
12)
13
14// DecodeBody extracts the configuration within the given body into the given
15// value. This value must be a non-nil pointer to either a struct or
16// a map, where in the former case the configuration will be decoded using
17// struct tags and in the latter case only attributes are allowed and their
18// values are decoded into the map.
19//
20// The given EvalContext is used to resolve any variables or functions in
21// expressions encountered while decoding. This may be nil to require only
22// constant values, for simple applications that do not support variables or
23// functions.
24//
25// The returned diagnostics should be inspected with its HasErrors method to
26// determine if the populated value is valid and complete. If error diagnostics
27// are returned then the given value may have been partially-populated but
28// may still be accessed by a careful caller for static analysis and editor
29// integration use-cases.
30func DecodeBody(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
31 rv := reflect.ValueOf(val)
32 if rv.Kind() != reflect.Ptr {
33 panic(fmt.Sprintf("target value must be a pointer, not %s", rv.Type().String()))
34 }
35
36 return decodeBodyToValue(body, ctx, rv.Elem())
37}
38
39func decodeBodyToValue(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
40 et := val.Type()
41 switch et.Kind() {
42 case reflect.Struct:
43 return decodeBodyToStruct(body, ctx, val)
44 case reflect.Map:
45 return decodeBodyToMap(body, ctx, val)
46 default:
47 panic(fmt.Sprintf("target value must be pointer to struct or map, not %s", et.String()))
48 }
49}
50
51func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
52 schema, partial := ImpliedBodySchema(val.Interface())
53
54 var content *hcl.BodyContent
55 var leftovers hcl.Body
56 var diags hcl.Diagnostics
57 if partial {
58 content, leftovers, diags = body.PartialContent(schema)
59 } else {
60 content, diags = body.Content(schema)
61 }
62 if content == nil {
63 return diags
64 }
65
66 tags := getFieldTags(val.Type())
67
68 if tags.Remain != nil {
69 fieldIdx := *tags.Remain
70 field := val.Type().Field(fieldIdx)
71 fieldV := val.Field(fieldIdx)
72 switch {
73 case bodyType.AssignableTo(field.Type):
74 fieldV.Set(reflect.ValueOf(leftovers))
75 case attrsType.AssignableTo(field.Type):
76 attrs, attrsDiags := leftovers.JustAttributes()
77 if len(attrsDiags) > 0 {
78 diags = append(diags, attrsDiags...)
79 }
80 fieldV.Set(reflect.ValueOf(attrs))
81 default:
82 diags = append(diags, decodeBodyToValue(leftovers, ctx, fieldV)...)
83 }
84 }
85
86 for name, fieldIdx := range tags.Attributes {
87 attr := content.Attributes[name]
88 field := val.Type().Field(fieldIdx)
89 fieldV := val.Field(fieldIdx)
90
91 if attr == nil {
92 if !exprType.AssignableTo(field.Type) {
93 continue
94 }
95
96 // As a special case, if the target is of type hcl.Expression then
97 // we'll assign an actual expression that evalues to a cty null,
98 // so the caller can deal with it within the cty realm rather
99 // than within the Go realm.
100 synthExpr := hcl.StaticExpr(cty.NullVal(cty.DynamicPseudoType), body.MissingItemRange())
101 fieldV.Set(reflect.ValueOf(synthExpr))
102 continue
103 }
104
105 switch {
106 case attrType.AssignableTo(field.Type):
107 fieldV.Set(reflect.ValueOf(attr))
108 case exprType.AssignableTo(field.Type):
109 fieldV.Set(reflect.ValueOf(attr.Expr))
110 default:
111 diags = append(diags, DecodeExpression(
112 attr.Expr, ctx, fieldV.Addr().Interface(),
113 )...)
114 }
115 }
116
117 blocksByType := content.Blocks.ByType()
118
119 for typeName, fieldIdx := range tags.Blocks {
120 blocks := blocksByType[typeName]
121 field := val.Type().Field(fieldIdx)
122
123 ty := field.Type
124 isSlice := false
125 isPtr := false
126 if ty.Kind() == reflect.Slice {
127 isSlice = true
128 ty = ty.Elem()
129 }
130 if ty.Kind() == reflect.Ptr {
131 isPtr = true
132 ty = ty.Elem()
133 }
134
135 if len(blocks) > 1 && !isSlice {
136 diags = append(diags, &hcl.Diagnostic{
137 Severity: hcl.DiagError,
138 Summary: fmt.Sprintf("Duplicate %s block", typeName),
139 Detail: fmt.Sprintf(
140 "Only one %s block is allowed. Another was defined at %s.",
141 typeName, blocks[0].DefRange.String(),
142 ),
143 Subject: &blocks[1].DefRange,
144 })
145 continue
146 }
147
148 if len(blocks) == 0 {
149 if isSlice || isPtr {
150 val.Field(fieldIdx).Set(reflect.Zero(field.Type))
151 } else {
152 diags = append(diags, &hcl.Diagnostic{
153 Severity: hcl.DiagError,
154 Summary: fmt.Sprintf("Missing %s block", typeName),
155 Detail: fmt.Sprintf("A %s block is required.", typeName),
156 Subject: body.MissingItemRange().Ptr(),
157 })
158 }
159 continue
160 }
161
162 switch {
163
164 case isSlice:
165 elemType := ty
166 if isPtr {
167 elemType = reflect.PtrTo(ty)
168 }
169 sli := reflect.MakeSlice(reflect.SliceOf(elemType), len(blocks), len(blocks))
170
171 for i, block := range blocks {
172 if isPtr {
173 v := reflect.New(ty)
174 diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
175 sli.Index(i).Set(v)
176 } else {
177 diags = append(diags, decodeBlockToValue(block, ctx, sli.Index(i))...)
178 }
179 }
180
181 val.Field(fieldIdx).Set(sli)
182
183 default:
184 block := blocks[0]
185 if isPtr {
186 v := reflect.New(ty)
187 diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
188 val.Field(fieldIdx).Set(v)
189 } else {
190 diags = append(diags, decodeBlockToValue(block, ctx, val.Field(fieldIdx))...)
191 }
192
193 }
194
195 }
196
197 return diags
198}
199
200func decodeBodyToMap(body hcl.Body, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
201 attrs, diags := body.JustAttributes()
202 if attrs == nil {
203 return diags
204 }
205
206 mv := reflect.MakeMap(v.Type())
207
208 for k, attr := range attrs {
209 switch {
210 case attrType.AssignableTo(v.Type().Elem()):
211 mv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(attr))
212 case exprType.AssignableTo(v.Type().Elem()):
213 mv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(attr.Expr))
214 default:
215 ev := reflect.New(v.Type().Elem())
216 diags = append(diags, DecodeExpression(attr.Expr, ctx, ev.Interface())...)
217 mv.SetMapIndex(reflect.ValueOf(k), ev.Elem())
218 }
219 }
220
221 v.Set(mv)
222
223 return diags
224}
225
226func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
227 var diags hcl.Diagnostics
228
229 ty := v.Type()
230
231 switch {
232 case blockType.AssignableTo(ty):
233 v.Elem().Set(reflect.ValueOf(block))
234 case bodyType.AssignableTo(ty):
235 v.Elem().Set(reflect.ValueOf(block.Body))
236 case attrsType.AssignableTo(ty):
237 attrs, attrsDiags := block.Body.JustAttributes()
238 if len(attrsDiags) > 0 {
239 diags = append(diags, attrsDiags...)
240 }
241 v.Elem().Set(reflect.ValueOf(attrs))
242 default:
243 diags = append(diags, decodeBodyToValue(block.Body, ctx, v)...)
244
245 if len(block.Labels) > 0 {
246 blockTags := getFieldTags(ty)
247 for li, lv := range block.Labels {
248 lfieldIdx := blockTags.Labels[li].FieldIndex
249 v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
250 }
251 }
252
253 }
254
255 return diags
256}
257
258// DecodeExpression extracts the value of the given expression into the given
259// value. This value must be something that gocty is able to decode into,
260// since the final decoding is delegated to that package.
261//
262// The given EvalContext is used to resolve any variables or functions in
263// expressions encountered while decoding. This may be nil to require only
264// constant values, for simple applications that do not support variables or
265// functions.
266//
267// The returned diagnostics should be inspected with its HasErrors method to
268// determine if the populated value is valid and complete. If error diagnostics
269// are returned then the given value may have been partially-populated but
270// may still be accessed by a careful caller for static analysis and editor
271// integration use-cases.
272func DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
273 srcVal, diags := expr.Value(ctx)
274
275 convTy, err := gocty.ImpliedType(val)
276 if err != nil {
277 panic(fmt.Sprintf("unsuitable DecodeExpression target: %s", err))
278 }
279
280 srcVal, err = convert.Convert(srcVal, convTy)
281 if err != nil {
282 diags = append(diags, &hcl.Diagnostic{
283 Severity: hcl.DiagError,
284 Summary: "Unsuitable value type",
285 Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
286 Subject: expr.StartRange().Ptr(),
287 Context: expr.Range().Ptr(),
288 })
289 return diags
290 }
291
292 err = gocty.FromCtyValue(srcVal, val)
293 if err != nil {
294 diags = append(diags, &hcl.Diagnostic{
295 Severity: hcl.DiagError,
296 Summary: "Unsuitable value type",
297 Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
298 Subject: expr.StartRange().Ptr(),
299 Context: expr.Range().Ptr(),
300 })
301 }
302
303 return diags
304}
diff --git a/vendor/github.com/hashicorp/hcl2/gohcl/doc.go b/vendor/github.com/hashicorp/hcl2/gohcl/doc.go
new file mode 100644
index 0000000..8500214
--- /dev/null
+++ b/vendor/github.com/hashicorp/hcl2/gohcl/doc.go
@@ -0,0 +1,49 @@
1// Package gohcl allows decoding HCL configurations into Go data structures.
2//
3// It provides a convenient and concise way of describing the schema for
4// configuration and then accessing the resulting data via native Go
5// types.
6//
7// A struct field tag scheme is used, similar to other decoding and
8// unmarshalling libraries. The tags are formatted as in the following example:
9//
10// ThingType string `hcl:"thing_type,attr"`
11//
12// Within each tag there are two comma-separated tokens. The first is the
13// name of the corresponding construct in configuration, while the second
14// is a keyword giving the kind of construct expected. The following
15// kind keywords are supported:
16//
17// attr (the default) indicates that the value is to be populated from an attribute
18// block indicates that the value is to populated from a block
19// label indicates that the value is to populated from a block label
20// remain indicates that the value is to be populated from the remaining body after populating other fields
21//
22// "attr" fields may either be of type *hcl.Expression, in which case the raw
23// expression is assigned, or of any type accepted by gocty, in which case
24// gocty will be used to assign the value to a native Go type.
25//
26// "block" fields may be of type *hcl.Block or hcl.Body, in which case the
27// corresponding raw value is assigned, or may be a struct that recursively
28// uses the same tags. Block fields may also be slices of any of these types,
29// in which case multiple blocks of the corresponding type are decoded into
30// the slice.
31//
32// "label" fields are considered only in a struct used as the type of a field
33// marked as "block", and are used sequentially to capture the labels of
34// the blocks being decoded. In this case, the name token is used only as
35// an identifier for the label in diagnostic messages.
36//
37// "remain" can be placed on a single field that may be either of type
38// hcl.Body or hcl.Attributes, in which case any remaining body content is
39// placed into this field for delayed processing. If no "remain" field is
40// present then any attributes or blocks not matched by another valid tag
41// will cause an error diagnostic.
42//
43// Broadly-speaking this package deals with two types of error. The first is
44// errors in the configuration itself, which are returned as diagnostics
45// written with the configuration author as the target audience. The second
46// is bugs in the calling program, such as invalid struct tags, which are
47// surfaced via panics since there can be no useful runtime handling of such
48// errors and they should certainly not be returned to the user as diagnostics.
49package gohcl
diff --git a/vendor/github.com/hashicorp/hcl2/gohcl/schema.go b/vendor/github.com/hashicorp/hcl2/gohcl/schema.go
new file mode 100644
index 0000000..88164cb
--- /dev/null
+++ b/vendor/github.com/hashicorp/hcl2/gohcl/schema.go
@@ -0,0 +1,174 @@
1package gohcl
2
3import (
4 "fmt"
5 "reflect"
6 "sort"
7 "strings"
8
9 "github.com/hashicorp/hcl2/hcl"
10)
11
12// ImpliedBodySchema produces a hcl.BodySchema derived from the type of the
13// given value, which must be a struct value or a pointer to one. If an
14// inappropriate value is passed, this function will panic.
15//
16// The second return argument indicates whether the given struct includes
17// a "remain" field, and thus the returned schema is non-exhaustive.
18//
19// This uses the tags on the fields of the struct to discover how each
20// field's value should be expressed within configuration. If an invalid
21// mapping is attempted, this function will panic.
22func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
23 ty := reflect.TypeOf(val)
24
25 if ty.Kind() == reflect.Ptr {
26 ty = ty.Elem()
27 }
28
29 if ty.Kind() != reflect.Struct {
30 panic(fmt.Sprintf("given value must be struct, not %T", val))
31 }
32
33 var attrSchemas []hcl.AttributeSchema
34 var blockSchemas []hcl.BlockHeaderSchema
35
36 tags := getFieldTags(ty)
37
38 attrNames := make([]string, 0, len(tags.Attributes))
39 for n := range tags.Attributes {
40 attrNames = append(attrNames, n)
41 }
42 sort.Strings(attrNames)
43 for _, n := range attrNames {
44 idx := tags.Attributes[n]
45 optional := tags.Optional[n]
46 field := ty.Field(idx)
47
48 var required bool
49
50 switch {
51 case field.Type.AssignableTo(exprType):
52 // If we're decoding to hcl.Expression then absense can be
53 // indicated via a null value, so we don't specify that
54 // the field is required during decoding.
55 required = false
56 case field.Type.Kind() != reflect.Ptr && !optional:
57 required = true
58 default:
59 required = false
60 }
61
62 attrSchemas = append(attrSchemas, hcl.AttributeSchema{
63 Name: n,
64 Required: required,
65 })
66 }
67
68 blockNames := make([]string, 0, len(tags.Blocks))
69 for n := range tags.Blocks {
70 blockNames = append(blockNames, n)
71 }
72 sort.Strings(blockNames)
73 for _, n := range blockNames {
74 idx := tags.Blocks[n]
75 field := ty.Field(idx)
76 fty := field.Type
77 if fty.Kind() == reflect.Slice {
78 fty = fty.Elem()
79 }
80 if fty.Kind() == reflect.Ptr {
81 fty = fty.Elem()
82 }
83 if fty.Kind() != reflect.Struct {
84 panic(fmt.Sprintf(
85 "hcl 'block' tag kind cannot be applied to %s field %s: struct required", field.Type.String(), field.Name,
86 ))
87 }
88 ftags := getFieldTags(fty)
89 var labelNames []string
90 if len(ftags.Labels) > 0 {
91 labelNames = make([]string, len(ftags.Labels))
92 for i, l := range ftags.Labels {
93 labelNames[i] = l.Name
94 }
95 }
96
97 blockSchemas = append(blockSchemas, hcl.BlockHeaderSchema{
98 Type: n,
99 LabelNames: labelNames,
100 })
101 }
102
103 partial = tags.Remain != nil
104 schema = &hcl.BodySchema{
105 Attributes: attrSchemas,
106 Blocks: blockSchemas,
107 }
108 return schema, partial
109}
110
111type fieldTags struct {
112 Attributes map[string]int
113 Blocks map[string]int
114 Labels []labelField
115 Remain *int
116 Optional map[string]bool
117}
118
119type labelField struct {
120 FieldIndex int
121 Name string
122}
123
124func getFieldTags(ty reflect.Type) *fieldTags {
125 ret := &fieldTags{
126 Attributes: map[string]int{},
127 Blocks: map[string]int{},
128 Optional: map[string]bool{},
129 }
130
131 ct := ty.NumField()
132 for i := 0; i < ct; i++ {
133 field := ty.Field(i)
134 tag := field.Tag.Get("hcl")
135 if tag == "" {
136 continue
137 }
138
139 comma := strings.Index(tag, ",")
140 var name, kind string
141 if comma != -1 {
142 name = tag[:comma]
143 kind = tag[comma+1:]
144 } else {
145 name = tag
146 kind = "attr"
147 }
148
149 switch kind {
150 case "attr":
151 ret.Attributes[name] = i
152 case "block":
153 ret.Blocks[name] = i
154 case "label":
155 ret.Labels = append(ret.Labels, labelField{
156 FieldIndex: i,
157 Name: name,
158 })
159 case "remain":
160 if ret.Remain != nil {
161 panic("only one 'remain' tag is permitted")
162 }
163 idx := i // copy, because this loop will continue assigning to i
164 ret.Remain = &idx
165 case "optional":
166 ret.Attributes[name] = i
167 ret.Optional[name] = true
168 default:
169 panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
170 }
171 }
172
173 return ret
174}
diff --git a/vendor/github.com/hashicorp/hcl2/gohcl/types.go b/vendor/github.com/hashicorp/hcl2/gohcl/types.go
new file mode 100644
index 0000000..a94f275
--- /dev/null
+++ b/vendor/github.com/hashicorp/hcl2/gohcl/types.go
@@ -0,0 +1,16 @@
1package gohcl
2
3import (
4 "reflect"
5
6 "github.com/hashicorp/hcl2/hcl"
7)
8
9var victimExpr hcl.Expression
10var victimBody hcl.Body
11
12var exprType = reflect.TypeOf(&victimExpr).Elem()
13var bodyType = reflect.TypeOf(&victimBody).Elem()
14var blockType = reflect.TypeOf((*hcl.Block)(nil))
15var attrType = reflect.TypeOf((*hcl.Attribute)(nil))
16var attrsType = reflect.TypeOf(hcl.Attributes(nil))