Repository: martinohmann/hcl-rs
Pull request #157 (2023-01-24 08:03:27)

fix(parser): automatically dedent indented heredocs


Patch:
diff --git a/src/expr/template_expr.rs b/src/expr/template_expr.rs
index bedc6d0..10459d6 100644
--- a/src/expr/template_expr.rs
+++ b/src/expr/template_expr.rs
@@ -1,7 +1,6 @@
-use crate::util::{dedent, try_unescape};
+use crate::util::try_unescape;
 use crate::{Error, Identifier, Result};
 use serde::Deserialize;
-use std::borrow::Cow;
 use std::fmt;
 use std::str::FromStr;
 
@@ -23,11 +22,11 @@ pub enum TemplateExpr {
 }
 
 impl TemplateExpr {
-    /// Returns the template as a copy-on-write string.
-    pub(crate) fn to_cow_str(&self) -> Cow<str> {
+    /// Returns the template as a `&str`.
+    pub(crate) fn as_str(&self) -> &str {
         match self {
-            TemplateExpr::QuotedString(s) => Cow::Borrowed(s),
-            TemplateExpr::Heredoc(heredoc) => heredoc.to_cow_str(),
+            TemplateExpr::QuotedString(s) => s,
+            TemplateExpr::Heredoc(heredoc) => &heredoc.template,
         }
     }
 }
@@ -52,8 +51,7 @@ impl From<Heredoc> for TemplateExpr {
 
 impl fmt::Display for TemplateExpr {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let s = self.to_cow_str();
-        f.write_str(&try_unescape(&s))
+        f.write_str(&try_unescape(self.as_str()))
     }
 }
 
@@ -87,14 +85,6 @@ impl Heredoc {
         self.strip = strip;
         self
     }
-
-    /// Returns the template as a copy-on-write string.
-    pub(crate) fn to_cow_str(&self) -> Cow<str> {
-        match self.strip {
-            HeredocStripMode::None => Cow::Borrowed(&self.template),
-            HeredocStripMode::Indent => dedent(&self.template),
-        }
-    }
 }
 
 /// The strip behaviour for the template contained in the heredoc.
diff --git a/src/parser/expr.rs b/src/parser/expr.rs
index e0df613..9efa0f5 100644
--- a/src/parser/expr.rs
+++ b/src/parser/expr.rs
@@ -1,8 +1,11 @@
 use super::*;
-use crate::expr::{
-    BinaryOp, Conditional, Expression, ForExpr, FuncCall, FuncCallBuilder, Heredoc,
-    HeredocStripMode, Object, ObjectKey, Operation, TemplateExpr, Traversal, TraversalOperator,
-    UnaryOp, UnaryOperator, Variable,
+use crate::{
+    expr::{
+        BinaryOp, Conditional, Expression, ForExpr, FuncCall, FuncCallBuilder, Heredoc,
+        HeredocStripMode, Object, ObjectKey, Operation, TemplateExpr, Traversal, TraversalOperator,
+        UnaryOp, UnaryOperator, Variable,
+    },
+    util::dedent,
 };
 
 pub fn expression(pair: Pair<Rule>) -> Result<Expression> {
@@ -247,7 +250,13 @@ fn heredoc(pair: Pair<Rule>) -> Heredoc {
     };
 
     let delimiter = ident(pairs.next().unwrap());
-    let mut template = string(pairs.next().unwrap());
+
+    let template = pairs.next().unwrap();
+
+    let mut template = match strip {
+        HeredocStripMode::None => string(template),
+        HeredocStripMode::Indent => dedent(template.as_str()).to_string(),
+    };
 
     // Append the trailing newline here. This is easier than doing this in the grammar.
     template.push('\n');
diff --git a/src/template.rs b/src/template.rs
index f3bbb90..331630e 100644
--- a/src/template.rs
+++ b/src/template.rs
@@ -116,7 +116,7 @@ impl Template {
     /// Returns an error if the parsing of raw string templates fails or if the template expression
     /// contains string literals with invalid escape sequences.
     pub fn from_expr(expr: &TemplateExpr) -> Result<Self> {
-        Template::from_str(&expr.to_cow_str())
+        Template::from_str(expr.as_str())
     }
 
     /// Returns a reference to the template elements.


Test patch:
diff --git a/src/structure/tests.rs b/src/structure/tests.rs
index 2fdd28a..81607ef 100644
--- a/src/structure/tests.rs
+++ b/src/structure/tests.rs
@@ -33,7 +33,7 @@ fn body_into_value() {
             TemplateExpr::Heredoc(
                 Heredoc::new(
                     Identifier::unchecked("EOS"),
-                    "  foo \\\n  bar ${baz}\\\\backslash",
+                    "foo \\\nbar ${baz}\\\\backslash",
                 )
                 .with_strip_mode(HeredocStripMode::Indent),
             ),
diff --git a/tests/de.rs b/tests/de.rs
index 5abd3b0..80b432f 100644
--- a/tests/de.rs
+++ b/tests/de.rs
@@ -361,7 +361,7 @@ fn unescape_strings() {
                     TemplateExpr::Heredoc(
                         Heredoc::new(
                             Identifier::unchecked("EOS"),
-                            "            heredoc template with \\\n            escaped newline and \\\\backslash is not unescaped yet\n"
+                            "heredoc template with \\\nescaped newline and \\\\backslash is not unescaped yet\n"
                         )
                         .with_strip_mode(HeredocStripMode::Indent)
                     )
diff --git a/tests/regressions.rs b/tests/regressions.rs
index e7917d2..5d8e459 100644
--- a/tests/regressions.rs
+++ b/tests/regressions.rs
@@ -129,7 +129,7 @@ fn issue_137() {
         cond_string: String::from("${var ? true : false}"),
         nested: Nested {
             template_expr: TemplateExpr::from("${some_var}"),
-            heredoc: Heredoc::new(Identifier::unchecked("EOS"), "    ${some_var}\n")
+            heredoc: Heredoc::new(Identifier::unchecked("EOS"), "${some_var}\n")
                 .with_strip_mode(HeredocStripMode::Indent),
         },
     };