github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonformat/computed/renderers/object.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 "github.com/terramate-io/tf/plans" 13 ) 14 15 var _ computed.DiffRenderer = (*objectRenderer)(nil) 16 17 func Object(attributes map[string]computed.Diff) computed.DiffRenderer { 18 return &objectRenderer{ 19 attributes: attributes, 20 overrideNullSuffix: true, 21 } 22 } 23 24 func NestedObject(attributes map[string]computed.Diff) computed.DiffRenderer { 25 return &objectRenderer{ 26 attributes: attributes, 27 overrideNullSuffix: false, 28 } 29 } 30 31 type objectRenderer struct { 32 NoWarningsRenderer 33 34 attributes map[string]computed.Diff 35 overrideNullSuffix bool 36 } 37 38 func (renderer objectRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { 39 if len(renderer.attributes) == 0 { 40 return fmt.Sprintf("{}%s%s", nullSuffix(diff.Action, opts), forcesReplacement(diff.Replace, opts)) 41 } 42 43 attributeOpts := opts.Clone() 44 attributeOpts.OverrideNullSuffix = renderer.overrideNullSuffix 45 46 // We need to keep track of our keys in two ways. The first is the order in 47 // which we will display them. The second is a mapping to their safely 48 // escaped equivalent. 49 50 maximumKeyLen := 0 51 var keys []string 52 escapedKeys := make(map[string]string) 53 for key := range renderer.attributes { 54 keys = append(keys, key) 55 escapedKey := EnsureValidAttributeName(key) 56 escapedKeys[key] = escapedKey 57 if maximumKeyLen < len(escapedKey) { 58 maximumKeyLen = len(escapedKey) 59 } 60 } 61 sort.Strings(keys) 62 63 unchangedAttributes := 0 64 var buf bytes.Buffer 65 buf.WriteString(fmt.Sprintf("{%s\n", forcesReplacement(diff.Replace, opts))) 66 for _, key := range keys { 67 attribute := renderer.attributes[key] 68 69 if importantAttribute(key) { 70 importantAttributeOpts := attributeOpts.Clone() 71 importantAttributeOpts.ShowUnchangedChildren = true 72 73 for _, warning := range attribute.WarningsHuman(indent+1, importantAttributeOpts) { 74 buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning)) 75 } 76 buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, importantAttributeOpts), maximumKeyLen, escapedKeys[key], attribute.RenderHuman(indent+1, importantAttributeOpts))) 77 continue 78 } 79 80 if attribute.Action == plans.NoOp && !opts.ShowUnchangedChildren { 81 // Don't render NoOp operations when we are compact display. 82 unchangedAttributes++ 83 continue 84 } 85 86 for _, warning := range attribute.WarningsHuman(indent+1, opts) { 87 buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning)) 88 } 89 buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, attributeOpts), maximumKeyLen, escapedKeys[key], attribute.RenderHuman(indent+1, attributeOpts))) 90 } 91 92 if unchangedAttributes > 0 { 93 buf.WriteString(fmt.Sprintf("%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("attribute", unchangedAttributes, opts))) 94 } 95 96 buf.WriteString(fmt.Sprintf("%s%s}%s", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts), nullSuffix(diff.Action, opts))) 97 return buf.String() 98 }