7 // MergeFiles combines the given files to produce a single body that contains
8 // configuration from all of the given files.
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
15 func MergeFiles(files []*File) Body {
17 for _, file := range files {
18 bodies = append(bodies, file.Body)
20 return MergeBodies(bodies)
23 // MergeBodies is like MergeFiles except it deals directly with bodies, rather
24 // than with entire files.
25 func MergeBodies(bodies []Body) Body {
27 // Swap out for our singleton empty body, to reduce the number of
28 // empty slices we have hanging around.
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.
38 for _, body := range bodies {
39 if children, merged := body.(mergedBodies); merged {
40 newLen += len(children)
47 if !flatten { // not just newLen == len, because we might have mergedBodies with single bodies inside
48 return mergedBodies(bodies)
52 // Don't allocate a new empty when we already have one
56 new := make([]Body, 0, newLen)
57 for _, body := range bodies {
58 if children, merged := body.(mergedBodies); merged {
59 new = append(new, children...)
61 new = append(new, body)
64 return mergedBodies(new)
67 var emptyBody = mergedBodies([]Body{})
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 {
75 type mergedBodies []Body
77 // Content returns the content produced by applying the given schema to all
78 // of the merged bodies and merging the result.
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)
92 func (mb mergedBodies) PartialContent(schema *BodySchema) (*BodyContent, Body, Diagnostics) {
93 return mb.mergedContent(schema, true)
96 func (mb mergedBodies) JustAttributes() (Attributes, Diagnostics) {
97 attrs := make(map[string]*Attribute)
100 for _, body := range mb {
101 thisAttrs, thisDiags := body.JustAttributes()
103 if len(thisDiags) != 0 {
104 diags = append(diags, thisDiags...)
107 if thisAttrs != nil {
108 for name, attr := range thisAttrs {
109 if existing := attrs[name]; existing != nil {
110 diags = diags.Append(&Diagnostic{
112 Summary: "Duplicate argument",
114 "Argument %q was already set at %s",
115 name, existing.NameRange.String(),
117 Subject: &attr.NameRange,
130 func (mb mergedBodies) MissingItemRange() Range {
132 // Nothing useful to return here, so we'll return some garbage.
138 // arbitrarily use the first body's missing item range
139 return mb[0].MissingItemRange()
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
147 mergedSchema := &BodySchema{
148 Blocks: schema.Blocks,
150 for _, attrS := range schema.Attributes {
152 mergedAttrS.Required = false
153 mergedSchema.Attributes = append(mergedSchema.Attributes, mergedAttrS)
156 var mergedLeftovers []Body
157 content := &BodyContent{
158 Attributes: map[string]*Attribute{},
161 var diags Diagnostics
162 for _, body := range mb {
163 var thisContent *BodyContent
164 var thisLeftovers Body
165 var thisDiags Diagnostics
168 thisContent, thisLeftovers, thisDiags = body.PartialContent(mergedSchema)
170 thisContent, thisDiags = body.Content(mergedSchema)
173 if thisLeftovers != nil {
174 mergedLeftovers = append(mergedLeftovers, thisLeftovers)
176 if len(thisDiags) != 0 {
177 diags = append(diags, thisDiags...)
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{
185 Summary: "Duplicate argument",
187 "Argument %q was already set at %s",
188 name, existing.NameRange.String(),
190 Subject: &attr.NameRange,
194 content.Attributes[name] = attr
198 if len(thisContent.Blocks) != 0 {
199 content.Blocks = append(content.Blocks, thisContent.Blocks...)
203 // Finally, we check for required attributes.
204 for _, attrS := range schema.Attributes {
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{
215 Summary: "Missing required argument",
217 "The argument %q is required, but was not set.",
224 leftoverBody := MergeBodies(mergedLeftovers)
225 return content, leftoverBody, diags