diff --git a/source/Ocl/OclSerializerOptions.cs b/source/Ocl/OclSerializerOptions.cs
index f1168c3..910f82d 100644
--- a/source/Ocl/OclSerializerOptions.cs
+++ b/source/Ocl/OclSerializerOptions.cs
@@ -9,7 +9,13 @@ namespace Octopus.Ocl
///
public class OclSerializerOptions
{
- public char IndentChar { get; set; } = ' ';
+ ///
+ /// What character is used when indenting the OCL text.
+ /// This is not settable as it is
+ /// currently not used in some places in the static Parser
+ ///
+ public char IndentChar { get; internal set; } = ' ';
+
public int IndentDepth { get; set; } = 4;
public string DefaultHeredocTag { get; set; } = "EOT";
public List Converters { get; set; } = new List();
diff --git a/source/Ocl/OclWriter.cs b/source/Ocl/OclWriter.cs
index 8acdc36..25d6269 100644
--- a/source/Ocl/OclWriter.cs
+++ b/source/Ocl/OclWriter.cs
@@ -232,7 +232,8 @@ void WriteValue(OclStringLiteral literal)
}
var isIndented = literal.Format == OclStringLiteralFormat.IndentedHeredoc;
-
+ var indentSize = 2;
+
writer.Write("<<");
if (isIndented)
writer.Write("-");
@@ -245,14 +246,15 @@ void WriteValue(OclStringLiteral literal)
writer.Write('\n');
if (isIndented)
- WriteIndent(2);
+ WriteIndent(indentSize);
writer.Write(lines[x]);
}
-
writer.WriteLine();
+ // Ident the ending tag by the same amount as the text value above.
+ // This ensures that when being read back out the parsed text line indentation can be made relative to this ending tag.
if (isIndented)
- WriteIndent(1);
+ WriteIndent(indentSize);
writer.Write(literal.HeredocTag);
}
diff --git a/source/Ocl/Parsing/HeredocParser.cs b/source/Ocl/Parsing/HeredocParser.cs
index 85a1154..9c20520 100644
--- a/source/Ocl/Parsing/HeredocParser.cs
+++ b/source/Ocl/Parsing/HeredocParser.cs
@@ -54,7 +54,7 @@ from start in Start
from lines in Line.Except(endParser).Many()
from end in endParser
let trimmed = TrimCarriageReturn(lines)
- let unindented = Unindent(trimmed.lines, start.format)
+ let unindented = Unindent(trimmed.lines, end, start.format)
let joined = string.Join(trimmed.lineSeparator, unindented)
select new OclStringLiteral(joined, start.format)
{
@@ -63,11 +63,11 @@ from end in endParser
internal static Parser End(string tag)
{
- var endLine = from leadingWhitespace in ParserCommon.WhitespaceExceptNewline.Many()
+ var endLine = from leadingWhitespace in ParserCommon.WhitespaceExceptNewline.Many().Text()
from endtag in Parse.String(tag).Text()
- from trailingWhitespace in ParserCommon.WhitespaceExceptNewline.Many()
- select endtag;
-
+ from trailingWhitespace in ParserCommon.WhitespaceExceptNewline.Many().Optional()
+ select leadingWhitespace + endtag;
+
var terminateWithNewline = from endtag in endLine
from newline in ParserCommon.NewLine
select endtag;
@@ -86,7 +86,7 @@ from newline in ParserCommon.NewLine
return (lineSeparator, trimmed);
}
- public static IEnumerable Unindent(IReadOnlyList lines, OclStringLiteralFormat format)
+ public static IEnumerable Unindent(IReadOnlyList lines, string endLine, OclStringLiteralFormat format)
{
if (format != OclStringLiteralFormat.IndentedHeredoc)
return lines;
@@ -102,8 +102,8 @@ int CountLeadingWhitespace(string input)
return int.MaxValue;
}
- var unindentBy = lines
- .Min(CountLeadingWhitespace);
+ var endTagLeadingWhitespace = CountLeadingWhitespace(endLine);
+ var unindentBy = Math.Min(endTagLeadingWhitespace, lines.Min(CountLeadingWhitespace));
return lines.Select(l => l.Length <= unindentBy ? "" : l.Substring(unindentBy));
}
diff --git a/source/Tests/Parsing/HeredocParsingFixture.cs b/source/Tests/Parsing/HeredocParsingFixture.cs
index d2f0bba..2477741 100644
--- a/source/Tests/Parsing/HeredocParsingFixture.cs
+++ b/source/Tests/Parsing/HeredocParsingFixture.cs
@@ -36,15 +36,15 @@ public void StartInvalid(string input)
.Should()
.BeFalse();
- [TestCase("ZZZ\n")]
- [TestCase("ZZZ")]
- [TestCase("\t\tZZZ\t\t\n")]
- [TestCase("ZZZ\nFoo\n")]
- public void End(string input)
+ [TestCase("ZZZ", "ZZZ", Reason = "Exact Match")]
+ [TestCase("ZZZ\n", "ZZZ", Reason = "Trailing line breaks removed")]
+ [TestCase("\t\tZZZ\t\t\n", "\t\tZZZ", Reason = "Leading whitespace returned for indent count")]
+ [TestCase("ZZZ\nFoo\n", "ZZZ", Reason = "Proceeding nodes ignored")]
+ public void End(string input, string expected)
=> HeredocParser.End("ZZZ")
.Parse(input)
.Should()
- .Be("ZZZ");
+ .Be(expected);
[TestCase("ZZZ A\n")]
[TestCase("A ZZZ\n")]
@@ -71,12 +71,12 @@ public void Heredoc(string input, string expected)
)
);
- [TestCase("<<-ZZZ\nZZZ", "", Description = "IndentedHeredoc - Empty")]
- [TestCase("<<-ZZZ\n \n \nZZZ", "\n", Description = "IndentedHeredoc - Whitespace")]
- [TestCase("<<-ZZZ\n A\n B\nZZZ", "A\nB", Description = "IndentedHeredoc - NoExtraIndent")]
- [TestCase("<<-ZZZ\n A\n B\n ZZZ", "A\n B", Description = "IndentedHeredoc - ExtraIndent")]
- [TestCase("<<-ZZZ\n A ZZZ\n ZZZ B\nZZZ", "A ZZZ\n ZZZ B", Description = "IndentedHeredoc - End Marker In Text")]
- [TestCase("<<-ZZZ\n A\n B\nZZZ", "A\n B", Description = "IndentedHeredoc - End marker does not affect indent")]
+ [TestCase("<<-ZZZ\nZZZ", "", Reason = "IndentedHeredoc - Empty")]
+ [TestCase("<<-ZZZ\n \n \nZZZ", " \n ", Reason = "IndentedHeredoc - Whitespace Preserved")]
+ [TestCase("<<-ZZZ\n \n \n ZZZ", "\n", Reason = "IndentedHeredoc - Whitespace Removed")]
+ [TestCase("<<-ZZZ\n A\n B\n ZZZ", "A\n B", Reason = "IndentedHeredoc - Offset By End Indent")]
+ [TestCase("<<-ZZZ\n A ZZZ\nZZZ B\nZZZ", " A ZZZ\nZZZ B", Reason = "IndentedHeredoc - End Marker In Text")]
+ [TestCase("<<-ZZZ\n A\nB\n ZZZ", " A\nB", Reason = "IndentedHeredoc - Min Across All Lines")]
public void IndentedHeredoc(string input, string expected)
=> OclParser.Execute("Foo = " + input.ToUnixLineEndings())
.Should()
@@ -124,7 +124,7 @@ public void LineEndingsArePreserved(string lineEnding)
[TestCase(" A", "B")]
public void UnindentNoChange(params string[] input)
{
- var result = HeredocParser.Unindent(input, OclStringLiteralFormat.IndentedHeredoc);
+ var result = HeredocParser.Unindent(input, "", OclStringLiteralFormat.IndentedHeredoc);
result.Should().BeEquivalentTo(input);
}
@@ -141,7 +141,7 @@ public void UnindentNoChange(params string[] input)
public void UnindentBy4Chars(params string[] input)
{
var expected = input.Select(i => i.Length < 4 ? "" : i.Substring(4));
- var result = HeredocParser.Unindent(input, OclStringLiteralFormat.IndentedHeredoc);
+ var result = HeredocParser.Unindent(input, "", OclStringLiteralFormat.IndentedHeredoc);
result.Should().BeEquivalentTo(expected);
}
}
diff --git a/source/Tests/RealLifeScenario/RealLifeScenarioFixture.MostFieldsWithNonDefaults.approved.txt b/source/Tests/RealLifeScenario/RealLifeScenarioFixture.MostFieldsWithNonDefaults.approved.txt
index 9270f08..cdfc441 100644
--- a/source/Tests/RealLifeScenario/RealLifeScenarioFixture.MostFieldsWithNonDefaults.approved.txt
+++ b/source/Tests/RealLifeScenario/RealLifeScenarioFixture.MostFieldsWithNonDefaults.approved.txt
@@ -3,7 +3,7 @@ description = <<-EOT
This is a
multiline a description with trailing newline
- EOT
+ EOT
environment_scope = "Specified"
environments = ["Production", "Development"]
multi_tenancy_mode = "TenantedOrUntenanted"
diff --git a/source/Tests/RealLifeScenario/RealLifeScenarioFixture.ScriptAction.approved.txt b/source/Tests/RealLifeScenario/RealLifeScenarioFixture.ScriptAction.approved.txt
index fac14f4..3133754 100644
--- a/source/Tests/RealLifeScenario/RealLifeScenarioFixture.ScriptAction.approved.txt
+++ b/source/Tests/RealLifeScenario/RealLifeScenarioFixture.ScriptAction.approved.txt
@@ -45,7 +45,7 @@ steps "Backup the Database" {
write-output $entryResult | ft
- EOT
+ EOT
Octopus.Action.Script.ScriptSource = "Inline"
Octopus.Action.Script.Syntax = "PowerShell"
}
diff --git a/source/Tests/ToString/OclWriterFixture.cs b/source/Tests/ToString/OclWriterFixture.cs
index 3a565c8..4c18163 100644
--- a/source/Tests/ToString/OclWriterFixture.cs
+++ b/source/Tests/ToString/OclWriterFixture.cs
@@ -113,6 +113,7 @@ public void Heredoc()
.Be(expected.ToUnixLineEndings());
}
+
[Test]
public void HeredocIndented()
{
@@ -126,9 +127,9 @@ public void HeredocIndented()
MyAttr = <<-EOT
a
b
- EOT
+ EOT
}";
-
+
Execute(w => w.Write(block))
.Should()
.Be(expected.ToUnixLineEndings());
@@ -145,12 +146,44 @@ public void MultilineStringsUseHeredocAndTheHeredocIdentifierFromOptions()
var expected = @"MyAttr = <<-YYY
a
b
- YYY";
+ YYY";
Execute(w => w.Write(new OclAttribute("MyAttr", "a\nb")), options)
.Should()
.Be(expected.ToUnixLineEndings());
}
+
+ [Test]
+ [TestCase(@"Hello
+World", @"blah = <<-EOT
+ Hello
+ World
+ EOT", Description = "No Indentation")]
+ [TestCase(@" Hello
+ World", @"blah = <<-EOT
+ Hello
+ World
+ EOT", Description = "Double Indent Preserved")]
+ [TestCase(@"Hello
+ World", @"blah = <<-EOT
+ Hello
+ World
+ EOT", Description = "Single Property Indented")]
+ public void MultilineStringsSupportHeredocLineFormat(string propertyText, string expectedOcl)
+ {
+ var serializer = new OclSerializer(new OclSerializerOptions());
+
+ var serializedOcl = serializer.Serialize(new Foo() { Blah = propertyText });
+ serializedOcl.Should().Be(expectedOcl);
+
+ var deserializedFoo = serializer.Deserialize(expectedOcl);
+ deserializedFoo.Blah.Should().Be(propertyText);
+ }
+
+ class Foo
+ {
+ public string? Blah { get; set; }
+ }
[Test]
public void WriteBlockEmpty()