Remove hanging indent on HEREDOCs with <<- prefix

This commit adds support for removing a hanging indent from HEREDOC
token contents if the marker is prefixed with <<-. For example, given
the HCL definition:

my_long_var_name = <<-EOF
                   {
                       "key": "value"
                   }
                   EOF

The value of the HEREDOC will be:

{
    "key": "value"
}

This is useful for use cases where indentation or leading whitespace
is important.

The rule applied is that the prefix on the terminating marker will be
removed from each each line in the HEREDOC, providing all the lines have
that prefix (i.e. every line is at least as indented as the terminating
marker, and using the same mechanism of tabs vs spaces).
This commit is contained in:
James Nugent 2016-02-27 15:10:56 -05:00
parent 3ad5dd62fd
commit f5480db646
4 changed files with 63 additions and 10 deletions

View File

@ -94,7 +94,12 @@ func TestDecode_interface(t *testing.T) {
{
"multiline_indented.hcl",
false,
map[string]interface{}{"foo": " bar\n baz\n"},
map[string]interface{}{"foo": " bar\n baz\n"},
},
{
"multiline_no_hanging_indent.hcl",
false,
map[string]interface{}{"foo": " baz\n bar\n foo\n"},
},
{
"multiline_no_eof.hcl",

View File

@ -142,14 +142,7 @@ func (t Token) Value() interface{} {
case IDENT:
return t.Text
case HEREDOC:
// We need to find the end of the marker
idx := strings.IndexByte(t.Text, '\n')
if idx == -1 {
panic("heredoc doesn't contain newline")
}
// Trim any trailing whitespace from the start of the marker
return strings.TrimRight(string(t.Text[idx+1:len(t.Text)-idx+1]), " \t")
return unindentHeredoc(t.Text)
case STRING:
// Determine the Unquote method to use. If it came from JSON,
// then we need to use the built-in unquote since we have to
@ -169,3 +162,53 @@ func (t Token) Value() interface{} {
panic(fmt.Sprintf("unimplemented Value for type: %s", t.Type))
}
}
// unindentHeredoc returns the string content of a HEREDOC if it is started with <<
// and the content of a HEREDOC with the hanging indent removed if it is started with
// a <<-, and the terminating line is at least as indented as the least indented line.
func unindentHeredoc(heredoc string) string {
// We need to find the end of the marker
idx := strings.IndexByte(heredoc, '\n')
if idx == -1 {
panic("heredoc doesn't contain newline")
}
unindent := heredoc[2] == '-'
// We can optimize if the heredoc isn't marked for indentation
if !unindent {
return string(heredoc[idx+1 : len(heredoc)-idx+1])
}
// We need to unindent each line based on the indentation level of the marker
lines := strings.Split(string(heredoc[idx+1:len(heredoc)-idx+2]), "\n")
whitespacePrefix := lines[len(lines)-1]
isIndented := true
for _, v := range lines {
if strings.HasPrefix(v, whitespacePrefix) {
continue
}
isIndented = false
break
}
// If all lines are not at least as indented as the terminating mark, return the
// heredoc as is, but trim the leading space from the marker on the final line.
if !isIndented {
return strings.TrimRight(string(heredoc[idx+1:len(heredoc)-idx+1]), " \t")
}
unindentedLines := make([]string, len(lines))
for k, v := range lines {
if k == len(lines)-1 {
unindentedLines[k] = ""
break
}
unindentedLines[k] = strings.TrimPrefix(v, whitespacePrefix)
}
return strings.Join(unindentedLines, "\n")
}

View File

@ -1,4 +1,4 @@
foo = <<-EOF
bar
baz
EOF
EOF

View File

@ -0,0 +1,5 @@
foo = <<-EOF
baz
bar
foo
EOF