github.com/hernad/nomad@v1.6.112/jobspec2/hclutil/blockattrs.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclutil 5 6 import ( 7 "github.com/hashicorp/hcl/v2" 8 hcls "github.com/hashicorp/hcl/v2/hclsyntax" 9 ) 10 11 // BlocksAsAttrs rewrites the hcl.Body so that hcl blocks are treated as 12 // attributes when schema is unknown. 13 // 14 // This conversion is necessary for parsing task driver configs, as they can be 15 // arbitrary nested without pre-defined schema. 16 // 17 // More concretely, it changes the following: 18 // 19 // ``` 20 // 21 // config { 22 // meta { ... } 23 // } 24 // 25 // ``` 26 // 27 // to 28 // 29 // ``` 30 // 31 // config { 32 // meta = { ... } # <- attribute now 33 // } 34 // 35 // ``` 36 func BlocksAsAttrs(body hcl.Body) hcl.Body { 37 if hclb, ok := body.(*hcls.Body); ok { 38 return &blockAttrs{body: hclb} 39 } 40 return body 41 } 42 43 type blockAttrs struct { 44 body hcl.Body 45 46 hiddenAttrs map[string]struct{} 47 hiddenBlocks map[string]struct{} 48 } 49 50 func (b *blockAttrs) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { 51 bc, diags := b.body.Content(schema) 52 bc.Blocks = expandBlocks(bc.Blocks) 53 return bc, diags 54 } 55 func (b *blockAttrs) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { 56 bc, remainBody, diags := b.body.PartialContent(schema) 57 bc.Blocks = expandBlocks(bc.Blocks) 58 59 remain := &blockAttrs{ 60 body: remainBody, 61 hiddenAttrs: map[string]struct{}{}, 62 hiddenBlocks: map[string]struct{}{}, 63 } 64 for name := range b.hiddenAttrs { 65 remain.hiddenAttrs[name] = struct{}{} 66 } 67 for typeName := range b.hiddenBlocks { 68 remain.hiddenBlocks[typeName] = struct{}{} 69 } 70 for _, attrS := range schema.Attributes { 71 remain.hiddenAttrs[attrS.Name] = struct{}{} 72 } 73 for _, blockS := range schema.Blocks { 74 remain.hiddenBlocks[blockS.Type] = struct{}{} 75 } 76 77 return bc, remain, diags 78 } 79 80 func (b *blockAttrs) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { 81 body, ok := b.body.(*hcls.Body) 82 if !ok { 83 return b.body.JustAttributes() 84 } 85 86 attrs := make(hcl.Attributes) 87 var diags hcl.Diagnostics 88 89 if body.Attributes == nil && len(body.Blocks) == 0 { 90 return attrs, diags 91 } 92 93 for name, attr := range body.Attributes { 94 if _, hidden := b.hiddenAttrs[name]; hidden { 95 continue 96 } 97 98 na := attr.AsHCLAttribute() 99 na.Expr = attrExpr(attr.Expr) 100 attrs[name] = na 101 } 102 103 for _, blocks := range blocksByType(body.Blocks) { 104 if _, hidden := b.hiddenBlocks[blocks[0].Type]; hidden { 105 continue 106 } 107 108 b := blocks[0] 109 attr := &hcls.Attribute{ 110 Name: b.Type, 111 NameRange: b.TypeRange, 112 EqualsRange: b.OpenBraceRange, 113 SrcRange: b.Body.SrcRange, 114 Expr: blocksToExpr(blocks), 115 } 116 117 attrs[blocks[0].Type] = attr.AsHCLAttribute() 118 } 119 120 return attrs, diags 121 } 122 123 func (b *blockAttrs) MissingItemRange() hcl.Range { 124 return b.body.MissingItemRange() 125 } 126 127 func expandBlocks(blocks hcl.Blocks) hcl.Blocks { 128 if len(blocks) == 0 { 129 return blocks 130 } 131 132 r := make([]*hcl.Block, len(blocks)) 133 for i, b := range blocks { 134 nb := *b 135 nb.Body = BlocksAsAttrs(b.Body) 136 r[i] = &nb 137 } 138 return r 139 } 140 141 func blocksByType(blocks hcls.Blocks) map[string]hcls.Blocks { 142 r := map[string]hcls.Blocks{} 143 for _, b := range blocks { 144 r[b.Type] = append(r[b.Type], b) 145 } 146 return r 147 } 148 149 func blocksToExpr(blocks hcls.Blocks) hcls.Expression { 150 if len(blocks) == 0 { 151 panic("unexpected empty blocks") 152 } 153 154 exprs := make([]hcls.Expression, len(blocks)) 155 for i, b := range blocks { 156 exprs[i] = blockToExpr(b) 157 } 158 159 last := blocks[len(blocks)-1] 160 return &hcls.TupleConsExpr{ 161 Exprs: exprs, 162 163 SrcRange: hcl.RangeBetween(blocks[0].OpenBraceRange, last.CloseBraceRange), 164 OpenRange: blocks[0].OpenBraceRange, 165 } 166 } 167 168 func blockToExpr(b *hcls.Block) hcls.Expression { 169 items := []hcls.ObjectConsItem{} 170 171 for _, attr := range b.Body.Attributes { 172 keyExpr := &hcls.ScopeTraversalExpr{ 173 Traversal: hcl.Traversal{ 174 hcl.TraverseRoot{ 175 Name: attr.Name, 176 SrcRange: attr.NameRange, 177 }, 178 }, 179 SrcRange: attr.NameRange, 180 } 181 key := &hcls.ObjectConsKeyExpr{ 182 Wrapped: keyExpr, 183 } 184 185 items = append(items, hcls.ObjectConsItem{ 186 KeyExpr: key, 187 ValueExpr: attrExpr(attr.Expr), 188 }) 189 } 190 191 for _, blocks := range blocksByType(b.Body.Blocks) { 192 keyExpr := &hcls.ScopeTraversalExpr{ 193 Traversal: hcl.Traversal{ 194 hcl.TraverseRoot{ 195 Name: blocks[0].Type, 196 SrcRange: blocks[0].TypeRange, 197 }, 198 }, 199 SrcRange: blocks[0].TypeRange, 200 } 201 key := &hcls.ObjectConsKeyExpr{ 202 Wrapped: keyExpr, 203 } 204 item := hcls.ObjectConsItem{ 205 KeyExpr: key, 206 ValueExpr: blocksToExpr(blocks), 207 } 208 209 items = append(items, item) 210 } 211 212 v := &hcls.ObjectConsExpr{ 213 Items: items, 214 } 215 216 // Create nested maps, with the labels as keys. 217 // Starts wrapping from most inner label to outer 218 for i := len(b.Labels) - 1; i >= 0; i-- { 219 keyExpr := &hcls.ScopeTraversalExpr{ 220 Traversal: hcl.Traversal{ 221 hcl.TraverseRoot{ 222 Name: b.Labels[i], 223 SrcRange: b.LabelRanges[i], 224 }, 225 }, 226 SrcRange: b.LabelRanges[i], 227 } 228 key := &hcls.ObjectConsKeyExpr{ 229 Wrapped: keyExpr, 230 } 231 item := hcls.ObjectConsItem{ 232 KeyExpr: key, 233 ValueExpr: &hcls.TupleConsExpr{ 234 Exprs: []hcls.Expression{v}, 235 }, 236 } 237 238 v = &hcls.ObjectConsExpr{ 239 Items: []hcls.ObjectConsItem{item}, 240 } 241 242 } 243 return v 244 } 245 246 func attrExpr(expr hcls.Expression) hcls.Expression { 247 if _, ok := expr.(*hcls.ObjectConsExpr); ok { 248 return &hcls.TupleConsExpr{ 249 Exprs: []hcls.Expression{expr}, 250 SrcRange: expr.Range(), 251 OpenRange: expr.StartRange(), 252 } 253 } 254 255 return expr 256 }