github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/repl/format.go (about) 1 package repl 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 8 "github.com/hashicorp/terraform/internal/lang/marks" 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 // FormatValue formats a value in a way that resembles Terraform language syntax 13 // and uses the type conversion functions where necessary to indicate exactly 14 // what type it is given, so that equality test failures can be quickly 15 // understood. 16 func FormatValue(v cty.Value, indent int) string { 17 if !v.IsKnown() { 18 return "(known after apply)" 19 } 20 if v.Type().Equals(cty.String) && v.HasMark(marks.Raw) { 21 raw, _ := v.Unmark() 22 return raw.AsString() 23 } 24 if v.HasMark(marks.Sensitive) { 25 return "(sensitive)" 26 } 27 if v.IsNull() { 28 ty := v.Type() 29 switch { 30 case ty == cty.DynamicPseudoType: 31 return "null" 32 case ty == cty.String: 33 return "tostring(null)" 34 case ty == cty.Number: 35 return "tonumber(null)" 36 case ty == cty.Bool: 37 return "tobool(null)" 38 case ty.IsListType(): 39 return fmt.Sprintf("tolist(null) /* of %s */", ty.ElementType().FriendlyName()) 40 case ty.IsSetType(): 41 return fmt.Sprintf("toset(null) /* of %s */", ty.ElementType().FriendlyName()) 42 case ty.IsMapType(): 43 return fmt.Sprintf("tomap(null) /* of %s */", ty.ElementType().FriendlyName()) 44 default: 45 return fmt.Sprintf("null /* %s */", ty.FriendlyName()) 46 } 47 } 48 49 ty := v.Type() 50 switch { 51 case ty.IsPrimitiveType(): 52 switch ty { 53 case cty.String: 54 if formatted, isMultiline := formatMultilineString(v, indent); isMultiline { 55 return formatted 56 } 57 return strconv.Quote(v.AsString()) 58 case cty.Number: 59 bf := v.AsBigFloat() 60 return bf.Text('f', -1) 61 case cty.Bool: 62 if v.True() { 63 return "true" 64 } else { 65 return "false" 66 } 67 } 68 case ty.IsObjectType(): 69 return formatMappingValue(v, indent) 70 case ty.IsTupleType(): 71 return formatSequenceValue(v, indent) 72 case ty.IsListType(): 73 return fmt.Sprintf("tolist(%s)", formatSequenceValue(v, indent)) 74 case ty.IsSetType(): 75 return fmt.Sprintf("toset(%s)", formatSequenceValue(v, indent)) 76 case ty.IsMapType(): 77 return fmt.Sprintf("tomap(%s)", formatMappingValue(v, indent)) 78 } 79 80 // Should never get here because there are no other types 81 return fmt.Sprintf("%#v", v) 82 } 83 84 func formatMultilineString(v cty.Value, indent int) (string, bool) { 85 str := v.AsString() 86 lines := strings.Split(str, "\n") 87 if len(lines) < 2 { 88 return "", false 89 } 90 91 // If the value is indented, we use the indented form of heredoc for readability. 92 operator := "<<" 93 if indent > 0 { 94 operator = "<<-" 95 } 96 97 // Default delimiter is "End Of Text" by convention 98 delimiter := "EOT" 99 100 OUTER: 101 for { 102 // Check if any of the lines are in conflict with the delimiter. The 103 // parser allows leading and trailing whitespace, so we must remove it 104 // before comparison. 105 for _, line := range lines { 106 // If the delimiter matches a line, extend it and start again 107 if strings.TrimSpace(line) == delimiter { 108 delimiter = delimiter + "_" 109 continue OUTER 110 } 111 } 112 113 // None of the lines match the delimiter, so we're ready 114 break 115 } 116 117 // Write the heredoc, with indentation as appropriate. 118 var buf strings.Builder 119 120 buf.WriteString(operator) 121 buf.WriteString(delimiter) 122 for _, line := range lines { 123 buf.WriteByte('\n') 124 buf.WriteString(strings.Repeat(" ", indent)) 125 buf.WriteString(line) 126 } 127 buf.WriteByte('\n') 128 buf.WriteString(strings.Repeat(" ", indent)) 129 buf.WriteString(delimiter) 130 131 return buf.String(), true 132 } 133 134 func formatMappingValue(v cty.Value, indent int) string { 135 var buf strings.Builder 136 count := 0 137 buf.WriteByte('{') 138 indent += 2 139 for it := v.ElementIterator(); it.Next(); { 140 count++ 141 k, v := it.Element() 142 buf.WriteByte('\n') 143 buf.WriteString(strings.Repeat(" ", indent)) 144 buf.WriteString(FormatValue(k, indent)) 145 buf.WriteString(" = ") 146 buf.WriteString(FormatValue(v, indent)) 147 } 148 indent -= 2 149 if count > 0 { 150 buf.WriteByte('\n') 151 buf.WriteString(strings.Repeat(" ", indent)) 152 } 153 buf.WriteByte('}') 154 return buf.String() 155 } 156 157 func formatSequenceValue(v cty.Value, indent int) string { 158 var buf strings.Builder 159 count := 0 160 buf.WriteByte('[') 161 indent += 2 162 for it := v.ElementIterator(); it.Next(); { 163 count++ 164 _, v := it.Element() 165 buf.WriteByte('\n') 166 buf.WriteString(strings.Repeat(" ", indent)) 167 buf.WriteString(FormatValue(v, indent)) 168 buf.WriteByte(',') 169 } 170 indent -= 2 171 if count > 0 { 172 buf.WriteByte('\n') 173 buf.WriteString(strings.Repeat(" ", indent)) 174 } 175 buf.WriteByte(']') 176 return buf.String() 177 }