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