| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package renderers | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "sort" | |
| "github.com/opentofu/opentofu/internal/command/jsonformat/computed" | |
| "github.com/opentofu/opentofu/internal/plans" | |
| ) | |
| var _ computed.DiffRenderer = (*mapRenderer)(nil) | |
| func Map(elements map[string]computed.Diff) computed.DiffRenderer { | |
| return &mapRenderer{ | |
| elements: elements, | |
| alignKeys: true, | |
| } | |
| } | |
| func NestedMap(elements map[string]computed.Diff) computed.DiffRenderer { | |
| return &mapRenderer{ | |
| elements: elements, | |
| overrideNullSuffix: true, | |
| overrideForcesReplacement: true, | |
| } | |
| } | |
| type mapRenderer struct { | |
| NoWarningsRenderer | |
| elements map[string]computed.Diff | |
| overrideNullSuffix bool | |
| overrideForcesReplacement bool | |
| alignKeys bool | |
| } | |
| func (renderer mapRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { | |
| forcesReplacementSelf := diff.Replace && !renderer.overrideForcesReplacement | |
| forcesReplacementChildren := diff.Replace && renderer.overrideForcesReplacement | |
| if len(renderer.elements) == 0 { | |
| return fmt.Sprintf("{}%s%s", nullSuffix(diff.Action, opts), forcesReplacement(forcesReplacementSelf, opts)) | |
| } | |
| // Sort the map elements by key, so we have a deterministic ordering in | |
| // the output. | |
| var keys []string | |
| // We need to make sure the keys are capable of rendering properly. | |
| escapedKeys := make(map[string]string) | |
| maximumKeyLen := 0 | |
| for key := range renderer.elements { | |
| keys = append(keys, key) | |
| escapedKey := hclEscapeString(key) | |
| escapedKeys[key] = escapedKey | |
| if maximumKeyLen < len(escapedKey) { | |
| maximumKeyLen = len(escapedKey) | |
| } | |
| } | |
| sort.Strings(keys) | |
| unchangedElements := 0 | |
| elementOpts := opts.Clone() | |
| elementOpts.OverrideNullSuffix = diff.Action == plans.Delete || renderer.overrideNullSuffix | |
| elementOpts.OverrideForcesReplacement = forcesReplacementChildren | |
| var buf bytes.Buffer | |
| fmt.Fprintf(&buf, "{%s\n", forcesReplacement(forcesReplacementSelf, opts)) | |
| for _, key := range keys { | |
| element := renderer.elements[key] | |
| if element.Action == plans.NoOp && !opts.ShowUnchangedChildren { | |
| // Don't render NoOp operations when we are compact display. | |
| unchangedElements++ | |
| continue | |
| } | |
| for _, warning := range element.WarningsHuman(indent+1, opts) { | |
| fmt.Fprintf(&buf, "%s%s\n", formatIndent(indent+1), warning) | |
| } | |
| // Only show commas between elements for objects. | |
| comma := "" | |
| if _, ok := element.Renderer.(*objectRenderer); ok { | |
| comma = "," | |
| } | |
| if renderer.alignKeys { | |
| fmt.Fprintf(&buf, "%s%s%-*s = %s%s\n", formatIndent(indent+1), writeDiffActionSymbol(element.Action, elementOpts), maximumKeyLen, escapedKeys[key], element.RenderHuman(indent+1, elementOpts), comma) | |
| } else { | |
| fmt.Fprintf(&buf, "%s%s%s = %s%s\n", formatIndent(indent+1), writeDiffActionSymbol(element.Action, elementOpts), escapedKeys[key], element.RenderHuman(indent+1, elementOpts), comma) | |
| } | |
| } | |
| if unchangedElements > 0 { | |
| fmt.Fprintf(&buf, "%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("element", unchangedElements, opts)) | |
| } | |
| fmt.Fprintf(&buf, "%s%s}%s", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts), nullSuffix(diff.Action, opts)) | |
| return buf.String() | |
| } |