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