From f5480db646fc904bd29f10187279560131c0d639 Mon Sep 17 00:00:00 2001 From: James Nugent Date: Sat, 27 Feb 2016 15:10:56 -0500 Subject: [PATCH] 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). --- decoder_test.go | 7 ++- hcl/token/token.go | 59 ++++++++++++++++--- test-fixtures/multiline_indented.hcl | 2 +- test-fixtures/multiline_no_hanging_indent.hcl | 5 ++ 4 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 test-fixtures/multiline_no_hanging_indent.hcl diff --git a/decoder_test.go b/decoder_test.go index b5d17de..a8e7085 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -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", diff --git a/hcl/token/token.go b/hcl/token/token.go index 58868c0..6e99498 100644 --- a/hcl/token/token.go +++ b/hcl/token/token.go @@ -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") +} diff --git a/test-fixtures/multiline_indented.hcl b/test-fixtures/multiline_indented.hcl index 8ac2ecc..f1d7a84 100644 --- a/test-fixtures/multiline_indented.hcl +++ b/test-fixtures/multiline_indented.hcl @@ -1,4 +1,4 @@ foo = <<-EOF bar baz - EOF + EOF diff --git a/test-fixtures/multiline_no_hanging_indent.hcl b/test-fixtures/multiline_no_hanging_indent.hcl new file mode 100644 index 0000000..c4331ee --- /dev/null +++ b/test-fixtures/multiline_no_hanging_indent.hcl @@ -0,0 +1,5 @@ +foo = <<-EOF + baz + bar + foo + EOF