github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonformat/computed/renderers/map.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 "sort" 10 11 "github.com/terramate-io/tf/command/jsonformat/computed" 12 13 "github.com/terramate-io/tf/plans" 14 ) 15 16 var _ computed.DiffRenderer = (*mapRenderer)(nil) 17 18 func Map(elements map[string]computed.Diff) computed.DiffRenderer { 19 return &mapRenderer{ 20 elements: elements, 21 alignKeys: true, 22 } 23 } 24 25 func NestedMap(elements map[string]computed.Diff) computed.DiffRenderer { 26 return &mapRenderer{ 27 elements: elements, 28 overrideNullSuffix: true, 29 overrideForcesReplacement: true, 30 } 31 } 32 33 type mapRenderer struct { 34 NoWarningsRenderer 35 36 elements map[string]computed.Diff 37 38 overrideNullSuffix bool 39 overrideForcesReplacement bool 40 alignKeys bool 41 } 42 43 func (renderer mapRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { 44 forcesReplacementSelf := diff.Replace && !renderer.overrideForcesReplacement 45 forcesReplacementChildren := diff.Replace && renderer.overrideForcesReplacement 46 47 if len(renderer.elements) == 0 { 48 return fmt.Sprintf("{}%s%s", nullSuffix(diff.Action, opts), forcesReplacement(forcesReplacementSelf, opts)) 49 } 50 51 // Sort the map elements by key, so we have a deterministic ordering in 52 // the output. 53 var keys []string 54 55 // We need to make sure the keys are capable of rendering properly. 56 escapedKeys := make(map[string]string) 57 58 maximumKeyLen := 0 59 for key := range renderer.elements { 60 keys = append(keys, key) 61 62 escapedKey := hclEscapeString(key) 63 escapedKeys[key] = escapedKey 64 if maximumKeyLen < len(escapedKey) { 65 maximumKeyLen = len(escapedKey) 66 } 67 } 68 sort.Strings(keys) 69 70 unchangedElements := 0 71 72 elementOpts := opts.Clone() 73 elementOpts.OverrideNullSuffix = diff.Action == plans.Delete || renderer.overrideNullSuffix 74 elementOpts.OverrideForcesReplacement = forcesReplacementChildren 75 76 var buf bytes.Buffer 77 buf.WriteString(fmt.Sprintf("{%s\n", forcesReplacement(forcesReplacementSelf, opts))) 78 for _, key := range keys { 79 element := renderer.elements[key] 80 81 if element.Action == plans.NoOp && !opts.ShowUnchangedChildren { 82 // Don't render NoOp operations when we are compact display. 83 unchangedElements++ 84 continue 85 } 86 87 for _, warning := range element.WarningsHuman(indent+1, opts) { 88 buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning)) 89 } 90 // Only show commas between elements for objects. 91 comma := "" 92 if _, ok := element.Renderer.(*objectRenderer); ok { 93 comma = "," 94 } 95 96 if renderer.alignKeys { 97 buf.WriteString(fmt.Sprintf("%s%s%-*s = %s%s\n", formatIndent(indent+1), writeDiffActionSymbol(element.Action, elementOpts), maximumKeyLen, escapedKeys[key], element.RenderHuman(indent+1, elementOpts), comma)) 98 } else { 99 buf.WriteString(fmt.Sprintf("%s%s%s = %s%s\n", formatIndent(indent+1), writeDiffActionSymbol(element.Action, elementOpts), escapedKeys[key], element.RenderHuman(indent+1, elementOpts), comma)) 100 } 101 102 } 103 104 if unchangedElements > 0 { 105 buf.WriteString(fmt.Sprintf("%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("element", unchangedElements, opts))) 106 } 107 108 buf.WriteString(fmt.Sprintf("%s%s}%s", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts), nullSuffix(diff.Action, opts))) 109 return buf.String() 110 }