command/format: a package for formatting plans/state for output
This commit is contained in:
parent
251e5c6f87
commit
0a0842a7d9
|
@ -0,0 +1,8 @@
|
|||
// Package format contains helpers for formatting various Terraform
|
||||
// structures for human-readabout output.
|
||||
//
|
||||
// This package is used by the official Terraform CLI in formatting any
|
||||
// output and is exported to encourage non-official frontends to mimic the
|
||||
// output formatting as much as possible so that text formats of Terraform
|
||||
// structures have a consistent look and feel.
|
||||
package format
|
|
@ -0,0 +1,231 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/colorstring"
|
||||
)
|
||||
|
||||
// PlanOpts are the options for formatting a plan.
|
||||
type PlanOpts struct {
|
||||
// Plan is the plan to format. This is required.
|
||||
Plan *terraform.Plan
|
||||
|
||||
// Color is the colorizer. This is optional.
|
||||
Color *colorstring.Colorize
|
||||
|
||||
// ModuleDepth is the depth of the modules to expand. By default this
|
||||
// is zero which will not expand modules at all.
|
||||
ModuleDepth int
|
||||
}
|
||||
|
||||
// Plan takes a plan and returns a
|
||||
func Plan(opts *PlanOpts) string {
|
||||
p := opts.Plan
|
||||
if p.Diff == nil || p.Diff.Empty() {
|
||||
return "This plan does nothing."
|
||||
}
|
||||
|
||||
if opts.Color == nil {
|
||||
opts.Color = &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Reset: false,
|
||||
}
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for _, m := range p.Diff.Modules {
|
||||
if len(m.Path)-1 <= opts.ModuleDepth || opts.ModuleDepth == -1 {
|
||||
formatPlanModuleExpand(buf, m, opts)
|
||||
} else {
|
||||
formatPlanModuleSingle(buf, m, opts)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimSpace(buf.String())
|
||||
}
|
||||
|
||||
// formatPlanModuleExpand will output the given module and all of its
|
||||
// resources.
|
||||
func formatPlanModuleExpand(
|
||||
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *PlanOpts) {
|
||||
// Ignore empty diffs
|
||||
if m.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
var moduleName string
|
||||
if !m.IsRoot() {
|
||||
moduleName = fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
|
||||
}
|
||||
|
||||
// We want to output the resources in sorted order to make things
|
||||
// easier to scan through, so get all the resource names and sort them.
|
||||
names := make([]string, 0, len(m.Resources))
|
||||
for name, _ := range m.Resources {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
// Go through each sorted name and start building the output
|
||||
for _, name := range names {
|
||||
rdiff := m.Resources[name]
|
||||
if rdiff.Empty() {
|
||||
continue
|
||||
}
|
||||
|
||||
dataSource := strings.HasPrefix(name, "data.")
|
||||
|
||||
if moduleName != "" {
|
||||
name = moduleName + "." + name
|
||||
}
|
||||
|
||||
// Determine the color for the text (green for adding, yellow
|
||||
// for change, red for delete), and symbol, and output the
|
||||
// resource header.
|
||||
color := "yellow"
|
||||
symbol := "~"
|
||||
oldValues := true
|
||||
switch rdiff.ChangeType() {
|
||||
case terraform.DiffDestroyCreate:
|
||||
color = "green"
|
||||
symbol = "-/+"
|
||||
case terraform.DiffCreate:
|
||||
color = "green"
|
||||
symbol = "+"
|
||||
oldValues = false
|
||||
|
||||
// If we're "creating" a data resource then we'll present it
|
||||
// to the user as a "read" operation, so it's clear that this
|
||||
// operation won't change anything outside of the Terraform state.
|
||||
// Unfortunately by the time we get here we only have the name
|
||||
// to work with, so we need to cheat and exploit knowledge of the
|
||||
// naming scheme for data resources.
|
||||
if dataSource {
|
||||
symbol = "<="
|
||||
color = "cyan"
|
||||
}
|
||||
case terraform.DiffDestroy:
|
||||
color = "red"
|
||||
symbol = "-"
|
||||
}
|
||||
|
||||
var extraAttr []string
|
||||
if rdiff.DestroyTainted {
|
||||
extraAttr = append(extraAttr, "tainted")
|
||||
}
|
||||
if rdiff.DestroyDeposed {
|
||||
extraAttr = append(extraAttr, "deposed")
|
||||
}
|
||||
var extraStr string
|
||||
if len(extraAttr) > 0 {
|
||||
extraStr = fmt.Sprintf(" (%s)", strings.Join(extraAttr, ", "))
|
||||
}
|
||||
|
||||
buf.WriteString(opts.Color.Color(fmt.Sprintf(
|
||||
"[%s]%s %s%s\n",
|
||||
color, symbol, name, extraStr)))
|
||||
|
||||
// Get all the attributes that are changing, and sort them. Also
|
||||
// determine the longest key so that we can align them all.
|
||||
keyLen := 0
|
||||
keys := make([]string, 0, len(rdiff.Attributes))
|
||||
for key, _ := range rdiff.Attributes {
|
||||
// Skip the ID since we do that specially
|
||||
if key == "id" {
|
||||
continue
|
||||
}
|
||||
|
||||
keys = append(keys, key)
|
||||
if len(key) > keyLen {
|
||||
keyLen = len(key)
|
||||
}
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// Go through and output each attribute
|
||||
for _, attrK := range keys {
|
||||
attrDiff := rdiff.Attributes[attrK]
|
||||
|
||||
v := attrDiff.New
|
||||
if v == "" && attrDiff.NewComputed {
|
||||
v = "<computed>"
|
||||
}
|
||||
|
||||
if attrDiff.Sensitive {
|
||||
v = "<sensitive>"
|
||||
}
|
||||
|
||||
updateMsg := ""
|
||||
if attrDiff.RequiresNew && rdiff.Destroy {
|
||||
updateMsg = opts.Color.Color(" [red](forces new resource)")
|
||||
} else if attrDiff.Sensitive && oldValues {
|
||||
updateMsg = opts.Color.Color(" [yellow](attribute changed)")
|
||||
}
|
||||
|
||||
if oldValues {
|
||||
var u string
|
||||
if attrDiff.Sensitive {
|
||||
u = "<sensitive>"
|
||||
} else {
|
||||
u = attrDiff.Old
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf(
|
||||
" %s:%s %#v => %#v%s\n",
|
||||
attrK,
|
||||
strings.Repeat(" ", keyLen-len(attrK)),
|
||||
u,
|
||||
v,
|
||||
updateMsg))
|
||||
} else {
|
||||
buf.WriteString(fmt.Sprintf(
|
||||
" %s:%s %#v%s\n",
|
||||
attrK,
|
||||
strings.Repeat(" ", keyLen-len(attrK)),
|
||||
v,
|
||||
updateMsg))
|
||||
}
|
||||
}
|
||||
|
||||
// Write the reset color so we don't overload the user's terminal
|
||||
buf.WriteString(opts.Color.Color("[reset]\n"))
|
||||
}
|
||||
}
|
||||
|
||||
// formatPlanModuleSingle will output the given module and all of its
|
||||
// resources.
|
||||
func formatPlanModuleSingle(
|
||||
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *PlanOpts) {
|
||||
// Ignore empty diffs
|
||||
if m.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
|
||||
|
||||
// Determine the color for the text (green for adding, yellow
|
||||
// for change, red for delete), and symbol, and output the
|
||||
// resource header.
|
||||
color := "yellow"
|
||||
symbol := "~"
|
||||
switch m.ChangeType() {
|
||||
case terraform.DiffCreate:
|
||||
color = "green"
|
||||
symbol = "+"
|
||||
case terraform.DiffDestroy:
|
||||
color = "red"
|
||||
symbol = "-"
|
||||
}
|
||||
|
||||
buf.WriteString(opts.Color.Color(fmt.Sprintf(
|
||||
"[%s]%s %s\n",
|
||||
color, symbol, moduleName)))
|
||||
buf.WriteString(fmt.Sprintf(
|
||||
" %d resource(s)",
|
||||
len(m.Resources)))
|
||||
buf.WriteString(opts.Color.Color("[reset]\n"))
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/colorstring"
|
||||
)
|
||||
|
||||
// Test that a root level data source gets a special plan output on create
|
||||
func TestPlan_destroyDeposed(t *testing.T) {
|
||||
plan := &terraform.Plan{
|
||||
Diff: &terraform.Diff{
|
||||
Modules: []*terraform.ModuleDiff{
|
||||
&terraform.ModuleDiff{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*terraform.InstanceDiff{
|
||||
"aws_instance.foo": &terraform.InstanceDiff{
|
||||
DestroyDeposed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
opts := &PlanOpts{
|
||||
Plan: plan,
|
||||
Color: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
},
|
||||
ModuleDepth: 1,
|
||||
}
|
||||
|
||||
actual := Plan(opts)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
- aws_instance.foo (deposed)
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that computed fields with an interpolation string get displayed
|
||||
func TestPlan_displayInterpolations(t *testing.T) {
|
||||
plan := &terraform.Plan{
|
||||
Diff: &terraform.Diff{
|
||||
Modules: []*terraform.ModuleDiff{
|
||||
&terraform.ModuleDiff{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*terraform.InstanceDiff{
|
||||
"aws_instance.foo": &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"computed_field": &terraform.ResourceAttrDiff{
|
||||
New: "${aws_instance.other.id}",
|
||||
NewComputed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
opts := &PlanOpts{
|
||||
Plan: plan,
|
||||
Color: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
},
|
||||
ModuleDepth: 1,
|
||||
}
|
||||
|
||||
out := Plan(opts)
|
||||
lines := strings.Split(out, "\n")
|
||||
if len(lines) != 2 {
|
||||
t.Fatal("expected 2 lines of output, got:\n", out)
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(lines[1])
|
||||
expected := `computed_field: "" => "${aws_instance.other.id}"`
|
||||
|
||||
if actual != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that a root level data source gets a special plan output on create
|
||||
func TestPlan_rootDataSource(t *testing.T) {
|
||||
plan := &terraform.Plan{
|
||||
Diff: &terraform.Diff{
|
||||
Modules: []*terraform.ModuleDiff{
|
||||
&terraform.ModuleDiff{
|
||||
Path: []string{"root"},
|
||||
Resources: map[string]*terraform.InstanceDiff{
|
||||
"data.type.name": &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"A": &terraform.ResourceAttrDiff{
|
||||
New: "B",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
opts := &PlanOpts{
|
||||
Plan: plan,
|
||||
Color: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
},
|
||||
ModuleDepth: 1,
|
||||
}
|
||||
|
||||
actual := Plan(opts)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
<= data.type.name
|
||||
A: "B"
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that data sources nested in modules get the same plan output
|
||||
func TestPlan_nestedDataSource(t *testing.T) {
|
||||
plan := &terraform.Plan{
|
||||
Diff: &terraform.Diff{
|
||||
Modules: []*terraform.ModuleDiff{
|
||||
&terraform.ModuleDiff{
|
||||
Path: []string{"root", "nested"},
|
||||
Resources: map[string]*terraform.InstanceDiff{
|
||||
"data.type.name": &terraform.InstanceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"A": &terraform.ResourceAttrDiff{
|
||||
New: "B",
|
||||
RequiresNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
opts := &PlanOpts{
|
||||
Plan: plan,
|
||||
Color: &colorstring.Colorize{
|
||||
Colors: colorstring.DefaultColors,
|
||||
Disable: true,
|
||||
},
|
||||
ModuleDepth: 2,
|
||||
}
|
||||
|
||||
actual := Plan(opts)
|
||||
|
||||
expected := strings.TrimSpace(`
|
||||
<= module.nested.data.type.name
|
||||
A: "B"
|
||||
`)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
package format
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/colorstring"
|
||||
)
|
||||
|
||||
// StateOpts are the options for formatting a state.
|
||||
type StateOpts struct {
|
||||
// State is the state to format. This is required.
|
||||
State *terraform.State
|
||||
|
||||
// Color is the colorizer. This is optional.
|
||||
Color *colorstring.Colorize
|
||||
|
||||
// ModuleDepth is the depth of the modules to expand. By default this
|
||||
// is zero which will not expand modules at all.
|
||||
ModuleDepth int
|
||||
}
|
||||
|
||||
// State takes a state and returns a string
|
||||
func State(opts *StateOpts) string {
|
||||
if opts.Color == nil {
|
||||
panic("colorize not given")
|
||||
}
|
||||
|
||||
s := opts.State
|
||||
if len(s.Modules) == 0 {
|
||||
return "The state file is empty. No resources are represented."
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("[reset]")
|
||||
|
||||
// Format all the modules
|
||||
for _, m := range s.Modules {
|
||||
if len(m.Path)-1 <= opts.ModuleDepth || opts.ModuleDepth == -1 {
|
||||
formatStateModuleExpand(&buf, m, opts)
|
||||
} else {
|
||||
formatStateModuleSingle(&buf, m, opts)
|
||||
}
|
||||
}
|
||||
|
||||
// Write the outputs for the root module
|
||||
m := s.RootModule()
|
||||
if len(m.Outputs) > 0 {
|
||||
buf.WriteString("\nOutputs:\n\n")
|
||||
|
||||
// Sort the outputs
|
||||
ks := make([]string, 0, len(m.Outputs))
|
||||
for k, _ := range m.Outputs {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks)
|
||||
|
||||
// Output each output k/v pair
|
||||
for _, k := range ks {
|
||||
v := m.Outputs[k]
|
||||
switch output := v.Value.(type) {
|
||||
case string:
|
||||
buf.WriteString(fmt.Sprintf("%s = %s", k, output))
|
||||
buf.WriteString("\n")
|
||||
case []interface{}:
|
||||
buf.WriteString(formatListOutput("", k, output))
|
||||
buf.WriteString("\n")
|
||||
case map[string]interface{}:
|
||||
buf.WriteString(formatMapOutput("", k, output))
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return opts.Color.Color(strings.TrimSpace(buf.String()))
|
||||
}
|
||||
|
||||
func formatStateModuleExpand(
|
||||
buf *bytes.Buffer, m *terraform.ModuleState, opts *StateOpts) {
|
||||
var moduleName string
|
||||
if !m.IsRoot() {
|
||||
moduleName = fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
|
||||
}
|
||||
|
||||
// First get the names of all the resources so we can show them
|
||||
// in alphabetical order.
|
||||
names := make([]string, 0, len(m.Resources))
|
||||
for name, _ := range m.Resources {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
// Go through each resource and begin building up the output.
|
||||
for _, k := range names {
|
||||
name := k
|
||||
if moduleName != "" {
|
||||
name = moduleName + "." + name
|
||||
}
|
||||
|
||||
rs := m.Resources[k]
|
||||
is := rs.Primary
|
||||
var id string
|
||||
if is != nil {
|
||||
id = is.ID
|
||||
}
|
||||
if id == "" {
|
||||
id = "<not created>"
|
||||
}
|
||||
|
||||
taintStr := ""
|
||||
if rs.Primary != nil && rs.Primary.Tainted {
|
||||
taintStr = " (tainted)"
|
||||
}
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%s:%s\n", name, taintStr))
|
||||
buf.WriteString(fmt.Sprintf(" id = %s\n", id))
|
||||
|
||||
if is != nil {
|
||||
// Sort the attributes
|
||||
attrKeys := make([]string, 0, len(is.Attributes))
|
||||
for ak, _ := range is.Attributes {
|
||||
// Skip the id attribute since we just show the id directly
|
||||
if ak == "id" {
|
||||
continue
|
||||
}
|
||||
|
||||
attrKeys = append(attrKeys, ak)
|
||||
}
|
||||
sort.Strings(attrKeys)
|
||||
|
||||
// Output each attribute
|
||||
for _, ak := range attrKeys {
|
||||
av := is.Attributes[ak]
|
||||
buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("[reset]\n")
|
||||
}
|
||||
|
||||
func formatStateModuleSingle(
|
||||
buf *bytes.Buffer, m *terraform.ModuleState, opts *StateOpts) {
|
||||
// Header with the module name
|
||||
buf.WriteString(fmt.Sprintf("module.%s\n", strings.Join(m.Path[1:], ".")))
|
||||
|
||||
// Now just write how many resources are in here.
|
||||
buf.WriteString(fmt.Sprintf(" %d resource(s)\n", len(m.Resources)))
|
||||
}
|
||||
|
||||
func formatNestedList(indent string, outputList []interface{}) string {
|
||||
outputBuf := new(bytes.Buffer)
|
||||
outputBuf.WriteString(fmt.Sprintf("%s[", indent))
|
||||
|
||||
lastIdx := len(outputList) - 1
|
||||
|
||||
for i, value := range outputList {
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, " ", value))
|
||||
if i != lastIdx {
|
||||
outputBuf.WriteString(",")
|
||||
}
|
||||
}
|
||||
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
|
||||
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||
}
|
||||
|
||||
func formatListOutput(indent, outputName string, outputList []interface{}) string {
|
||||
keyIndent := ""
|
||||
|
||||
outputBuf := new(bytes.Buffer)
|
||||
|
||||
if outputName != "" {
|
||||
outputBuf.WriteString(fmt.Sprintf("%s%s = [", indent, outputName))
|
||||
keyIndent = " "
|
||||
}
|
||||
|
||||
lastIdx := len(outputList) - 1
|
||||
|
||||
for i, value := range outputList {
|
||||
switch typedValue := value.(type) {
|
||||
case string:
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, keyIndent, value))
|
||||
case []interface{}:
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
|
||||
formatNestedList(indent+keyIndent, typedValue)))
|
||||
case map[string]interface{}:
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
|
||||
formatNestedMap(indent+keyIndent, typedValue)))
|
||||
}
|
||||
|
||||
if lastIdx != i {
|
||||
outputBuf.WriteString(",")
|
||||
}
|
||||
}
|
||||
|
||||
if outputName != "" {
|
||||
if len(outputList) > 0 {
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
|
||||
} else {
|
||||
outputBuf.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||
}
|
||||
|
||||
func formatNestedMap(indent string, outputMap map[string]interface{}) string {
|
||||
ks := make([]string, 0, len(outputMap))
|
||||
for k, _ := range outputMap {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks)
|
||||
|
||||
outputBuf := new(bytes.Buffer)
|
||||
outputBuf.WriteString(fmt.Sprintf("%s{", indent))
|
||||
|
||||
lastIdx := len(outputMap) - 1
|
||||
for i, k := range ks {
|
||||
v := outputMap[k]
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s = %v", indent+" ", k, v))
|
||||
|
||||
if lastIdx != i {
|
||||
outputBuf.WriteString(",")
|
||||
}
|
||||
}
|
||||
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
|
||||
|
||||
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||
}
|
||||
|
||||
func formatMapOutput(indent, outputName string, outputMap map[string]interface{}) string {
|
||||
ks := make([]string, 0, len(outputMap))
|
||||
for k, _ := range outputMap {
|
||||
ks = append(ks, k)
|
||||
}
|
||||
sort.Strings(ks)
|
||||
|
||||
keyIndent := ""
|
||||
|
||||
outputBuf := new(bytes.Buffer)
|
||||
if outputName != "" {
|
||||
outputBuf.WriteString(fmt.Sprintf("%s%s = {", indent, outputName))
|
||||
keyIndent = " "
|
||||
}
|
||||
|
||||
for _, k := range ks {
|
||||
v := outputMap[k]
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s = %v", indent, keyIndent, k, v))
|
||||
}
|
||||
|
||||
if outputName != "" {
|
||||
if len(outputMap) > 0 {
|
||||
outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
|
||||
} else {
|
||||
outputBuf.WriteString("}")
|
||||
}
|
||||
}
|
||||
|
||||
return strings.TrimPrefix(outputBuf.String(), "\n")
|
||||
}
|
Loading…
Reference in New Issue