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()