repl: Display multi-line strings as heredocs
The console and output formatter previously displayed multi-line strings with escaped newlines, e.g. `"hello\nworld\n"`. While this is a valid way to write the HCL string, it is not as common or as readable as using the heredoc syntax, e.g. <<EOF hello world EOF This commit adds heredoc detection and display to this formatter, including support for indented heredocs for nested multi-line strings. This change affects the apply, console, and output sub-commands.
This commit is contained in:
parent
111825da45
commit
4e7607deb5
|
@ -46,8 +46,9 @@ func FormatValue(v cty.Value, indent int) string {
|
|||
case ty.IsPrimitiveType():
|
||||
switch ty {
|
||||
case cty.String:
|
||||
// FIXME: If it's a multi-line string, better to render it using
|
||||
// HEREDOC-style syntax.
|
||||
if formatted, isMultiline := formatMultilineString(v, indent); isMultiline {
|
||||
return formatted
|
||||
}
|
||||
return strconv.Quote(v.AsString())
|
||||
case cty.Number:
|
||||
bf := v.AsBigFloat()
|
||||
|
@ -75,6 +76,56 @@ func FormatValue(v cty.Value, indent int) string {
|
|||
return fmt.Sprintf("%#v", v)
|
||||
}
|
||||
|
||||
func formatMultilineString(v cty.Value, indent int) (string, bool) {
|
||||
str := v.AsString()
|
||||
lines := strings.Split(str, "\n")
|
||||
if len(lines) < 2 {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// If the value is indented, we use the indented form of heredoc for readability.
|
||||
operator := "<<"
|
||||
if indent > 0 {
|
||||
operator = "<<-"
|
||||
}
|
||||
|
||||
// Default delimiter is "End Of Text" by convention
|
||||
delimiter := "EOT"
|
||||
|
||||
OUTER:
|
||||
for {
|
||||
// Check if any of the lines are in conflict with the delimiter. The
|
||||
// parser allows leading and trailing whitespace, so we must remove it
|
||||
// before comparison.
|
||||
for _, line := range lines {
|
||||
// If the delimiter matches a line, extend it and start again
|
||||
if strings.TrimSpace(line) == delimiter {
|
||||
delimiter = delimiter + "_"
|
||||
continue OUTER
|
||||
}
|
||||
}
|
||||
|
||||
// None of the lines match the delimiter, so we're ready
|
||||
break
|
||||
}
|
||||
|
||||
// Write the heredoc, with indentation as appropriate.
|
||||
var buf strings.Builder
|
||||
|
||||
buf.WriteString(operator)
|
||||
buf.WriteString(delimiter)
|
||||
for _, line := range lines {
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
buf.WriteString(line)
|
||||
}
|
||||
buf.WriteByte('\n')
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
buf.WriteString(delimiter)
|
||||
|
||||
return buf.String(), true
|
||||
}
|
||||
|
||||
func formatMappingValue(v cty.Value, indent int) string {
|
||||
var buf strings.Builder
|
||||
count := 0
|
||||
|
|
|
@ -58,7 +58,28 @@ func TestFormatValue(t *testing.T) {
|
|||
},
|
||||
{
|
||||
cty.StringVal("hello\nworld"),
|
||||
`"hello\nworld"`, // Ideally we'd use heredoc syntax here for better readability, but we don't yet
|
||||
`<<EOT
|
||||
hello
|
||||
world
|
||||
EOT`,
|
||||
},
|
||||
{
|
||||
cty.StringVal("EOR\nEOS\nEOT\nEOU"),
|
||||
`<<EOT_
|
||||
EOR
|
||||
EOS
|
||||
EOT
|
||||
EOU
|
||||
EOT_`,
|
||||
},
|
||||
{
|
||||
cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("boop\nbeep")}),
|
||||
`{
|
||||
"foo" = <<-EOT
|
||||
boop
|
||||
beep
|
||||
EOT
|
||||
}`,
|
||||
},
|
||||
{
|
||||
cty.Zero,
|
||||
|
|
Loading…
Reference in New Issue