github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/ast_body.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclwrite
     5  
     6  import (
     7  	"reflect"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hclsyntax"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  type Body struct {
    15  	inTree
    16  
    17  	items nodeSet
    18  }
    19  
    20  func newBody() *Body {
    21  	return &Body{
    22  		inTree: newInTree(),
    23  		items:  newNodeSet(),
    24  	}
    25  }
    26  
    27  func (b *Body) appendItem(c nodeContent) *node {
    28  	nn := b.children.Append(c)
    29  	b.items.Add(nn)
    30  	return nn
    31  }
    32  
    33  func (b *Body) appendItemNode(nn *node) *node {
    34  	nn.assertUnattached()
    35  	b.children.AppendNode(nn)
    36  	b.items.Add(nn)
    37  	return nn
    38  }
    39  
    40  // Clear removes all of the items from the body, making it empty.
    41  func (b *Body) Clear() {
    42  	b.children.Clear()
    43  }
    44  
    45  func (b *Body) AppendUnstructuredTokens(ts Tokens) {
    46  	b.inTree.children.Append(ts)
    47  }
    48  
    49  // Attributes returns a new map of all of the attributes in the body, with
    50  // the attribute names as the keys.
    51  func (b *Body) Attributes() map[string]*Attribute {
    52  	ret := make(map[string]*Attribute)
    53  	for n := range b.items {
    54  		if attr, isAttr := n.content.(*Attribute); isAttr {
    55  			nameObj := attr.name.content.(*identifier)
    56  			name := string(nameObj.token.Bytes)
    57  			ret[name] = attr
    58  		}
    59  	}
    60  	return ret
    61  }
    62  
    63  // Blocks returns a new slice of all the blocks in the body.
    64  func (b *Body) Blocks() []*Block {
    65  	ret := make([]*Block, 0, len(b.items))
    66  	for _, n := range b.items.List() {
    67  		if block, isBlock := n.content.(*Block); isBlock {
    68  			ret = append(ret, block)
    69  		}
    70  	}
    71  	return ret
    72  }
    73  
    74  // GetAttribute returns the attribute from the body that has the given name,
    75  // or returns nil if there is currently no matching attribute.
    76  func (b *Body) GetAttribute(name string) *Attribute {
    77  	for n := range b.items {
    78  		if attr, isAttr := n.content.(*Attribute); isAttr {
    79  			nameObj := attr.name.content.(*identifier)
    80  			if nameObj.hasName(name) {
    81  				// We've found it!
    82  				return attr
    83  			}
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  // getAttributeNode is like GetAttribute but it returns the node containing
    91  // the selected attribute (if one is found) rather than the attribute itself.
    92  func (b *Body) getAttributeNode(name string) *node {
    93  	for n := range b.items {
    94  		if attr, isAttr := n.content.(*Attribute); isAttr {
    95  			nameObj := attr.name.content.(*identifier)
    96  			if nameObj.hasName(name) {
    97  				// We've found it!
    98  				return n
    99  			}
   100  		}
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  // FirstMatchingBlock returns a first matching block from the body that has the
   107  // given name and labels or returns nil if there is currently no matching
   108  // block.
   109  func (b *Body) FirstMatchingBlock(typeName string, labels []string) *Block {
   110  	for _, block := range b.Blocks() {
   111  		if typeName == block.Type() {
   112  			labelNames := block.Labels()
   113  			if len(labels) == 0 && len(labelNames) == 0 {
   114  				return block
   115  			}
   116  			if reflect.DeepEqual(labels, labelNames) {
   117  				return block
   118  			}
   119  		}
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // RemoveBlock removes the given block from the body, if it's in that body.
   126  // If it isn't present, this is a no-op.
   127  //
   128  // Returns true if it removed something, or false otherwise.
   129  func (b *Body) RemoveBlock(block *Block) bool {
   130  	for n := range b.items {
   131  		if n.content == block {
   132  			n.Detach()
   133  			b.items.Remove(n)
   134  			return true
   135  		}
   136  	}
   137  	return false
   138  }
   139  
   140  // SetAttributeRaw either replaces the expression of an existing attribute
   141  // of the given name or adds a new attribute definition to the end of the block,
   142  // using the given tokens verbatim as the expression.
   143  //
   144  // The same caveats apply to this function as for NewExpressionRaw on which
   145  // it is based. If possible, prefer to use SetAttributeValue or
   146  // SetAttributeTraversal.
   147  func (b *Body) SetAttributeRaw(name string, tokens Tokens) *Attribute {
   148  	attr := b.GetAttribute(name)
   149  	expr := NewExpressionRaw(tokens)
   150  	if attr != nil {
   151  		attr.expr = attr.expr.ReplaceWith(expr)
   152  	} else {
   153  		attr := newAttribute()
   154  		attr.init(name, expr)
   155  		b.appendItem(attr)
   156  	}
   157  	return attr
   158  }
   159  
   160  // SetAttributeValue either replaces the expression of an existing attribute
   161  // of the given name or adds a new attribute definition to the end of the block.
   162  //
   163  // The value is given as a cty.Value, and must therefore be a literal. To set
   164  // a variable reference or other traversal, use SetAttributeTraversal.
   165  //
   166  // The return value is the attribute that was either modified in-place or
   167  // created.
   168  func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
   169  	attr := b.GetAttribute(name)
   170  	expr := NewExpressionLiteral(val)
   171  	if attr != nil {
   172  		attr.expr = attr.expr.ReplaceWith(expr)
   173  	} else {
   174  		attr := newAttribute()
   175  		attr.init(name, expr)
   176  		b.appendItem(attr)
   177  	}
   178  	return attr
   179  }
   180  
   181  // SetAttributeTraversal either replaces the expression of an existing attribute
   182  // of the given name or adds a new attribute definition to the end of the body.
   183  //
   184  // The new expression is given as a hcl.Traversal, which must be an absolute
   185  // traversal. To set a literal value, use SetAttributeValue.
   186  //
   187  // The return value is the attribute that was either modified in-place or
   188  // created.
   189  func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute {
   190  	attr := b.GetAttribute(name)
   191  	expr := NewExpressionAbsTraversal(traversal)
   192  	if attr != nil {
   193  		attr.expr = attr.expr.ReplaceWith(expr)
   194  	} else {
   195  		attr := newAttribute()
   196  		attr.init(name, expr)
   197  		b.appendItem(attr)
   198  	}
   199  	return attr
   200  }
   201  
   202  // RemoveAttribute removes the attribute with the given name from the body.
   203  //
   204  // The return value is the attribute that was removed, or nil if there was
   205  // no such attribute (in which case the call was a no-op).
   206  func (b *Body) RemoveAttribute(name string) *Attribute {
   207  	node := b.getAttributeNode(name)
   208  	if node == nil {
   209  		return nil
   210  	}
   211  	node.Detach()
   212  	b.items.Remove(node)
   213  	return node.content.(*Attribute)
   214  }
   215  
   216  // AppendBlock appends an existing block (which must not be already attached
   217  // to a body) to the end of the receiving body.
   218  func (b *Body) AppendBlock(block *Block) *Block {
   219  	b.appendItem(block)
   220  	return block
   221  }
   222  
   223  // AppendNewBlock appends a new nested block to the end of the receiving body
   224  // with the given type name and labels.
   225  func (b *Body) AppendNewBlock(typeName string, labels []string) *Block {
   226  	block := newBlock()
   227  	block.init(typeName, labels)
   228  	b.appendItem(block)
   229  	return block
   230  }
   231  
   232  // AppendNewline appends a newline token to th end of the receiving body,
   233  // which generally serves as a separator between different sets of body
   234  // contents.
   235  func (b *Body) AppendNewline() {
   236  	b.AppendUnstructuredTokens(Tokens{
   237  		{
   238  			Type:  hclsyntax.TokenNewline,
   239  			Bytes: []byte{'\n'},
   240  		},
   241  	})
   242  }