]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/hcl2/hcl/merged.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / hcl2 / hcl / merged.go
1 package hcl
2
3 import (
4 "fmt"
5 )
6
7 // MergeFiles combines the given files to produce a single body that contains
8 // configuration from all of the given files.
9 //
10 // The ordering of the given files decides the order in which contained
11 // elements will be returned. If any top-level attributes are defined with
12 // the same name across multiple files, a diagnostic will be produced from
13 // the Content and PartialContent methods describing this error in a
14 // user-friendly way.
15 func MergeFiles(files []*File) Body {
16 var bodies []Body
17 for _, file := range files {
18 bodies = append(bodies, file.Body)
19 }
20 return MergeBodies(bodies)
21 }
22
23 // MergeBodies is like MergeFiles except it deals directly with bodies, rather
24 // than with entire files.
25 func MergeBodies(bodies []Body) Body {
26 if len(bodies) == 0 {
27 // Swap out for our singleton empty body, to reduce the number of
28 // empty slices we have hanging around.
29 return emptyBody
30 }
31
32 // If any of the given bodies are already merged bodies, we'll unpack
33 // to flatten to a single mergedBodies, since that's conceptually simpler.
34 // This also, as a side-effect, eliminates any empty bodies, since
35 // empties are merged bodies with no inner bodies.
36 var newLen int
37 var flatten bool
38 for _, body := range bodies {
39 if children, merged := body.(mergedBodies); merged {
40 newLen += len(children)
41 flatten = true
42 } else {
43 newLen++
44 }
45 }
46
47 if !flatten { // not just newLen == len, because we might have mergedBodies with single bodies inside
48 return mergedBodies(bodies)
49 }
50
51 if newLen == 0 {
52 // Don't allocate a new empty when we already have one
53 return emptyBody
54 }
55
56 new := make([]Body, 0, newLen)
57 for _, body := range bodies {
58 if children, merged := body.(mergedBodies); merged {
59 new = append(new, children...)
60 } else {
61 new = append(new, body)
62 }
63 }
64 return mergedBodies(new)
65 }
66
67 var emptyBody = mergedBodies([]Body{})
68
69 // EmptyBody returns a body with no content. This body can be used as a
70 // placeholder when a body is required but no body content is available.
71 func EmptyBody() Body {
72 return emptyBody
73 }
74
75 type mergedBodies []Body
76
77 // Content returns the content produced by applying the given schema to all
78 // of the merged bodies and merging the result.
79 //
80 // Although required attributes _are_ supported, they should be used sparingly
81 // with merged bodies since in this case there is no contextual information
82 // with which to return good diagnostics. Applications working with merged
83 // bodies may wish to mark all attributes as optional and then check for
84 // required attributes afterwards, to produce better diagnostics.
85 func (mb mergedBodies) Content(schema *BodySchema) (*BodyContent, Diagnostics) {
86 // the returned body will always be empty in this case, because mergedContent
87 // will only ever call Content on the child bodies.
88 content, _, diags := mb.mergedContent(schema, false)
89 return content, diags
90 }
91
92 func (mb mergedBodies) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics) {
93 return mb.mergedContent(schema, true)
94 }
95
96 func (mb mergedBodies) JustAttributes() (Attributes, Diagnostics) {
97 attrs := make(map[string]*Attribute)
98 var diags Diagnostics
99
100 for _, body := range mb {
101 thisAttrs, thisDiags := body.JustAttributes()
102
103 if len(thisDiags) != 0 {
104 diags = append(diags, thisDiags...)
105 }
106
107 if thisAttrs != nil {
108 for name, attr := range thisAttrs {
109 if existing := attrs[name]; existing != nil {
110 diags = diags.Append(&Diagnostic{
111 Severity: DiagError,
112 Summary: "Duplicate argument",
113 Detail: fmt.Sprintf(
114 "Argument %q was already set at %s",
115 name, existing.NameRange.String(),
116 ),
117 Subject: &attr.NameRange,
118 })
119 continue
120 }
121
122 attrs[name] = attr
123 }
124 }
125 }
126
127 return attrs, diags
128 }
129
130 func (mb mergedBodies) MissingItemRange() Range {
131 if len(mb) == 0 {
132 // Nothing useful to return here, so we'll return some garbage.
133 return Range{
134 Filename: "<empty>",
135 }
136 }
137
138 // arbitrarily use the first body's missing item range
139 return mb[0].MissingItemRange()
140 }
141
142 func (mb mergedBodies) mergedContent(schema *BodySchema, partial bool) (*BodyContent, Body, Diagnostics) {
143 // We need to produce a new schema with none of the attributes marked as
144 // required, since _any one_ of our bodies can contribute an attribute value.
145 // We'll separately check that all required attributes are present at
146 // the end.
147 mergedSchema := &BodySchema{
148 Blocks: schema.Blocks,
149 }
150 for _, attrS := range schema.Attributes {
151 mergedAttrS := attrS
152 mergedAttrS.Required = false
153 mergedSchema.Attributes = append(mergedSchema.Attributes, mergedAttrS)
154 }
155
156 var mergedLeftovers []Body
157 content := &BodyContent{
158 Attributes: map[string]*Attribute{},
159 }
160
161 var diags Diagnostics
162 for _, body := range mb {
163 var thisContent *BodyContent
164 var thisLeftovers Body
165 var thisDiags Diagnostics
166
167 if partial {
168 thisContent, thisLeftovers, thisDiags = body.PartialContent(mergedSchema)
169 } else {
170 thisContent, thisDiags = body.Content(mergedSchema)
171 }
172
173 if thisLeftovers != nil {
174 mergedLeftovers = append(mergedLeftovers, thisLeftovers)
175 }
176 if len(thisDiags) != 0 {
177 diags = append(diags, thisDiags...)
178 }
179
180 if thisContent.Attributes != nil {
181 for name, attr := range thisContent.Attributes {
182 if existing := content.Attributes[name]; existing != nil {
183 diags = diags.Append(&Diagnostic{
184 Severity: DiagError,
185 Summary: "Duplicate argument",
186 Detail: fmt.Sprintf(
187 "Argument %q was already set at %s",
188 name, existing.NameRange.String(),
189 ),
190 Subject: &attr.NameRange,
191 })
192 continue
193 }
194 content.Attributes[name] = attr
195 }
196 }
197
198 if len(thisContent.Blocks) != 0 {
199 content.Blocks = append(content.Blocks, thisContent.Blocks...)
200 }
201 }
202
203 // Finally, we check for required attributes.
204 for _, attrS := range schema.Attributes {
205 if !attrS.Required {
206 continue
207 }
208
209 if content.Attributes[attrS.Name] == nil {
210 // We don't have any context here to produce a good diagnostic,
211 // which is why we warn in the Content docstring to minimize the
212 // use of required attributes on merged bodies.
213 diags = diags.Append(&Diagnostic{
214 Severity: DiagError,
215 Summary: "Missing required argument",
216 Detail: fmt.Sprintf(
217 "The argument %q is required, but was not set.",
218 attrS.Name,
219 ),
220 })
221 }
222 }
223
224 leftoverBody := MergeBodies(mergedLeftovers)
225 return content, leftoverBody, diags
226 }