github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/jobspec2/hclutil/blockattrs.go (about)

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