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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package hclwrite
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2/hclsyntax"
     8  	"github.com/zclconf/go-cty/cty"
     9  )
    10  
    11  type Block struct {
    12  	inTree
    13  
    14  	leadComments *node
    15  	typeName     *node
    16  	labels       *node
    17  	open         *node
    18  	body         *node
    19  	close        *node
    20  }
    21  
    22  func newBlock() *Block {
    23  	return &Block{
    24  		inTree: newInTree(),
    25  	}
    26  }
    27  
    28  // NewBlock constructs a new, empty block with the given type name and labels.
    29  func NewBlock(typeName string, labels []string) *Block {
    30  	block := newBlock()
    31  	block.init(typeName, labels)
    32  	return block
    33  }
    34  
    35  func (b *Block) init(typeName string, labels []string) {
    36  	nameTok := newIdentToken(typeName)
    37  	nameObj := newIdentifier(nameTok)
    38  	b.leadComments = b.children.Append(newComments(nil))
    39  	b.typeName = b.children.Append(nameObj)
    40  	labelsObj := newBlockLabels(labels)
    41  	b.labels = b.children.Append(labelsObj)
    42  	b.open = b.children.AppendUnstructuredTokens(Tokens{
    43  		{
    44  			Type:  hclsyntax.TokenOBrace,
    45  			Bytes: []byte{'{'},
    46  		},
    47  		{
    48  			Type:  hclsyntax.TokenNewline,
    49  			Bytes: []byte{'\n'},
    50  		},
    51  	})
    52  	body := newBody() // initially totally empty; caller can append to it subsequently
    53  	b.body = b.children.Append(body)
    54  	b.close = b.children.AppendUnstructuredTokens(Tokens{
    55  		{
    56  			Type:  hclsyntax.TokenCBrace,
    57  			Bytes: []byte{'}'},
    58  		},
    59  		{
    60  			Type:  hclsyntax.TokenNewline,
    61  			Bytes: []byte{'\n'},
    62  		},
    63  	})
    64  }
    65  
    66  // Body returns the body that represents the content of the receiving block.
    67  //
    68  // Appending to or otherwise modifying this body will make changes to the
    69  // tokens that are generated between the blocks open and close braces.
    70  func (b *Block) Body() *Body {
    71  	return b.body.content.(*Body)
    72  }
    73  
    74  // Type returns the type name of the block.
    75  func (b *Block) Type() string {
    76  	typeNameObj := b.typeName.content.(*identifier)
    77  	return string(typeNameObj.token.Bytes)
    78  }
    79  
    80  // SetType updates the type name of the block to a given name.
    81  func (b *Block) SetType(typeName string) {
    82  	nameTok := newIdentToken(typeName)
    83  	nameObj := newIdentifier(nameTok)
    84  	b.typeName.ReplaceWith(nameObj)
    85  }
    86  
    87  // Labels returns the labels of the block.
    88  func (b *Block) Labels() []string {
    89  	return b.labelsObj().Current()
    90  }
    91  
    92  // SetLabels updates the labels of the block to given labels.
    93  // Since we cannot assume that old and new labels are equal in length,
    94  // remove old labels and insert new ones before TokenOBrace.
    95  func (b *Block) SetLabels(labels []string) {
    96  	b.labelsObj().Replace(labels)
    97  }
    98  
    99  // labelsObj returns the internal node content representation of the block
   100  // labels. This is not part of the public API because we're intentionally
   101  // exposing only a limited API to get/set labels on the block itself in a
   102  // manner similar to the main hcl.Block type, but our block accessors all
   103  // use this to get the underlying node content to work with.
   104  func (b *Block) labelsObj() *blockLabels {
   105  	return b.labels.content.(*blockLabels)
   106  }
   107  
   108  type blockLabels struct {
   109  	inTree
   110  
   111  	items nodeSet
   112  }
   113  
   114  func newBlockLabels(labels []string) *blockLabels {
   115  	ret := &blockLabels{
   116  		inTree: newInTree(),
   117  		items:  newNodeSet(),
   118  	}
   119  
   120  	ret.Replace(labels)
   121  	return ret
   122  }
   123  
   124  func (bl *blockLabels) Replace(newLabels []string) {
   125  	bl.inTree.children.Clear()
   126  	bl.items.Clear()
   127  
   128  	for _, label := range newLabels {
   129  		labelToks := TokensForValue(cty.StringVal(label))
   130  		// Force a new label to use the quoted form, which is the idiomatic
   131  		// form. The unquoted form is supported in HCL 2 only for compatibility
   132  		// with historical use in HCL 1.
   133  		labelObj := newQuoted(labelToks)
   134  		labelNode := bl.children.Append(labelObj)
   135  		bl.items.Add(labelNode)
   136  	}
   137  }
   138  
   139  func (bl *blockLabels) Current() []string {
   140  	labelNames := make([]string, 0, len(bl.items))
   141  	list := bl.items.List()
   142  
   143  	for _, label := range list {
   144  		switch labelObj := label.content.(type) {
   145  		case *identifier:
   146  			if labelObj.token.Type == hclsyntax.TokenIdent {
   147  				labelString := string(labelObj.token.Bytes)
   148  				labelNames = append(labelNames, labelString)
   149  			}
   150  
   151  		case *quoted:
   152  			tokens := labelObj.tokens
   153  			if len(tokens) == 3 &&
   154  				tokens[0].Type == hclsyntax.TokenOQuote &&
   155  				tokens[1].Type == hclsyntax.TokenQuotedLit &&
   156  				tokens[2].Type == hclsyntax.TokenCQuote {
   157  				// Note that TokenQuotedLit may contain escape sequences.
   158  				labelString, diags := hclsyntax.ParseStringLiteralToken(tokens[1].asHCLSyntax())
   159  
   160  				// If parsing the string literal returns error diagnostics
   161  				// then we can just assume the label doesn't match, because it's invalid in some way.
   162  				if !diags.HasErrors() {
   163  					labelNames = append(labelNames, labelString)
   164  				}
   165  			} else if len(tokens) == 2 &&
   166  				tokens[0].Type == hclsyntax.TokenOQuote &&
   167  				tokens[1].Type == hclsyntax.TokenCQuote {
   168  				// An open quote followed immediately by a closing quote is a
   169  				// valid but unusual blank string label.
   170  				labelNames = append(labelNames, "")
   171  			}
   172  
   173  		default:
   174  			// If neither of the previous cases are true (should be impossible)
   175  			// then we can just ignore it, because it's invalid too.
   176  		}
   177  	}
   178  
   179  	return labelNames
   180  }