github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/computed/renderers/list.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package renderers
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  
    12  	"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
    13  	"github.com/opentofu/opentofu/internal/plans"
    14  )
    15  
    16  var _ computed.DiffRenderer = (*listRenderer)(nil)
    17  
    18  func List(elements []computed.Diff) computed.DiffRenderer {
    19  	return &listRenderer{
    20  		displayContext: true,
    21  		elements:       elements,
    22  	}
    23  }
    24  
    25  func NestedList(elements []computed.Diff) computed.DiffRenderer {
    26  	return &listRenderer{
    27  		elements: elements,
    28  	}
    29  }
    30  
    31  type listRenderer struct {
    32  	NoWarningsRenderer
    33  
    34  	displayContext bool
    35  	elements       []computed.Diff
    36  }
    37  
    38  func (renderer listRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string {
    39  	if len(renderer.elements) == 0 {
    40  		return fmt.Sprintf("[]%s%s", nullSuffix(diff.Action, opts), forcesReplacement(diff.Replace, opts))
    41  	}
    42  
    43  	elementOpts := opts.Clone()
    44  	elementOpts.OverrideNullSuffix = true
    45  
    46  	unchangedElementOpts := opts.Clone()
    47  	unchangedElementOpts.ShowUnchangedChildren = true
    48  
    49  	var unchangedElements []computed.Diff
    50  
    51  	// renderNext tells the renderer to print out the next element in the list
    52  	// whatever state it is in. So, even if a change is a NoOp we will still
    53  	// print it out if the last change we processed wants us to.
    54  	renderNext := false
    55  
    56  	var buf bytes.Buffer
    57  	buf.WriteString(fmt.Sprintf("[%s\n", forcesReplacement(diff.Replace, opts)))
    58  	for _, element := range renderer.elements {
    59  		if element.Action == plans.NoOp && !renderNext && !opts.ShowUnchangedChildren {
    60  			unchangedElements = append(unchangedElements, element)
    61  			continue
    62  		}
    63  		renderNext = false
    64  
    65  		opts := elementOpts
    66  
    67  		// If we want to display the context around this change, we want to
    68  		// render the change immediately before this change in the list, and the
    69  		// change immediately after in the list, even if both these changes are
    70  		// NoOps. This will give the user reading the diff some context as to
    71  		// where in the list these changes are being made, as order matters.
    72  		if renderer.displayContext {
    73  			// If our list of unchanged elements contains more than one entry
    74  			// we'll print out a count of the number of unchanged elements that
    75  			// we skipped. Note, this is the length of the unchanged elements
    76  			// minus 1 as the most recent unchanged element will be printed out
    77  			// in full.
    78  			if len(unchangedElements) > 1 {
    79  				buf.WriteString(fmt.Sprintf("%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("element", len(unchangedElements)-1, opts)))
    80  			}
    81  			// If our list of unchanged elements contains at least one entry,
    82  			// we're going to print out the most recent change in full. That's
    83  			// what happens here.
    84  			if len(unchangedElements) > 0 {
    85  				lastElement := unchangedElements[len(unchangedElements)-1]
    86  				buf.WriteString(fmt.Sprintf("%s%s%s,\n", formatIndent(indent+1), writeDiffActionSymbol(lastElement.Action, unchangedElementOpts), lastElement.RenderHuman(indent+1, unchangedElementOpts)))
    87  			}
    88  			// We now reset the unchanged elements list, we've printed out a
    89  			// count of all the elements we skipped so we start counting from
    90  			// scratch again. This means that if we process a run of changed
    91  			// elements, they won't all start printing out summaries of every
    92  			// change that happened previously.
    93  			unchangedElements = nil
    94  
    95  			if element.Action == plans.NoOp {
    96  				// If this is a NoOp action then we're going to render it below
    97  				// so we need to just override the opts we're going to use to
    98  				// make sure we use the unchanged opts.
    99  				opts = unchangedElementOpts
   100  			} else {
   101  				// As we also want to render the element immediately after any
   102  				// changes, we make a note here to say we should render the next
   103  				// change whatever it is. But, we only want to render the next
   104  				// change if the current change isn't a NoOp. If the current change
   105  				// is a NoOp then it was told to print by the last change and we
   106  				// don't want to cascade and print all changes from now on.
   107  				renderNext = true
   108  			}
   109  		}
   110  
   111  		for _, warning := range element.WarningsHuman(indent+1, opts) {
   112  			buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
   113  		}
   114  		buf.WriteString(fmt.Sprintf("%s%s%s,\n", formatIndent(indent+1), writeDiffActionSymbol(element.Action, opts), element.RenderHuman(indent+1, opts)))
   115  	}
   116  
   117  	// If we were not displaying any context alongside our changes then the
   118  	// unchangedElements list will contain every unchanged element, and we'll
   119  	// print that out as we do with every other collection.
   120  	//
   121  	// If we were displaying context, then this will contain any unchanged
   122  	// elements since our last change, so we should also print it out.
   123  	if len(unchangedElements) > 0 {
   124  		buf.WriteString(fmt.Sprintf("%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("element", len(unchangedElements), opts)))
   125  	}
   126  
   127  	buf.WriteString(fmt.Sprintf("%s%s]%s", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts), nullSuffix(diff.Action, opts)))
   128  	return buf.String()
   129  }