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 }