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