diff --git a/build/Build.cs b/build/Build.cs index 9917298..2ab19f6 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -22,18 +22,24 @@ [ShutdownDotNetAfterServerBuild] class Build : NukeBuild { - [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] - public Configuration Configuration => IsLocalBuild ? Configuration.Debug : Configuration.Release; + [Parameter( + "Whether to auto-detect the branch name - this is okay for a local build, but should not be used under CI.")] + readonly bool AutoDetectBranch = IsLocalBuild; - [Parameter("Branch name for OctoVersion to use to calculate the version number. Can be set via the environment variable OCTOVERSION_CurrentBranch.", Name = "OCTOVERSION_CurrentBranch")] + [Parameter( + "Branch name for OctoVersion to use to calculate the version number. Can be set via the environment variable OCTOVERSION_CurrentBranch.", + Name = "OCTOVERSION_CurrentBranch")] readonly string BranchName; - [Parameter("Whether to auto-detect the branch name - this is okay for a local build, but should not be used under CI.")] - readonly bool AutoDetectBranch = IsLocalBuild; - [OctoVersion(UpdateBuildNumber = true, BranchParameter = nameof(BranchName), AutoDetectBranchParameter = nameof(AutoDetectBranch), Framework = "net6.0")] + + [OctoVersion(UpdateBuildNumber = true, BranchParameter = nameof(BranchName), + AutoDetectBranchParameter = nameof(AutoDetectBranch), Framework = "net6.0")] readonly OctoVersionInfo OctoVersionInfo; [Solution] readonly Solution Solution; + [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] + public Configuration Configuration => IsLocalBuild ? Configuration.Debug : Configuration.Release; + AbsolutePath SourceDirectory => RootDirectory / "source"; AbsolutePath TestsDirectory => RootDirectory / "tests"; AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; @@ -48,6 +54,7 @@ class Build : NukeBuild EnsureCleanDirectory(ArtifactsDirectory); EnsureCleanDirectory(PublishDirectory); }); + Target Restore => _ => _ .DependsOn(Clean) .Executes(() => @@ -55,6 +62,7 @@ class Build : NukeBuild DotNetRestore(s => s .SetProjectFile(Solution)); }); + Target Compile => _ => _ .DependsOn(Restore) .Executes(() => @@ -65,6 +73,7 @@ class Build : NukeBuild .SetVersion(OctoVersionInfo.FullSemVer) .EnableNoRestore()); }); + Target Test => _ => _ .DependsOn(Compile) .Executes(() => @@ -93,7 +102,8 @@ class Build : NukeBuild .AddProperty("Version", OctoVersionInfo.FullSemVer) ); - TeamCity.Instance?.PublishArtifacts(ArtifactsDirectory / $"Octopus.CoreParsers.Hcl.{OctoVersionInfo.FullSemVer}.nupkg"); + TeamCity.Instance?.PublishArtifacts(ArtifactsDirectory / + $"Octopus.CoreParsers.Hcl.{OctoVersionInfo.FullSemVer}.nupkg"); }); Target CopyToLocalPackages => _ => _ diff --git a/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/Hcl2TemplateParserTest.cs b/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/Hcl2TemplateParserTest.cs index c490cd5..e1f80dc 100644 --- a/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/Hcl2TemplateParserTest.cs +++ b/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/Hcl2TemplateParserTest.cs @@ -3,528 +3,527 @@ using NUnit.Framework; using Sprache; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +[TestFixture] +public class Hcl2TemplateParserTest : TerraformTemplateLoader { - [TestFixture] - public class Hcl2TemplateParserTest : TerraformTemplateLoader - { - private const string HCL2TemplateSamples = "HCL2TemplateSamples"; - - [TestCase("var.region == \"\"")] - [TestCase("var.region == blah")] - [TestCase("var.region == blah + 3 - 2 * 1")] - [TestCase("var.manual_deploy_enabled ? \"STOP_DEPLOYMENT\" : \"CONTINUE_DEPLOYMENT\"")] - [TestCase("\"a\" == \"a\"")] - [TestCase("(a + b =\n c) +\n ddd ?\n \"e\" :\n \"f\"", "(a + b =\n c) + ddd ? \"e\" : \"f\"")] - [TestCase("a +\n b =\n c +\n d", "a + b = c + d")] - public void TestText(string index, string expected = null) - { - var result = HclParser.UnquotedContent.Parse(index); - result.ToString().Should().Be(expected ?? index); - } + private const string HCL2TemplateSamples = "HCL2TemplateSamples"; + + [TestCase("var.region == \"\"")] + [TestCase("var.region == blah")] + [TestCase("var.region == blah + 3 - 2 * 1")] + [TestCase("var.manual_deploy_enabled ? \"STOP_DEPLOYMENT\" : \"CONTINUE_DEPLOYMENT\"")] + [TestCase("\"a\" == \"a\"")] + [TestCase("(a + b =\n c) +\n ddd ?\n \"e\" :\n \"f\"", "(a + b =\n c) + ddd ? \"e\" : \"f\"")] + [TestCase("a +\n b =\n c +\n d", "a + b = c + d")] + public void TestText(string index, string expected = null) + { + var result = HclParser.UnquotedContent.Parse(index); + result.ToString().Should().Be(expected ?? index); + } - [TestCase("test = \"a\" == \"a\"")] - public void TestTextAssignment(string index) - { - var result = HclParser.UnquotedNameUnquotedElementProperty.Parse(index); - result.ToString().Should().Be(index); - } + [TestCase("test = \"a\" == \"a\"")] + public void TestTextAssignment(string index) + { + var result = HclParser.UnquotedNameUnquotedElementProperty.Parse(index); + result.ToString().Should().Be(index); + } - [TestCase("blah \"==\" {test = \"a\" == \"a\"}")] - [TestCase("blah \"==\" {test = \"a\"}")] - public void TestTextAssignmentInElement(string index) - { - var result = HclParser.NameValueElement.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("blah \"==\" {test = \"a\" == \"a\"}")] + [TestCase("blah \"==\" {test = \"a\"}")] + public void TestTextAssignmentInElement(string index) + { + var result = HclParser.NameValueElement.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("\"a\"", "\"a\"")] - [TestCase("\"a\" ", "\"a\"")] - public void TestStringLiteralSuccess(string index, string expected) - { - var result = HclParser.PropertyValue.Parse(index); - result.ToString().Should().Be(expected); - } + [TestCase("\"a\"", "\"a\"")] + [TestCase("\"a\" ", "\"a\"")] + public void TestStringLiteralSuccess(string index, string expected) + { + var result = HclParser.PropertyValue.Parse(index); + result.ToString().Should().Be(expected); + } - [TestCase("object({name = \"string\", age = \"number\"})")] - [TestCase("object({name = \"string\", age = object({name = \"string\", age = \"number\"})})")] - [TestCase( - "object({name = \"string\", age = object({name = \"string\", age = \"number\"}), address = tuple([\"string\", object({name = \"string\", age = \"number\"})])})")] - public void ObjectTypeTest(string index) - { - var result = HclParser.ObjectTypeProperty.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("object({name = \"string\", age = \"number\"})")] + [TestCase("object({name = \"string\", age = object({name = \"string\", age = \"number\"})})")] + [TestCase( + "object({name = \"string\", age = object({name = \"string\", age = \"number\"}), address = tuple([\"string\", object({name = \"string\", age = \"number\"})])})")] + public void ObjectTypeTest(string index) + { + var result = HclParser.ObjectTypeProperty.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("tuple([\"string\", \"number\"])")] - [TestCase( - "tuple([\"string\", object({name = \"string\", age = \"number\"}), object({name = \"string\", age = object({name = \"string\", age = \"number\"}), address = tuple([\"string\", object({name = \"string\", age = \"number\"})])})])")] - public void TypleTypeTest(string index) - { - var result = HclParser.TupleTypeProperty.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("tuple([\"string\", \"number\"])")] + [TestCase( + "tuple([\"string\", object({name = \"string\", age = \"number\"}), object({name = \"string\", age = object({name = \"string\", age = \"number\"}), address = tuple([\"string\", object({name = \"string\", age = \"number\"})])})])")] + public void TypleTypeTest(string index) + { + var result = HclParser.TupleTypeProperty.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("list(\"string\")")] - [TestCase("list(\"number\")")] - [TestCase("list(\"any\")")] - [TestCase("list(\"bool\")")] - [TestCase("list(object({name = \"string\", age = \"number\"}))")] - public void ListTypeTest(string index) - { - var result = HclParser.ListTypeProperty.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("list(\"string\")")] + [TestCase("list(\"number\")")] + [TestCase("list(\"any\")")] + [TestCase("list(\"bool\")")] + [TestCase("list(object({name = \"string\", age = \"number\"}))")] + public void ListTypeTest(string index) + { + var result = HclParser.ListTypeProperty.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("map(\"string\")")] - [TestCase("map(\"number\")")] - [TestCase("map(\"bool\")")] - [TestCase("map(\"any\")")] - [TestCase("map(object({name = \"string\", age = \"number\"}))")] - public void MapTypeTest(string index) - { - var result = HclParser.MapTypeProperty.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("map(\"string\")")] + [TestCase("map(\"number\")")] + [TestCase("map(\"bool\")")] + [TestCase("map(\"any\")")] + [TestCase("map(object({name = \"string\", age = \"number\"}))")] + public void MapTypeTest(string index) + { + var result = HclParser.MapTypeProperty.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("set(\"string\")")] - [TestCase("set(\"number\")")] - [TestCase("set(\"bool\")")] - [TestCase("set(\"any\")")] - [TestCase("set(object({name = \"string\", age = \"number\"}))")] - public void SetTypeTest(string index) - { - var result = HclParser.SetTypeProperty.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("set(\"string\")")] + [TestCase("set(\"number\")")] + [TestCase("set(\"bool\")")] + [TestCase("set(\"any\")")] + [TestCase("set(object({name = \"string\", age = \"number\"}))")] + public void SetTypeTest(string index) + { + var result = HclParser.SetTypeProperty.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("\"a\" something")] - [TestCase("\"a\" \"b\"")] - [TestCase("\"a\" == \"b\"")] - [TestCase("\"a\" ==")] - public void TestStringLiteralFailures(string index) + [TestCase("\"a\" something")] + [TestCase("\"a\" \"b\"")] + [TestCase("\"a\" == \"b\"")] + [TestCase("\"a\" ==")] + public void TestStringLiteralFailures(string index) + { + try { - try - { - var result = HclParser.PropertyValue.Parse(index); - Assert.Fail("should have not parsed"); - } - catch - { - // all ok - } + var result = HclParser.PropertyValue.Parse(index); + Assert.Fail("should have not parsed"); } - - [TestCase("test = var.manual_deploy_enabled ? \"STOP_DEPLOYMENT\" : \"CONTINUE_DEPLOYMENT\"")] - [TestCase("template = file(\"task-definitions/covid-portal.json\", \"2\", \"\")")] - [TestCase("allocation_id = aws_eip.covidportal_natgw.*.id[count.index]")] - public void TestUnquotedElementProperty(string index) + catch { - var result = HclParser.UnquotedNameUnquotedElementProperty.Parse(index); - result.ToString().Should().Be(index); + // all ok } + } - [TestCase("[var.region]")] - public void TestUnquotedList(string index) - { - var result = HclParser.ListValue.Parse(index); - result.ToString(-1).Should().Be(index); - } + [TestCase("test = var.manual_deploy_enabled ? \"STOP_DEPLOYMENT\" : \"CONTINUE_DEPLOYMENT\"")] + [TestCase("template = file(\"task-definitions/covid-portal.json\", \"2\", \"\")")] + [TestCase("allocation_id = aws_eip.covidportal_natgw.*.id[count.index]")] + public void TestUnquotedElementProperty(string index) + { + var result = HclParser.UnquotedNameUnquotedElementProperty.Parse(index); + result.ToString().Should().Be(index); + } - [TestCase("depends_on = [\n aws_s3_bucket.bucket\n]")] - public void TestListAssignment(string index) - { - var result = HclParser.ElementListProperty.Parse(index); - result.ToString().Should().Be(index); - } + [TestCase("[var.region]")] + public void TestUnquotedList(string index) + { + var result = HclParser.ListValue.Parse(index); + result.ToString(-1).Should().Be(index); + } - [TestCase("(hi)")] - [TestCase("(h(hi)i)")] - [TestCase("(h \"unbalanced in a string (\" i)")] - public void TestGroupText(string index) - { - var result = HclParser.GroupText.Parse(index); - result.Should().Be(index); + [TestCase("depends_on = [\n aws_s3_bucket.bucket\n]")] + public void TestListAssignment(string index) + { + var result = HclParser.ElementListProperty.Parse(index); + result.ToString().Should().Be(index); + } - var result2 = HclParser.UnquotedContent.Parse(index); - result2.Value.Should().Be(index); - } + [TestCase("(hi)")] + [TestCase("(h(hi)i)")] + [TestCase("(h \"unbalanced in a string (\" i)")] + public void TestGroupText(string index) + { + var result = HclParser.GroupText.Parse(index); + result.Should().Be(index); - [TestCase("{hi}")] - [TestCase("{h{hi}i}")] - [TestCase("{h \"unbalanced in a string {\" i}")] - public void TestCurlyGroupText(string index) - { - var result = HclParser.CurlyGroupText.Parse(index); - result.Should().Be(index); + var result2 = HclParser.UnquotedContent.Parse(index); + result2.Value.Should().Be(index); + } - var result2 = HclParser.UnquotedContent.Parse(index); - result2.Value.Should().Be(index); - } + [TestCase("{hi}")] + [TestCase("{h{hi}i}")] + [TestCase("{h \"unbalanced in a string {\" i}")] + public void TestCurlyGroupText(string index) + { + var result = HclParser.CurlyGroupText.Parse(index); + result.Should().Be(index); - [TestCase("[hi]")] - [TestCase("[h[hi]i]")] - [TestCase("[h \"unbalanced in a string [\" i]")] - public void TestListOrIndexText(string index) - { - var result = HclParser.ListOrIndexText.Parse(index); - result.Should().Be(index); + var result2 = HclParser.UnquotedContent.Parse(index); + result2.Value.Should().Be(index); + } - var result2 = HclParser.UnquotedContent.Parse(index); - result2.Value.Should().Be(index); - } + [TestCase("[hi]")] + [TestCase("[h[hi]i]")] + [TestCase("[h \"unbalanced in a string [\" i]")] + public void TestListOrIndexText(string index) + { + var result = HclParser.ListOrIndexText.Parse(index); + result.Should().Be(index); - [TestCase("*")] - [TestCase("/")] - [TestCase("%")] - [TestCase("+")] - [TestCase("-")] - [TestCase("<")] - [TestCase(">")] - [TestCase(">=")] - [TestCase("<=")] - [TestCase("!=")] - [TestCase("==")] - [TestCase("&&")] - [TestCase("||")] - [TestCase("?")] - [TestCase(":")] - [TestCase("=")] - public void TestLogicSymbolInLineBreaks(string input) - { - var inputWithLineBreak = "a starting string " + input + "\nsome other text"; - var result2 = HclParser.UnquotedContent.Parse(inputWithLineBreak); - result2.Value.Should().Be(inputWithLineBreak.Replace("\n", " ")); - } + var result2 = HclParser.UnquotedContent.Parse(index); + result2.Value.Should().Be(index); + } - [TestCase( - "{for l in keys(local.id_context) : title(l) => local.id_context[l] if length(local.id_context[l]) > 0}")] - [TestCase( - "[for l in keys(local.id_context) : title(l) => local.id_context[l] if length(local.id_context[l]) > 0]")] - public void TestForLoop(string index) - { - var result = HclParser.UnquotedContent.Parse(index); - result.ToString().Should().Be(index); - } + [TestCase("*")] + [TestCase("/")] + [TestCase("%")] + [TestCase("+")] + [TestCase("-")] + [TestCase("<")] + [TestCase(">")] + [TestCase(">=")] + [TestCase("<=")] + [TestCase("!=")] + [TestCase("==")] + [TestCase("&&")] + [TestCase("||")] + [TestCase("?")] + [TestCase(":")] + [TestCase("=")] + public void TestLogicSymbolInLineBreaks(string input) + { + var inputWithLineBreak = "a starting string " + input + "\nsome other text"; + var result2 = HclParser.UnquotedContent.Parse(inputWithLineBreak); + result2.Value.Should().Be(inputWithLineBreak.Replace("\n", " ")); + } - [TestCase("{foo: 2}", "{foo = 2}")] - [TestCase("{foo: 2, bar:\"a\"}", "{foo = 2, bar = \"a\"}")] - [TestCase("{foo: 2, bar:\"a\", baz = null}", "{foo = 2, bar = \"a\", baz = null}")] - public void TestObject(string index, string expected) - { - var result = HclParser.MapValue.Parse(index); - result.ToString(-1).Should().Be(expected); - } + [TestCase( + "{for l in keys(local.id_context) : title(l) => local.id_context[l] if length(local.id_context[l]) > 0}")] + [TestCase( + "[for l in keys(local.id_context) : title(l) => local.id_context[l] if length(local.id_context[l]) > 0]")] + public void TestForLoop(string index) + { + var result = HclParser.UnquotedContent.Parse(index); + result.ToString().Should().Be(index); + } - /// - /// This is how the old parser found the types of variables - /// - /// - /// - [TestCase( - "variable \"image_id\" {type = \"string\", description = \"The id of the machine image (AMI) to use for the server.\"}", - "string")] - [TestCase("variable \"availability_zone_names\" {type = \"list\", default = [\"us-west-1a\"]}", "list")] - [TestCase( - "variable \"tags\" {description = \"Tags applied to all Airflow related objects\", type = \"map\", default = {\"Project\" = \"Airflow\"}}", - "map")] - public void TestOldVariableTypes(string index, string expected) - { - var result = HclParser.HclTemplate.Parse(index); - result.Child.Children.First(child => child.Name == "type").Value.Should().Be(expected); - } + [TestCase("{foo: 2}", "{foo = 2}")] + [TestCase("{foo: 2, bar:\"a\"}", "{foo = 2, bar = \"a\"}")] + [TestCase("{foo: 2, bar:\"a\", baz = null}", "{foo = 2, bar = \"a\", baz = null}")] + public void TestObject(string index, string expected) + { + var result = HclParser.MapValue.Parse(index); + result.ToString(-1).Should().Be(expected); + } - [TestCase("variable \"availability_zone_names\" {type = list(\"string\"), default = [\"us-west-1a\"]}", - "list(\"string\")")] - [TestCase( - "variable \"tags\" {description = \"Tags applied to all Airflow related objects\", type = map(\"string\"), default = {\"Project\" = \"Airflow\"}}", - "map(\"string\")")] - public void TestNewVariableTypes(string index, string expected) - { - var result = HclParser.HclTemplate.Parse(index); - result.Child.Children.First(child => child.Name == "type").Value.Should().Be(expected); - } + /// + /// This is how the old parser found the types of variables + /// + /// + /// + [TestCase( + "variable \"image_id\" {type = \"string\", description = \"The id of the machine image (AMI) to use for the server.\"}", + "string")] + [TestCase("variable \"availability_zone_names\" {type = \"list\", default = [\"us-west-1a\"]}", "list")] + [TestCase( + "variable \"tags\" {description = \"Tags applied to all Airflow related objects\", type = \"map\", default = {\"Project\" = \"Airflow\"}}", + "map")] + public void TestOldVariableTypes(string index, string expected) + { + var result = HclParser.HclTemplate.Parse(index); + result.Child.Children.First(child => child.Name == "type").Value.Should().Be(expected); + } - [TestCase( - "variable \"engine_version\" {type = string , default = \"4.0.10\", description = \"Redis engine version\" }", - "4.0.10")] - [TestCase( - "variable \"transit_encryption_enabled\" {\ntype = bool\ndefault = true\ndescription = \"Enable TLS\"\n}", - "true")] - [TestCase( - "variable \"images\" {\ntype = map\ndefault = {\nus-east-1 = \"image-1234\"\nus-west-2 = \"image-4567\"\n}\n}", - "{us-east-1 = \"image-1234\", us-west-2 = \"image-4567\"}")] - public void TestVariableValues(string index, string expected) - { - var result = HclParser.HclTemplate.Parse(index); - result.Child.Children.First(child => child.Name == "default").Value.Should().Be(expected); - // The original HCL version 1 parser treated the "naked" option on the ToString() method as a way of getting the value. - result.Child.Children.First(child => child.Name == "default").ToString(true, -1).Should().Be(expected); - } + [TestCase("variable \"availability_zone_names\" {type = list(\"string\"), default = [\"us-west-1a\"]}", + "list(\"string\")")] + [TestCase( + "variable \"tags\" {description = \"Tags applied to all Airflow related objects\", type = map(\"string\"), default = {\"Project\" = \"Airflow\"}}", + "map(\"string\")")] + public void TestNewVariableTypes(string index, string expected) + { + var result = HclParser.HclTemplate.Parse(index); + result.Child.Children.First(child => child.Name == "type").Value.Should().Be(expected); + } + [TestCase( + "variable \"engine_version\" {type = string , default = \"4.0.10\", description = \"Redis engine version\" }", + "4.0.10")] + [TestCase( + "variable \"transit_encryption_enabled\" {\ntype = bool\ndefault = true\ndescription = \"Enable TLS\"\n}", + "true")] + [TestCase( + "variable \"images\" {\ntype = map\ndefault = {\nus-east-1 = \"image-1234\"\nus-west-2 = \"image-4567\"\n}\n}", + "{us-east-1 = \"image-1234\", us-west-2 = \"image-4567\"}")] + public void TestVariableValues(string index, string expected) + { + var result = HclParser.HclTemplate.Parse(index); + result.Child.Children.First(child => child.Name == "default").Value.Should().Be(expected); + // The original HCL version 1 parser treated the "naked" option on the ToString() method as a way of getting the value. + result.Child.Children.First(child => child.Name == "default").ToString(true, -1).Should().Be(expected); + } - [TestCase("blah = {\ntype = map\ndefault = {\nus-east-1 = \"image-1234\"\nus-west-2 = \"image-4567\"\n}\n}", - "{type = map, default = {us-east-1 = \"image-1234\", us-west-2 = \"image-4567\"}}")] - public void TestMapPropertyParsing(string index, string expected) - { - var result = HclParser.ElementMapProperty.Parse(index); - result.Value.Should().Be(expected); - result.ToString(true, -1).Should().Be(expected); - } + [TestCase("blah = {\ntype = map\ndefault = {\nus-east-1 = \"image-1234\"\nus-west-2 = \"image-4567\"\n}\n}", + "{type = map, default = {us-east-1 = \"image-1234\", us-west-2 = \"image-4567\"}}")] + public void TestMapPropertyParsing(string index, string expected) + { + var result = HclParser.ElementMapProperty.Parse(index); + result.Value.Should().Be(expected); + result.ToString(true, -1).Should().Be(expected); + } - [TestCase("{\ntype = map\ndefault = {\nus-east-1 = \"image-1234\"\nus-west-2 = \"image-4567\"\n}\n}", - "{type = map, default = {us-east-1 = \"image-1234\", us-west-2 = \"image-4567\"}}")] - public void TestMapParsing(string index, string expected) - { - var result = HclParser.MapValue.Parse(index); - result.Value.Should().Be(expected); - result.ToString(true, -1).Should().Be(expected); - } - /// - /// 100 random terraform examples found on GitHub to test the parser on. - /// - [TestCase("hcl2githubexample1.tf")] - [TestCase("hcl2githubexample2.tf")] - [TestCase("hcl2githubexample3.tf")] - [TestCase("hcl2githubexample4.tf")] - [TestCase("hcl2githubexample5.tf")] - [TestCase("hcl2githubexample6.tf")] - [TestCase("hcl2githubexample7.tf")] - [TestCase("hcl2githubexample8.tf")] - [TestCase("hcl2githubexample9.tf")] - [TestCase("hcl2githubexample10.tf")] - [TestCase("hcl2githubexample11.tf")] - [TestCase("hcl2githubexample12.tf")] - [TestCase("hcl2githubexample13.tf")] - [TestCase("hcl2githubexample14.tf")] - [TestCase("hcl2githubexample15.tf")] - [TestCase("hcl2githubexample16.tf")] - [TestCase("hcl2githubexample17.tf")] - [TestCase("hcl2githubexample18.tf")] - [TestCase("hcl2githubexample19.tf")] - [TestCase("hcl2githubexample20.tf")] - [TestCase("hcl2githubexample21.tf")] - [TestCase("hcl2githubexample22.tf")] - [TestCase("hcl2githubexample23.tf")] - [TestCase("hcl2githubexample24.tf")] - [TestCase("hcl2githubexample25.tf")] - [TestCase("hcl2githubexample26.tf")] - [TestCase("hcl2githubexample27.tf")] - [TestCase("hcl2githubexample28.tf")] - [TestCase("hcl2githubexample29.tf")] - [TestCase("hcl2githubexample30.tf")] - [TestCase("hcl2githubexample31.tf")] - [TestCase("hcl2githubexample32.tf")] - [TestCase("hcl2githubexample33.tf")] - [TestCase("hcl2githubexample34.tf")] - [TestCase("hcl2githubexample35.tf")] - [TestCase("hcl2githubexample36.tf")] - [TestCase("hcl2githubexample37.tf")] - [TestCase("hcl2githubexample38.tf")] - [TestCase("hcl2githubexample39.tf")] - [TestCase("hcl2githubexample40.tf")] - [TestCase("hcl2githubexample41.tf")] - [TestCase("hcl2githubexample42.tf")] - [TestCase("hcl2githubexample43.tf")] - [TestCase("hcl2githubexample44.tf")] - [TestCase("hcl2githubexample45.tf")] - [TestCase("hcl2githubexample46.tf")] - [TestCase("hcl2githubexample47.tf")] - [TestCase("hcl2githubexample48.tf")] - [TestCase("hcl2githubexample49.tf")] - [TestCase("hcl2githubexample50.tf")] - [TestCase("hcl2githubexample51.tf")] - [TestCase("hcl2githubexample52.tf")] - [TestCase("hcl2githubexample53.tf")] - [TestCase("hcl2githubexample54.tf")] - [TestCase("hcl2githubexample55.tf")] - [TestCase("hcl2githubexample56.tf")] - [TestCase("hcl2githubexample57.tf")] - [TestCase("hcl2githubexample58.tf")] - [TestCase("hcl2githubexample59.tf")] - [TestCase("hcl2githubexample60.tf")] - [TestCase("hcl2githubexample61.tf")] - [TestCase("hcl2githubexample62.tf")] - [TestCase("hcl2githubexample63.tf")] - [TestCase("hcl2githubexample64.tf")] - [TestCase("hcl2githubexample65.tf")] - [TestCase("hcl2githubexample66.tf")] - [TestCase("hcl2githubexample67.tf")] - [TestCase("hcl2githubexample68.tf")] - [TestCase("hcl2githubexample69.tf")] - [TestCase("hcl2githubexample70.tf")] - [TestCase("hcl2githubexample71.tf")] - [TestCase("hcl2githubexample72.tf")] - [TestCase("hcl2githubexample73.tf")] - [TestCase("hcl2githubexample74.tf")] - [TestCase("hcl2githubexample75.tf")] - [TestCase("hcl2githubexample76.tf")] - [TestCase("hcl2githubexample77.tf")] - [TestCase("hcl2githubexample78.tf")] - [TestCase("hcl2githubexample79.tf")] - [TestCase("hcl2githubexample80.tf")] - [TestCase("hcl2githubexample81.tf")] - [TestCase("hcl2githubexample82.tf")] - [TestCase("hcl2githubexample83.tf")] - [TestCase("hcl2githubexample84.tf")] - [TestCase("hcl2githubexample85.tf")] - [TestCase("hcl2githubexample86.tf")] - [TestCase("hcl2githubexample87.tf")] - [TestCase("hcl2githubexample88.tf")] - [TestCase("hcl2githubexample89.tf")] - [TestCase("hcl2githubexample90.tf")] - [TestCase("hcl2githubexample91.tf")] - [TestCase("hcl2githubexample92.tf")] - [TestCase("hcl2githubexample93.tf")] - [TestCase("hcl2githubexample94.tf")] - [TestCase("hcl2githubexample95.tf")] - [TestCase("hcl2githubexample96.tf")] - [TestCase("hcl2githubexample97.tf")] - [TestCase("hcl2githubexample98.tf")] - [TestCase("hcl2githubexample99.tf")] - [TestCase("hcl2githubexample100.tf")] - public void RandomGitHubExamples(string file) - { - var template = TerraformLoadTemplate(file, HCL2TemplateSamples); - var parsed = HclParser.HclTemplate.Parse(template); - var printed = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(printed); - var reprinted = reparsed.ToString(); - printed.Should().Be(reprinted); - } + [TestCase("{\ntype = map\ndefault = {\nus-east-1 = \"image-1234\"\nus-west-2 = \"image-4567\"\n}\n}", + "{type = map, default = {us-east-1 = \"image-1234\", us-west-2 = \"image-4567\"}}")] + public void TestMapParsing(string index, string expected) + { + var result = HclParser.MapValue.Parse(index); + result.Value.Should().Be(expected); + result.ToString(true, -1).Should().Be(expected); + } - /// - /// Examples from https://github.com/hashicorp/hcl/tree/hcl2/hclwrite/fuzz - /// - [Test] - [TestCase("attr.hcl")] - [TestCase("attr-expr.hcl")] - [TestCase("attr-literal.hcl")] - [TestCase("block-attrs.hcl")] - [TestCase("block-comment.hcl")] - [TestCase("block-empty.hcl")] - [TestCase("block-nested.hcl")] - [TestCase("complex.hcl")] - [TestCase("empty.hcl")] - [TestCase("escape-dollar.hcl")] - [TestCase("escape-newline.hcl")] - [TestCase("function-call.hcl")] - [TestCase("hash-comment.hcl")] - [TestCase("index.hcl")] - [TestCase("int.hcl")] - [TestCase("int-tmpl.hcl")] - [TestCase("just-interp.hcl")] - [TestCase("literal.hcl")] - [TestCase("lots-of-comments.hcl")] - [TestCase("slash-comment.hcl")] - [TestCase("splat-attr.hcl")] - [TestCase("splat-dot-full.hcl")] - [TestCase("splat-full.hcl")] - [TestCase("traversal-dot-index.hcl")] - [TestCase("traversal-dot-index-terminal.hcl")] - [TestCase("traversal-index.hcl")] - [TestCase("utf8.hcl")] - [TestCase("var.hcl")] - public void CorpusExamples(string file) - { - var template = TerraformLoadTemplate(file, "corpus"); - var parsed = HclParser.HclTemplate.Parse(template); - var printed = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(printed); - var reprinted = reparsed.ToString(); - printed.Should().Be(reprinted); - } + /// + /// 100 random terraform examples found on GitHub to test the parser on. + /// + [TestCase("hcl2githubexample1.tf")] + [TestCase("hcl2githubexample2.tf")] + [TestCase("hcl2githubexample3.tf")] + [TestCase("hcl2githubexample4.tf")] + [TestCase("hcl2githubexample5.tf")] + [TestCase("hcl2githubexample6.tf")] + [TestCase("hcl2githubexample7.tf")] + [TestCase("hcl2githubexample8.tf")] + [TestCase("hcl2githubexample9.tf")] + [TestCase("hcl2githubexample10.tf")] + [TestCase("hcl2githubexample11.tf")] + [TestCase("hcl2githubexample12.tf")] + [TestCase("hcl2githubexample13.tf")] + [TestCase("hcl2githubexample14.tf")] + [TestCase("hcl2githubexample15.tf")] + [TestCase("hcl2githubexample16.tf")] + [TestCase("hcl2githubexample17.tf")] + [TestCase("hcl2githubexample18.tf")] + [TestCase("hcl2githubexample19.tf")] + [TestCase("hcl2githubexample20.tf")] + [TestCase("hcl2githubexample21.tf")] + [TestCase("hcl2githubexample22.tf")] + [TestCase("hcl2githubexample23.tf")] + [TestCase("hcl2githubexample24.tf")] + [TestCase("hcl2githubexample25.tf")] + [TestCase("hcl2githubexample26.tf")] + [TestCase("hcl2githubexample27.tf")] + [TestCase("hcl2githubexample28.tf")] + [TestCase("hcl2githubexample29.tf")] + [TestCase("hcl2githubexample30.tf")] + [TestCase("hcl2githubexample31.tf")] + [TestCase("hcl2githubexample32.tf")] + [TestCase("hcl2githubexample33.tf")] + [TestCase("hcl2githubexample34.tf")] + [TestCase("hcl2githubexample35.tf")] + [TestCase("hcl2githubexample36.tf")] + [TestCase("hcl2githubexample37.tf")] + [TestCase("hcl2githubexample38.tf")] + [TestCase("hcl2githubexample39.tf")] + [TestCase("hcl2githubexample40.tf")] + [TestCase("hcl2githubexample41.tf")] + [TestCase("hcl2githubexample42.tf")] + [TestCase("hcl2githubexample43.tf")] + [TestCase("hcl2githubexample44.tf")] + [TestCase("hcl2githubexample45.tf")] + [TestCase("hcl2githubexample46.tf")] + [TestCase("hcl2githubexample47.tf")] + [TestCase("hcl2githubexample48.tf")] + [TestCase("hcl2githubexample49.tf")] + [TestCase("hcl2githubexample50.tf")] + [TestCase("hcl2githubexample51.tf")] + [TestCase("hcl2githubexample52.tf")] + [TestCase("hcl2githubexample53.tf")] + [TestCase("hcl2githubexample54.tf")] + [TestCase("hcl2githubexample55.tf")] + [TestCase("hcl2githubexample56.tf")] + [TestCase("hcl2githubexample57.tf")] + [TestCase("hcl2githubexample58.tf")] + [TestCase("hcl2githubexample59.tf")] + [TestCase("hcl2githubexample60.tf")] + [TestCase("hcl2githubexample61.tf")] + [TestCase("hcl2githubexample62.tf")] + [TestCase("hcl2githubexample63.tf")] + [TestCase("hcl2githubexample64.tf")] + [TestCase("hcl2githubexample65.tf")] + [TestCase("hcl2githubexample66.tf")] + [TestCase("hcl2githubexample67.tf")] + [TestCase("hcl2githubexample68.tf")] + [TestCase("hcl2githubexample69.tf")] + [TestCase("hcl2githubexample70.tf")] + [TestCase("hcl2githubexample71.tf")] + [TestCase("hcl2githubexample72.tf")] + [TestCase("hcl2githubexample73.tf")] + [TestCase("hcl2githubexample74.tf")] + [TestCase("hcl2githubexample75.tf")] + [TestCase("hcl2githubexample76.tf")] + [TestCase("hcl2githubexample77.tf")] + [TestCase("hcl2githubexample78.tf")] + [TestCase("hcl2githubexample79.tf")] + [TestCase("hcl2githubexample80.tf")] + [TestCase("hcl2githubexample81.tf")] + [TestCase("hcl2githubexample82.tf")] + [TestCase("hcl2githubexample83.tf")] + [TestCase("hcl2githubexample84.tf")] + [TestCase("hcl2githubexample85.tf")] + [TestCase("hcl2githubexample86.tf")] + [TestCase("hcl2githubexample87.tf")] + [TestCase("hcl2githubexample88.tf")] + [TestCase("hcl2githubexample89.tf")] + [TestCase("hcl2githubexample90.tf")] + [TestCase("hcl2githubexample91.tf")] + [TestCase("hcl2githubexample92.tf")] + [TestCase("hcl2githubexample93.tf")] + [TestCase("hcl2githubexample94.tf")] + [TestCase("hcl2githubexample95.tf")] + [TestCase("hcl2githubexample96.tf")] + [TestCase("hcl2githubexample97.tf")] + [TestCase("hcl2githubexample98.tf")] + [TestCase("hcl2githubexample99.tf")] + [TestCase("hcl2githubexample100.tf")] + public void RandomGitHubExamples(string file) + { + var template = TerraformLoadTemplate(file, HCL2TemplateSamples); + var parsed = HclParser.HclTemplate.Parse(template); + var printed = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(printed); + var reprinted = reparsed.ToString(); + printed.Should().Be(reprinted); + } - [Test] - public void ExampleFromDocs() - { - var template = TerraformLoadTemplate("hcl2examplefromdocs.tf", HCL2TemplateSamples); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(3); - } + /// + /// Examples from https://github.com/hashicorp/hcl/tree/hcl2/hclwrite/fuzz + /// + [Test] + [TestCase("attr.hcl")] + [TestCase("attr-expr.hcl")] + [TestCase("attr-literal.hcl")] + [TestCase("block-attrs.hcl")] + [TestCase("block-comment.hcl")] + [TestCase("block-empty.hcl")] + [TestCase("block-nested.hcl")] + [TestCase("complex.hcl")] + [TestCase("empty.hcl")] + [TestCase("escape-dollar.hcl")] + [TestCase("escape-newline.hcl")] + [TestCase("function-call.hcl")] + [TestCase("hash-comment.hcl")] + [TestCase("index.hcl")] + [TestCase("int.hcl")] + [TestCase("int-tmpl.hcl")] + [TestCase("just-interp.hcl")] + [TestCase("literal.hcl")] + [TestCase("lots-of-comments.hcl")] + [TestCase("slash-comment.hcl")] + [TestCase("splat-attr.hcl")] + [TestCase("splat-dot-full.hcl")] + [TestCase("splat-full.hcl")] + [TestCase("traversal-dot-index.hcl")] + [TestCase("traversal-dot-index-terminal.hcl")] + [TestCase("traversal-index.hcl")] + [TestCase("utf8.hcl")] + [TestCase("var.hcl")] + public void CorpusExamples(string file) + { + var template = TerraformLoadTemplate(file, "corpus"); + var parsed = HclParser.HclTemplate.Parse(template); + var printed = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(printed); + var reprinted = reparsed.ToString(); + printed.Should().Be(reprinted); + } - /// - /// A couple of specific examples to test the parser against. These live in files because modifying - /// line endings in strings is hard work. - /// - [Test] - [TestCase("hcl2example1.txt")] - [TestCase("hcl2example2.txt")] - [TestCase("hcl2example3.txt")] - [TestCase("hcl2example4.txt")] - [TestCase("hcl2example5.txt")] - [TestCase("hcl2example6.txt")] - [TestCase("hcl2example7.txt")] - [TestCase("hcl2example8.txt")] - [TestCase("hcl2example9.txt")] - [TestCase("hcl2example10.txt")] - [TestCase("hcl2example11.txt")] - [TestCase("hcl2example12.txt")] - [TestCase("hcl2example13.txt")] - [TestCase("hcl2example14.txt")] - [TestCase("hcl2example15.txt")] - [TestCase("hcl2example16.txt")] - [TestCase("hcl2example17.txt")] - [TestCase("hcl2example18.txt")] - [TestCase("hcl2example19.txt")] - [TestCase("hcl2example20.txt")] - public void GenericExamples(string file) - { - var template = TerraformLoadTemplate(file, HCL2TemplateSamples); - var parsed = HclParser.HclTemplate.Parse(template); - var printed = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(printed); - var reprinted = reparsed.ToString(); - printed.Should().Be(reprinted); - } + [Test] + public void ExampleFromDocs() + { + var template = TerraformLoadTemplate("hcl2examplefromdocs.tf", HCL2TemplateSamples); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(3); + } - [Test] - [TestCase("hcl2objectproperty.txt", "vpc = object({id = \"string\", cidr_block = \"string\"})")] - [TestCase("hcl2objectproperty2.txt", - "vpc = object({id = \"string\", cidr_block = \"string\", vpc = object({id = \"string\", cidr_block = \"string\"})})")] - public void ObjectProperty(string file, string result) - { - var template = TerraformLoadTemplate(file, HCL2TemplateSamples); - var parsed = HclParser.ElementTypedObjectProperty.Parse(template); - parsed.ToString(-1).Should().Be(result); - } + /// + /// A couple of specific examples to test the parser against. These live in files because modifying + /// line endings in strings is hard work. + /// + [Test] + [TestCase("hcl2example1.txt")] + [TestCase("hcl2example2.txt")] + [TestCase("hcl2example3.txt")] + [TestCase("hcl2example4.txt")] + [TestCase("hcl2example5.txt")] + [TestCase("hcl2example6.txt")] + [TestCase("hcl2example7.txt")] + [TestCase("hcl2example8.txt")] + [TestCase("hcl2example9.txt")] + [TestCase("hcl2example10.txt")] + [TestCase("hcl2example11.txt")] + [TestCase("hcl2example12.txt")] + [TestCase("hcl2example13.txt")] + [TestCase("hcl2example14.txt")] + [TestCase("hcl2example15.txt")] + [TestCase("hcl2example16.txt")] + [TestCase("hcl2example17.txt")] + [TestCase("hcl2example18.txt")] + [TestCase("hcl2example19.txt")] + [TestCase("hcl2example20.txt")] + public void GenericExamples(string file) + { + var template = TerraformLoadTemplate(file, HCL2TemplateSamples); + var parsed = HclParser.HclTemplate.Parse(template); + var printed = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(printed); + var reprinted = reparsed.ToString(); + printed.Should().Be(reprinted); + } - [Test] - [TestCase("locals {\n tags = merge(\"var.tags\")\n}")] - [TestCase("locals {\n tags = merge(\"var.tags1\", \"var.tags2\")\n}")] - [TestCase("locals {\n tags = merge(var.tags, {\"Name\" = \"${var.network_name}-ip\"})\n}")] - [TestCase("locals {\n tags = merge({\"Name\" = \"${var.network_name}-ip\"})\n}")] - [TestCase("locals {\n depends_on = [\n aws_s3_bucket.bucket\n ]\n}")] - public void TestAssignmentInElement(string index) - { - var result = HclParser.NameElement.Parse(index); - result.ToString().Should().Be(index); - } + [Test] + [TestCase("hcl2objectproperty.txt", "vpc = object({id = \"string\", cidr_block = \"string\"})")] + [TestCase("hcl2objectproperty2.txt", + "vpc = object({id = \"string\", cidr_block = \"string\", vpc = object({id = \"string\", cidr_block = \"string\"})})")] + public void ObjectProperty(string file, string result) + { + var template = TerraformLoadTemplate(file, HCL2TemplateSamples); + var parsed = HclParser.ElementTypedObjectProperty.Parse(template); + parsed.ToString(-1).Should().Be(result); + } - [Test] - [TestCase("{\n \"Name\" = \"${var.network_name}-ip\"\n}")] - public void TestMapValue(string index) - { - var result = HclParser.MapValue.Parse(index); - result.ToString().Should().Be(index); - } + [Test] + [TestCase("locals {\n tags = merge(\"var.tags\")\n}")] + [TestCase("locals {\n tags = merge(\"var.tags1\", \"var.tags2\")\n}")] + [TestCase("locals {\n tags = merge(var.tags, {\"Name\" = \"${var.network_name}-ip\"})\n}")] + [TestCase("locals {\n tags = merge({\"Name\" = \"${var.network_name}-ip\"})\n}")] + [TestCase("locals {\n depends_on = [\n aws_s3_bucket.bucket\n ]\n}")] + public void TestAssignmentInElement(string index) + { + var result = HclParser.NameElement.Parse(index); + result.ToString().Should().Be(index); + } - [Test] - [TestCase("\"Name\" = \"${var.network_name}-ip\"")] - public void TestQuotedElementProperty(string index) - { - var result = HclParser.QuotedElementProperty.Parse(index); - result.ToString().Should().Be(index); - } + [Test] + [TestCase("{\n \"Name\" = \"${var.network_name}-ip\"\n}")] + public void TestMapValue(string index) + { + var result = HclParser.MapValue.Parse(index); + result.ToString().Should().Be(index); + } - [Test] - [TestCase("${var.network_name}-ip")] - public void TestStringLiteralQuote(string index) - { - var result = HclParser.StringLiteralQuoteContent.Parse(index); - result.Should().Be(index); - } + [Test] + [TestCase("\"Name\" = \"${var.network_name}-ip\"")] + public void TestQuotedElementProperty(string index) + { + var result = HclParser.QuotedElementProperty.Parse(index); + result.ToString().Should().Be(index); + } + + [Test] + [TestCase("${var.network_name}-ip")] + public void TestStringLiteralQuote(string index) + { + var result = HclParser.StringLiteralQuoteContent.Parse(index); + result.Should().Be(index); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/HclTemplateParserTest.cs b/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/HclTemplateParserTest.cs index 5715369..299e4b5 100644 --- a/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/HclTemplateParserTest.cs +++ b/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/HclTemplateParserTest.cs @@ -4,1313 +4,1313 @@ using NUnit.Framework; using Sprache; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Tested based on +/// https://github.com/hashicorp/hcl/blob/a4b07c25de5ff55ad3b8936cea69a79a3d95a855/hcl/parser/parser_test.go +/// +public class HclTemplateParserTest : TerraformTemplateLoader { + [Test] + public void ParseHCL1() + { + var template = TerraformLoadTemplate("aws.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.First().Value.Should().Match("aws_region"); + parsed.Children.First().Name.Should().Match("variable"); + parsed.Children.First().Children.Should().HaveCount(2); + parsed.Children.First().Children.FirstOrDefault(prop => "description" == prop.Name).Should().NotBeNull(); + parsed.Children.First().Children.FirstOrDefault(prop => "default" == prop.Name).Should().NotBeNull(); + parsed.Children.First().Children.First(prop => "description" == prop.Name).Value.Should() + .Match("The AWS region to create things in."); + parsed.Children.First().Children.First(prop => "default" == prop.Name).Value.Should().Match("us-east-1"); + } + + [Test] + public void ParseHCL2() + { + var template = TerraformLoadTemplate("empty.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(6); + parsed.Children.First().Value.Should().Match("prod_access_key"); + parsed.Children.First().Name.Should().Match("variable"); + parsed.Children.First().Children.Should().HaveCount(0); + } + + [Test] + public void ParseHCL3() + { + var template = TerraformLoadTemplate("heredocdescription.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.First().Value.Should().Match("public_key_path"); + parsed.Children.First().Name.Should().Match("variable"); + parsed.Children.First().Children.Should().HaveCount(1); + parsed.Children.First().Children.First().Name.Should().Match("description"); + parsed.Children.First().Children.First().Value.Should().Match( + "\nPath to the SSH public key to be used for authentication.\n" + + "Ensure this keypair is added to your local SSH agent so provisioners can\n" + + "connect.\n" + + "Example: ~/.ssh/terraform.pub\n"); + } + + [Test] + public void ParseHCL4() + { + var template = TerraformLoadTemplate("mixedtext.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(7); + parsed.Children.First().Value.Should().Match("public_key_path"); + parsed.Children.First().Name.Should().Match("variable"); + parsed.Children.First().Children.Should().HaveCount(1); + parsed.Children.First().Children.First().Name.Should().Match("description"); + parsed.Children.First().Children.First().Value.Should().Match( + "\nPath to the SSH public key to be used for authentication.\n" + + "Ensure this keypair is added to your local SSH agent so provisioners can\n" + + "connect.\n" + + "Example: ~/.ssh/terraform.pub\n"); + } + + [Test] + public void StringWithoutInterpolationParsing() + { + var template = TerraformLoadTemplate("string_without_interpolation_parsing.txt"); + var parsed = HclParser.StringLiteralQuote.Parse(template); + parsed.Should().Match("${element(var.remote_port[\"${element(keys(var.remote_port), count.index)}\"], 1)}"); + } + + [Test] + public void NestedInterpolation() + { + var template = TerraformLoadTemplate("nested_interpolation.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + } + + [Test] + public void Example1() + { + var template = TerraformLoadTemplate("example1.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Where(obj => "variable" == obj.Name).ToList().Should().HaveCount(15); + } + + [Test] + public void Example15() + { + var template = TerraformLoadTemplate("example15.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + // This is an example of some nested interpolation + parsed.Children.First().Children.Any(obj => + obj.Name == "backend_port" && obj.Value == + "${element(var.remote_port[\"${element(keys(var.remote_port), count.index)}\"], 1)}").Should().BeTrue(); + } + + [Test] + public void Example29() + { + var template = TerraformLoadTemplate("example29.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + } + + [Test] + public void Example30() + { + var template = TerraformLoadTemplate("example30.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + } + + [Test] + public void Example31() + { + var template = TerraformLoadTemplate("example31.tf"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(3); + } + /// - /// Tested based on - /// https://github.com/hashicorp/hcl/blob/a4b07c25de5ff55ad3b8936cea69a79a3d95a855/hcl/parser/parser_test.go + /// 100 random examples of terraform templates from GitHub. /// - public class HclTemplateParserTest : TerraformTemplateLoader + /// The terraform file to parse + [Test] + [TestCase("example2.tf")] + [TestCase("example3.tf")] + [TestCase("example4.tf")] + [TestCase("example5.tf")] + [TestCase("example6.tf")] + [TestCase("example7.tf")] + [TestCase("example8.tf")] + [TestCase("example9.tf")] + [TestCase("example10.tf")] + [TestCase("example11.tf")] + [TestCase("example12.tf")] + [TestCase("example13.tf")] + [TestCase("example14.tf")] + [TestCase("example16.tf")] + [TestCase("example17.tf")] + [TestCase("example18.tf")] + [TestCase("example19.tf")] + [TestCase("example20.tf")] + [TestCase("example21.tf")] + [TestCase("example22.tf")] + [TestCase("example23.tf")] + [TestCase("example24.tf")] + [TestCase("example25.tf")] + [TestCase("example26.tf")] + [TestCase("example27.tf")] + [TestCase("example28.tf")] + [TestCase("example32.tf")] + [TestCase("example33.tf")] + [TestCase("example34.tf")] + [TestCase("example35.tf")] + [TestCase("example36.tf")] + [TestCase("example37.tf")] + [TestCase("example38.tf")] + [TestCase("example39.tf")] + [TestCase("example40.tf")] + [TestCase("example41.tf")] + [TestCase("example42.tf")] + [TestCase("example43.tf")] + [TestCase("example44.tf")] + [TestCase("example45.tf")] + [TestCase("example46.tf")] + [TestCase("example47.tf")] + [TestCase("example48.tf")] + [TestCase("example49.tf")] + [TestCase("example50.tf")] + [TestCase("example51.tf")] + [TestCase("example52.tf")] + [TestCase("example53.tf")] + [TestCase("example54.tf")] + [TestCase("example55.tf")] + [TestCase("example56.tf")] + [TestCase("example57.tf")] + [TestCase("example58.tf")] + [TestCase("example59.tf")] + [TestCase("example60.tf")] + [TestCase("example61.tf")] + [TestCase("example62.tf")] + [TestCase("example63.tf")] + [TestCase("example64.tf")] + [TestCase("example65.tf")] + [TestCase("example66.tf")] + [TestCase("example67.tf")] + [TestCase("example68.tf")] + [TestCase("example69.tf")] + [TestCase("example70.tf")] + [TestCase("example71.tf")] + [TestCase("example72.tf")] + [TestCase("example73.tf")] + [TestCase("example74.tf")] + [TestCase("example75.tf")] + [TestCase("example76.tf")] + [TestCase("example77.tf")] + [TestCase("example78.tf")] + [TestCase("example79.tf")] + [TestCase("example80.tf")] + [TestCase("example81.tf")] + [TestCase("example82.tf")] + [TestCase("example83.tf")] + [TestCase("example84.tf")] + [TestCase("example86.tf")] + [TestCase("example87.tf")] + [TestCase("example88.tf")] + [TestCase("example89.tf")] + [TestCase("example90.tf")] + [TestCase("example91.tf")] + [TestCase("example92.tf")] + [TestCase("example93.tf")] + [TestCase("example94.tf")] + [TestCase("example95.tf")] + [TestCase("example96.tf")] + [TestCase("example97.tf")] + [TestCase("example98.tf")] + [TestCase("example99.tf")] + public void GenericExamples(string file) { - [Test] - public void ParseHCL1() - { - var template = TerraformLoadTemplate("aws.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.First().Value.Should().Match("aws_region"); - parsed.Children.First().Name.Should().Match("variable"); - parsed.Children.First().Children.Should().HaveCount(2); - parsed.Children.First().Children.FirstOrDefault(prop => "description" == prop.Name).Should().NotBeNull(); - parsed.Children.First().Children.FirstOrDefault(prop => "default" == prop.Name).Should().NotBeNull(); - parsed.Children.First().Children.First(prop => "description" == prop.Name).Value.Should() - .Match("The AWS region to create things in."); - parsed.Children.First().Children.First(prop => "default" == prop.Name).Value.Should().Match("us-east-1"); - } - - [Test] - public void ParseHCL2() - { - var template = TerraformLoadTemplate("empty.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(6); - parsed.Children.First().Value.Should().Match("prod_access_key"); - parsed.Children.First().Name.Should().Match("variable"); - parsed.Children.First().Children.Should().HaveCount(0); - } + var template = TerraformLoadTemplate(file); + var parsed = HclParser.HclTemplate.Parse(template); + var reprinted = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(reprinted); + var reprinted2 = reparsed.ToString(); + reprinted.Should().Match(reprinted2); + parsed.Should().BeEquivalentTo(reparsed); + } - [Test] - public void ParseHCL3() - { - var template = TerraformLoadTemplate("heredocdescription.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.First().Value.Should().Match("public_key_path"); - parsed.Children.First().Name.Should().Match("variable"); - parsed.Children.First().Children.Should().HaveCount(1); - parsed.Children.First().Children.First().Name.Should().Match("description"); - parsed.Children.First().Children.First().Value.Should().Match( - "\nPath to the SSH public key to be used for authentication.\n" + - "Ensure this keypair is added to your local SSH agent so provisioners can\n" + - "connect.\n" + - "Example: ~/.ssh/terraform.pub\n"); - } + [TestCase("example39.tf")] + public void OneFileExample(string file) + { + var template = TerraformLoadTemplate(file); + var parsed = HclParser.HclTemplate.Parse(template); + var reprinted = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(reprinted); + var reprinted2 = reparsed.ToString(); + reprinted.Should().Match(reprinted2); + parsed.Should().BeEquivalentTo(reparsed); + } - [Test] - public void ParseHCL4() - { - var template = TerraformLoadTemplate("mixedtext.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(7); - parsed.Children.First().Value.Should().Match("public_key_path"); - parsed.Children.First().Name.Should().Match("variable"); - parsed.Children.First().Children.Should().HaveCount(1); - parsed.Children.First().Children.First().Name.Should().Match("description"); - parsed.Children.First().Children.First().Value.Should().Match( - "\nPath to the SSH public key to be used for authentication.\n" + - "Ensure this keypair is added to your local SSH agent so provisioners can\n" + - "connect.\n" + - "Example: ~/.ssh/terraform.pub\n"); - } + [Test] + public void QuotedText() + { + var template = TerraformLoadTemplate("quotedtext.txt"); + var parsed = HclParser.ElementProperty.Parse(template); + parsed.Value.Should().Match("\"altitude-nyc-abcd-2017-stage.storage.googleapis.com\""); + } - [Test] - public void StringWithoutInterpolationParsing() - { - var template = TerraformLoadTemplate("string_without_interpolation_parsing.txt"); - var parsed = HclParser.StringLiteralQuote.Parse(template); - parsed.Should().Match("${element(var.remote_port[\"${element(keys(var.remote_port), count.index)}\"], 1)}"); - } + [Test] + public void UnquotingText() + { + var template = TerraformLoadTemplate("quotedtext_raw.txt"); + var parsed = HclParser.StringLiteralQuoteContentReverse.Parse(template); + parsed.Should().Match("\\\"altitude-nyc-abcd-2017-stage.storage.googleapis.com\\\""); + } - [Test] - public void NestedInterpolation() - { - var template = TerraformLoadTemplate("nested_interpolation.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - } + [Test] + public void CommentsAndNameElement() + { + var template = TerraformLoadTemplate("test1.txt"); + var parsed = HclParser.HclTemplate.Parse(template); + var reprinted = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(reprinted); + var reprinted2 = reparsed.ToString(); + reprinted.Should().Match(reprinted2); + } - [Test] - public void Example1() - { - var template = TerraformLoadTemplate("example1.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Where(obj => "variable" == obj.Name).ToList().Should().HaveCount(15); - } + [Test] + public void EndingComments() + { + var template = TerraformLoadTemplate("test3.txt"); + var parsed = HclParser.HclTemplate.Parse(template); + var reprinted = parsed.ToString(); + var reparsed = HclParser.HclTemplate.Parse(reprinted); + var reprinted2 = reparsed.ToString(); + reprinted.Should().Match(reprinted2); + } - [Test] - public void Example15() - { - var template = TerraformLoadTemplate("example15.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - // This is an example of some nested interpolation - parsed.Children.First().Children.Any(obj => - obj.Name == "backend_port" && obj.Value == - "${element(var.remote_port[\"${element(keys(var.remote_port), count.index)}\"], 1)}").Should().BeTrue(); - } + [Test] + public void ParseCommentSingleLine() + { + var template = TerraformLoadTemplate("commentsingleline.txt"); + var parsed = HclParser.SingleLineComment.Many().Parse(template).ToList(); + parsed.Should().HaveCount(2); + parsed.All(obj => obj.Value.StartsWith("Hello World")).Should().BeTrue(); + } - [Test] - public void Example29() - { - var template = TerraformLoadTemplate("example29.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - } + [Test] + public void ParseComment() + { + var template = TerraformLoadTemplate("comment.txt"); + var parsed = HclParser.MultilineComment.Parse(template); + parsed.Value.Should().Match("\nHello\nWorld\n"); + } - [Test] - public void Example30() - { - var template = TerraformLoadTemplate("example30.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - } + [Test] + public void ParseHereDoc() + { + var template = TerraformLoadTemplate("multilinestring.txt"); + var parsed = HclParser.HereDoc.Parse(template); + parsed.Item3.Should().Match("\nHello\nWorld\n"); + } - [Test] - public void Example31() - { - var template = TerraformLoadTemplate("example31.tf"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(3); - } + [Test] + public void ParseMap() + { + var template = TerraformLoadTemplate("map.tf"); + var parsed = HclParser.NameValueElement.Parse(template); + parsed.Name.Should().Match("variable"); + } - /// - /// 100 random examples of terraform templates from GitHub. - /// - /// The terraform file to parse - [Test] - [TestCase("example2.tf")] - [TestCase("example3.tf")] - [TestCase("example4.tf")] - [TestCase("example5.tf")] - [TestCase("example6.tf")] - [TestCase("example7.tf")] - [TestCase("example8.tf")] - [TestCase("example9.tf")] - [TestCase("example10.tf")] - [TestCase("example11.tf")] - [TestCase("example12.tf")] - [TestCase("example13.tf")] - [TestCase("example14.tf")] - [TestCase("example16.tf")] - [TestCase("example17.tf")] - [TestCase("example18.tf")] - [TestCase("example19.tf")] - [TestCase("example20.tf")] - [TestCase("example21.tf")] - [TestCase("example22.tf")] - [TestCase("example23.tf")] - [TestCase("example24.tf")] - [TestCase("example25.tf")] - [TestCase("example26.tf")] - [TestCase("example27.tf")] - [TestCase("example28.tf")] - [TestCase("example32.tf")] - [TestCase("example33.tf")] - [TestCase("example34.tf")] - [TestCase("example35.tf")] - [TestCase("example36.tf")] - [TestCase("example37.tf")] - [TestCase("example38.tf")] - [TestCase("example39.tf")] - [TestCase("example40.tf")] - [TestCase("example41.tf")] - [TestCase("example42.tf")] - [TestCase("example43.tf")] - [TestCase("example44.tf")] - [TestCase("example45.tf")] - [TestCase("example46.tf")] - [TestCase("example47.tf")] - [TestCase("example48.tf")] - [TestCase("example49.tf")] - [TestCase("example50.tf")] - [TestCase("example51.tf")] - [TestCase("example52.tf")] - [TestCase("example53.tf")] - [TestCase("example54.tf")] - [TestCase("example55.tf")] - [TestCase("example56.tf")] - [TestCase("example57.tf")] - [TestCase("example58.tf")] - [TestCase("example59.tf")] - [TestCase("example60.tf")] - [TestCase("example61.tf")] - [TestCase("example62.tf")] - [TestCase("example63.tf")] - [TestCase("example64.tf")] - [TestCase("example65.tf")] - [TestCase("example66.tf")] - [TestCase("example67.tf")] - [TestCase("example68.tf")] - [TestCase("example69.tf")] - [TestCase("example70.tf")] - [TestCase("example71.tf")] - [TestCase("example72.tf")] - [TestCase("example73.tf")] - [TestCase("example74.tf")] - [TestCase("example75.tf")] - [TestCase("example76.tf")] - [TestCase("example77.tf")] - [TestCase("example78.tf")] - [TestCase("example79.tf")] - [TestCase("example80.tf")] - [TestCase("example81.tf")] - [TestCase("example82.tf")] - [TestCase("example83.tf")] - [TestCase("example84.tf")] - [TestCase("example86.tf")] - [TestCase("example87.tf")] - [TestCase("example88.tf")] - [TestCase("example89.tf")] - [TestCase("example90.tf")] - [TestCase("example91.tf")] - [TestCase("example92.tf")] - [TestCase("example93.tf")] - [TestCase("example94.tf")] - [TestCase("example95.tf")] - [TestCase("example96.tf")] - [TestCase("example97.tf")] - [TestCase("example98.tf")] - [TestCase("example99.tf")] - public void GenericExamples(string file) - { - var template = TerraformLoadTemplate(file); - var parsed = HclParser.HclTemplate.Parse(template); - var reprinted = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(reprinted); - var reprinted2 = reparsed.ToString(); - reprinted.Should().Match(reprinted2); - parsed.Should().BeEquivalentTo(reparsed); - } + [Test] + public void ParseMapColon() + { + var template = TerraformLoadTemplate("map_colon.tf"); + var parsed = HclParser.NameValueElement.Parse(template); + parsed.Name.Should().Match("variable"); + parsed.Child.Name.Should().Match("default"); + var children = parsed.Child.Children.ToArray(); + + children[0].Name.Should().Match("eu-west-1"); + children[0].Value.Should().Match("ami-674cbc1e"); + children[1].Name.Should().Match("us-east-1"); + children[1].Value.Should().Match("ami-1d4e7a66"); + children[2].Name.Should().Match("us-west-1"); + children[2].Value.Should().Match("ami-969ab1f6"); + children[3].Name.Should().Match("us-west-2"); + children[3].Value.Should().Match("ami-8803e0f0"); + } - [TestCase("example39.tf")] - public void OneFileExample(string file) - { - var template = TerraformLoadTemplate(file); - var parsed = HclParser.HclTemplate.Parse(template); - var reprinted = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(reprinted); - var reprinted2 = reparsed.ToString(); - reprinted.Should().Match(reprinted2); - parsed.Should().BeEquivalentTo(reparsed); - } + [Test] + public void ParseListWithBool() + { + var template = TerraformLoadTemplate("listwithboolean.txt"); + var parsed = HclParser.ElementListProperty.Parse(template); + parsed.Name.Should().Match("bool"); + } - [Test] - public void QuotedText() - { - var template = TerraformLoadTemplate("quotedtext.txt"); - var parsed = HclParser.ElementProperty.Parse(template); - parsed.Value.Should().Match("\"altitude-nyc-abcd-2017-stage.storage.googleapis.com\""); - } + [Test] + public void ParseMapWithListWithBool() + { + var template = TerraformLoadTemplate("mapwithlistwithboolean.txt"); + var parsed = HclParser.ElementMapProperty.Parse(template); + parsed.Name.Should().Match("permissions"); + } - [Test] - public void UnquotingText() - { - var template = TerraformLoadTemplate("quotedtext_raw.txt"); - var parsed = HclParser.StringLiteralQuoteContentReverse.Parse(template); - parsed.Should().Match("\\\"altitude-nyc-abcd-2017-stage.storage.googleapis.com\\\""); - } + [Test] + public void ParseEmptyResource() + { + var template = TerraformLoadTemplate("emptyresource.tf"); + var parsed = HclParser.NameValueTypeElement.Parse(template); + parsed.Name.Should().Match("resource"); + } - [Test] - public void CommentsAndNameElement() - { - var template = TerraformLoadTemplate("test1.txt"); - var parsed = HclParser.HclTemplate.Parse(template); - var reprinted = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(reprinted); - var reprinted2 = reparsed.ToString(); - reprinted.Should().Match(reprinted2); - } + [Test] + public void ParseResourceWithChildren() + { + var template = TerraformLoadTemplate("resourcewithchildren.tf"); + var parsed = HclParser.NameValueTypeElement.Parse(template); + parsed.Name.Should().Match("resource"); + } - [Test] - public void EndingComments() - { - var template = TerraformLoadTemplate("test3.txt"); - var parsed = HclParser.HclTemplate.Parse(template); - var reprinted = parsed.ToString(); - var reparsed = HclParser.HclTemplate.Parse(reprinted); - var reprinted2 = reparsed.ToString(); - reprinted.Should().Match(reprinted2); - } + [Test] + public void ParseResource() + { + var template = TerraformLoadTemplate("resource.tf"); + var parsed = HclParser.NameValueTypeElement.Parse(template); + parsed.Name.Should().Match("resource"); + } - [Test] - public void ParseCommentSingleLine() - { - var template = TerraformLoadTemplate("commentsingleline.txt"); - var parsed = HclParser.SingleLineComment.Many().Parse(template).ToList(); - parsed.Should().HaveCount(2); - parsed.All(obj => obj.Value.StartsWith("Hello World")).Should().BeTrue(); - } + [Test] + public void StringInterpolationRaw() + { + var template = TerraformLoadTemplate("interpolation.txt"); + var parsed = HclParser.StringLiteralCurly.Parse(template); + parsed.Should().Match("${\"there\"}"); + } - [Test] - public void ParseComment() - { - var template = TerraformLoadTemplate("comment.txt"); - var parsed = HclParser.MultilineComment.Parse(template); - parsed.Value.Should().Match("\nHello\nWorld\n"); - } + [Test] + public void StringInterpolation() + { + var template = TerraformLoadTemplate("curlytexttest.txt"); + var parsed = HclParser.StringLiteralQuote.Parse(template); + parsed.Should().Match("Hi ${\"there\"}"); + } - [Test] - public void ParseHereDoc() - { - var template = TerraformLoadTemplate("multilinestring.txt"); - var parsed = HclParser.HereDoc.Parse(template); - parsed.Item3.Should().Match("\nHello\nWorld\n"); - } + [Test] + public void Basic() + { + var template = TerraformLoadTemplate("basic.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(2); + parsed.FirstOrDefault(element => "foo" == element.Name).Should().NotBeNull(); + parsed.First(element => "foo" == element.Name).Value.Should().Match("bar"); + parsed.FirstOrDefault(element => "bar" == element.Name).Should().NotBeNull(); + parsed.First(element => "bar" == element.Name).Value.Should().Match("${file(\"bing/bong.txt\")}"); + } - [Test] - public void ParseMap() - { - var template = TerraformLoadTemplate("map.tf"); - var parsed = HclParser.NameValueElement.Parse(template); - parsed.Name.Should().Match("variable"); - } + [Test] + public void BasicIntString() + { + var template = TerraformLoadTemplate("basic_int_string.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(1); + parsed.FirstOrDefault(element => "count" == element.Name).Should().NotBeNull(); + parsed.First(element => "count" == element.Name).Value.Should().Match("3"); + } - [Test] - public void ParseMapColon() - { - var template = TerraformLoadTemplate("map_colon.tf"); - var parsed = HclParser.NameValueElement.Parse(template); - parsed.Name.Should().Match("variable"); - parsed.Child.Name.Should().Match("default"); - var children = parsed.Child.Children.ToArray(); - - children[0].Name.Should().Match("eu-west-1"); - children[0].Value.Should().Match("ami-674cbc1e"); - children[1].Name.Should().Match("us-east-1"); - children[1].Value.Should().Match("ami-1d4e7a66"); - children[2].Name.Should().Match("us-west-1"); - children[2].Value.Should().Match("ami-969ab1f6"); - children[3].Name.Should().Match("us-west-2"); - children[3].Value.Should().Match("ami-8803e0f0"); - } + [Test] + public void BasicSquish() + { + var template = TerraformLoadTemplate("basic_squish.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(3); + parsed.FirstOrDefault(element => "foo" == element.Name).Should().NotBeNull(); + parsed.First(element => "foo" == element.Name).Value.Should().Match("bar"); + parsed.FirstOrDefault(element => "bar" == element.Name).Should().NotBeNull(); + parsed.First(element => "bar" == element.Name).Value.Should().Match("${file(\"bing/bong.txt\")}"); + parsed.FirstOrDefault(element => "foo-bar" == element.Name).Should().NotBeNull(); + parsed.First(element => "foo-bar" == element.Name).Value.Should().Match("baz"); + } - [Test] - public void ParseListWithBool() - { - var template = TerraformLoadTemplate("listwithboolean.txt"); - var parsed = HclParser.ElementListProperty.Parse(template); - parsed.Name.Should().Match("bool"); - } + [Test] + public void BlockAssign() + { + var template = TerraformLoadTemplate("block_assign.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.First().Name.Should().Match("environment"); + parsed.Children.First().Value.Should().Match("aws"); + } - [Test] - public void ParseMapWithListWithBool() - { - var template = TerraformLoadTemplate("mapwithlistwithboolean.txt"); - var parsed = HclParser.ElementMapProperty.Parse(template); - parsed.Name.Should().Match("permissions"); - } + [Test] + public void DecodePolicy() + { + var template = TerraformLoadTemplate("decode_policy.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(4); + parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "" == obj.Value).Should().NotBeNull(); + parsed.Children.First(obj => "key" == obj.Name && "" == obj.Value).Children.Should().HaveCount(1); + parsed.Children.First(obj => "key" == obj.Name && "" == obj.Value).Children.First().Name.Should() + .Match("policy"); + parsed.Children.First(obj => "key" == obj.Name && "" == obj.Value).Children.First().Value.Should() + .Match("read"); + + parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "foo/" == obj.Value).Should().NotBeNull(); + parsed.Children.First(obj => "key" == obj.Name && "foo/" == obj.Value).Children.Should().HaveCount(1); + parsed.Children.First(obj => "key" == obj.Name && "foo/" == obj.Value).Children.First().Name.Should() + .Match("policy"); + parsed.Children.First(obj => "key" == obj.Name && "foo/" == obj.Value).Children.First().Value.Should() + .Match("write"); + + parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Should().NotBeNull(); + parsed.Children.First(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Children.Should().HaveCount(1); + parsed.Children.First(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Children.First().Name.Should() + .Match("policy"); + parsed.Children.First(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Children.First().Value.Should() + .Match("read"); + + parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Should().NotBeNull(); + parsed.Children.First(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Children.Should() + .HaveCount(1); + parsed.Children.First(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Children.First().Name.Should() + .Match("policy"); + parsed.Children.First(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Children.First().Value + .Should().Match("deny"); + } - [Test] - public void ParseEmptyResource() - { - var template = TerraformLoadTemplate("emptyresource.tf"); - var parsed = HclParser.NameValueTypeElement.Parse(template); - parsed.Name.Should().Match("resource"); - } + [Test] + public void DecodeTFVariable() + { + var template = TerraformLoadTemplate("decode_tf_variable.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => obj.Name == "variable" && obj.Value == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "foo").Children.Should().HaveCount(2); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "foo").Children + .FirstOrDefault(obj => obj.Name == "default" && obj.Value == "bar").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "foo").Children + .FirstOrDefault(obj => obj.Name == "description" && obj.Value == "bar").Should().NotBeNull(); + + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children.Should().HaveCount(1); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children + .FirstOrDefault(obj => obj.Name == "default").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children + .First(obj => obj.Name == "default").Children.Should().HaveCount(1); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children + .First(obj => obj.Name == "default").Children.First().Name.Should().Match("east"); + parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children + .First(obj => obj.Name == "default").Children.First().Value.Should().Match("foo"); + } - [Test] - public void ParseResourceWithChildren() - { - var template = TerraformLoadTemplate("resourcewithchildren.tf"); - var parsed = HclParser.NameValueTypeElement.Parse(template); - parsed.Name.Should().Match("resource"); - } + [Test] + public void EmptyHCL() + { + var template = TerraformLoadTemplate("empty.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.First().Name.Should().Match("resource"); + parsed.Children.First().Value.Should().Match("foo"); + parsed.Children.First().Children.Should().BeEmpty(); + } - [Test] - public void ParseResource() - { - var template = TerraformLoadTemplate("resource.tf"); - var parsed = HclParser.NameValueTypeElement.Parse(template); - parsed.Name.Should().Match("resource"); - } + [Test] + public void Escape() + { + var template = TerraformLoadTemplate("escape.hcl"); + var parsed = HclParser.Properties.Parse(template).ToArray(); + parsed.Should().HaveCount(6); + parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar\"baz\\n").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "new\nline").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "qux" && obj.Value == "back\\slash").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "qax" && obj.Value == "slash\\:colon").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "nested" && obj.Value == "${HH\\\\:mm\\\\:ss}").Should() + .NotBeNull(); + parsed.FirstOrDefault(obj => + obj.Name == "nestedquotes" && obj.Value == "${\"\\\"stringwrappedinquotes\\\"\"}").Should().NotBeNull(); + } - [Test] - public void StringInterpolationRaw() - { - var template = TerraformLoadTemplate("interpolation.txt"); - var parsed = HclParser.StringLiteralCurly.Parse(template); - parsed.Should().Match("${\"there\"}"); - } + [Test] + public void EscapeBackslash() + { + var template = TerraformLoadTemplate("escape_backslash.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "output").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "output").Children.FirstOrDefault(obj => + obj.Name == "one" && obj.Value == @"${replace(var.sub_domain, ""."", ""\\."")}").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "output").Children.FirstOrDefault(obj => + obj.Name == "two" && obj.Value == @"${replace(var.sub_domain, ""."", ""\\\\."")}").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "output").Children.FirstOrDefault(obj => + obj.Name == "many" && obj.Value == @"${replace(var.sub_domain, ""."", ""\\\\\\\\."")}").Should() + .NotBeNull(); + } - [Test] - public void StringInterpolation() - { - var template = TerraformLoadTemplate("curlytexttest.txt"); - var parsed = HclParser.StringLiteralQuote.Parse(template); - parsed.Should().Match("Hi ${\"there\"}"); - } + [Test] + public void Flat() + { + var template = TerraformLoadTemplate("flat.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(2); + parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "Key" && obj.Value == "7").Should().NotBeNull(); + } - [Test] - public void Basic() - { - var template = TerraformLoadTemplate("basic.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(2); - parsed.FirstOrDefault(element => "foo" == element.Name).Should().NotBeNull(); - parsed.First(element => "foo" == element.Name).Value.Should().Match("bar"); - parsed.FirstOrDefault(element => "bar" == element.Name).Should().NotBeNull(); - parsed.First(element => "bar" == element.Name).Value.Should().Match("${file(\"bing/bong.txt\")}"); - } + [Test] + public void Float() + { + var template = TerraformLoadTemplate("float.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => obj.Name == "a" && obj.Value == "1.02").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "b" && obj.Value == "2").Should().NotBeNull(); + } - [Test] - public void BasicIntString() - { - var template = TerraformLoadTemplate("basic_int_string.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(1); - parsed.FirstOrDefault(element => "count" == element.Name).Should().NotBeNull(); - parsed.First(element => "count" == element.Name).Value.Should().Match("3"); - } + [Test] + public void ListOfLists() + { + var template = TerraformLoadTemplate("list_of_lists.hcl"); + var parsed = HclParser.ElementListProperty.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.Count(child => child.Children.All(grandchild => grandchild.Value == "foo")).Should().Be(1); + parsed.Children.Count(child => child.Children.All(grandchild => grandchild.Value == "bar")).Should().Be(1); + } - [Test] - public void BasicSquish() - { - var template = TerraformLoadTemplate("basic_squish.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(3); - parsed.FirstOrDefault(element => "foo" == element.Name).Should().NotBeNull(); - parsed.First(element => "foo" == element.Name).Value.Should().Match("bar"); - parsed.FirstOrDefault(element => "bar" == element.Name).Should().NotBeNull(); - parsed.First(element => "bar" == element.Name).Value.Should().Match("${file(\"bing/bong.txt\")}"); - parsed.FirstOrDefault(element => "foo-bar" == element.Name).Should().NotBeNull(); - parsed.First(element => "foo-bar" == element.Name).Value.Should().Match("baz"); - } + [Test] + public void ListOfMaps() + { + var template = TerraformLoadTemplate("list_of_maps.hcl"); + var parsed = HclParser.ElementListProperty.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.Count(child => child.Children.All(grandchild => grandchild.Value == "someval1")).Should() + .Be(1); + parsed.Children.Count(child => child.Children.Any(grandchild => grandchild.Value == "someval2")).Should() + .Be(1); + parsed.Children.Count(child => child.Children.Any(grandchild => grandchild.Value == "someextraval")) + .Should().Be(1); + } - [Test] - public void BlockAssign() - { - var template = TerraformLoadTemplate("block_assign.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.First().Name.Should().Match("environment"); - parsed.Children.First().Value.Should().Match("aws"); - } + [Test] + public void Multiline() + { + var template = TerraformLoadTemplate("multiline.hcl"); + var parsed = HclParser.ElementMultilineProperty.Parse(template); + parsed.Value.Should().Match("\nbar\nbaz\n"); + } - [Test] - public void DecodePolicy() - { - var template = TerraformLoadTemplate("decode_policy.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(4); - parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "" == obj.Value).Should().NotBeNull(); - parsed.Children.First(obj => "key" == obj.Name && "" == obj.Value).Children.Should().HaveCount(1); - parsed.Children.First(obj => "key" == obj.Name && "" == obj.Value).Children.First().Name.Should() - .Match("policy"); - parsed.Children.First(obj => "key" == obj.Name && "" == obj.Value).Children.First().Value.Should() - .Match("read"); - - parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "foo/" == obj.Value).Should().NotBeNull(); - parsed.Children.First(obj => "key" == obj.Name && "foo/" == obj.Value).Children.Should().HaveCount(1); - parsed.Children.First(obj => "key" == obj.Name && "foo/" == obj.Value).Children.First().Name.Should() - .Match("policy"); - parsed.Children.First(obj => "key" == obj.Name && "foo/" == obj.Value).Children.First().Value.Should() - .Match("write"); - - parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Should().NotBeNull(); - parsed.Children.First(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Children.Should().HaveCount(1); - parsed.Children.First(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Children.First().Name.Should() - .Match("policy"); - parsed.Children.First(obj => "key" == obj.Name && "foo/bar/" == obj.Value).Children.First().Value.Should() - .Match("read"); - - parsed.Children.FirstOrDefault(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Should().NotBeNull(); - parsed.Children.First(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Children.Should() - .HaveCount(1); - parsed.Children.First(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Children.First().Name.Should() - .Match("policy"); - parsed.Children.First(obj => "key" == obj.Name && "foo/bar/baz" == obj.Value).Children.First().Value - .Should().Match("deny"); - } + [Test] + public void MultilineIndented() + { + var template = TerraformLoadTemplate("multiline_indented.hcl"); + var parsed = HclParser.ElementMultilineProperty.Parse(template); + parsed.Value.Should().Match("\n bar\n baz\n "); + } - [Test] - public void DecodeTFVariable() - { - var template = TerraformLoadTemplate("decode_tf_variable.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => obj.Name == "variable" && obj.Value == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "foo").Children.Should().HaveCount(2); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "foo").Children - .FirstOrDefault(obj => obj.Name == "default" && obj.Value == "bar").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "foo").Children - .FirstOrDefault(obj => obj.Name == "description" && obj.Value == "bar").Should().NotBeNull(); - - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children.Should().HaveCount(1); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children - .FirstOrDefault(obj => obj.Name == "default").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children - .First(obj => obj.Name == "default").Children.Should().HaveCount(1); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children - .First(obj => obj.Name == "default").Children.First().Name.Should().Match("east"); - parsed.Children.First(obj => obj.Name == "variable" && obj.Value == "amis").Children - .First(obj => obj.Name == "default").Children.First().Value.Should().Match("foo"); - } + [Test] + public void MultilineLiteral() + { + var template = TerraformLoadTemplate("multiline_literal.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(1); + parsed.First().Name.Should().Match("multiline_literal"); + parsed.First().Value.Should().Match("hello\n world"); + } - [Test] - public void EmptyHCL() - { - var template = TerraformLoadTemplate("empty.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.First().Name.Should().Match("resource"); - parsed.Children.First().Value.Should().Match("foo"); - parsed.Children.First().Children.Should().BeEmpty(); - } + [Test] + public void MultilineLiteralHil() + { + var template = TerraformLoadTemplate("multiline_literal_with_hil.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(1); + parsed.First().Name.Should().Match("multiline_literal_with_hil"); + parsed.First().Value.Should().Match("${hello\n world}"); + } - [Test] - public void Escape() - { - var template = TerraformLoadTemplate("escape.hcl"); - var parsed = HclParser.Properties.Parse(template).ToArray(); - parsed.Should().HaveCount(6); - parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar\"baz\\n").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "new\nline").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "qux" && obj.Value == "back\\slash").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "qax" && obj.Value == "slash\\:colon").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "nested" && obj.Value == "${HH\\\\:mm\\\\:ss}").Should() - .NotBeNull(); - parsed.FirstOrDefault(obj => - obj.Name == "nestedquotes" && obj.Value == "${\"\\\"stringwrappedinquotes\\\"\"}").Should().NotBeNull(); - } + [Test] + public void MultilineNoEOF() + { + var template = TerraformLoadTemplate("multiline_no_eof.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(2); + parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "\nbar\nbaz\n").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "value").Should().NotBeNull(); + } - [Test] - public void EscapeBackslash() - { - var template = TerraformLoadTemplate("escape_backslash.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "output").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "output").Children.FirstOrDefault(obj => - obj.Name == "one" && obj.Value == @"${replace(var.sub_domain, ""."", ""\\."")}").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "output").Children.FirstOrDefault(obj => - obj.Name == "two" && obj.Value == @"${replace(var.sub_domain, ""."", ""\\\\."")}").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "output").Children.FirstOrDefault(obj => - obj.Name == "many" && obj.Value == @"${replace(var.sub_domain, ""."", ""\\\\\\\\."")}").Should() - .NotBeNull(); - } + [Test] + public void MultilineNoHangingIndent() + { + var template = TerraformLoadTemplate("multiline_no_hanging_indent.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(1); + parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "\n baz\n bar\n foo\n ") + .Should().NotBeNull(); + } - [Test] - public void Flat() - { - var template = TerraformLoadTemplate("flat.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(2); - parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "Key" && obj.Value == "7").Should().NotBeNull(); - } + [Test] + public void NestedBlockComment() + { + var template = TerraformLoadTemplate("nested_block_comment.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(2); + parsed.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "\nfoo = \"bar/*\"\n") + .Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "value").Should().NotBeNull(); + } - [Test] - public void Float() - { - var template = TerraformLoadTemplate("float.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => obj.Name == "a" && obj.Value == "1.02").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "b" && obj.Value == "2").Should().NotBeNull(); - } + [Test] + public void ObjectWithBool() + { + var template = TerraformLoadTemplate("object_with_bool.hcl"); + var parsed = HclParser.NameElement.Parse(template); + parsed.Name.Should().Match("path"); + parsed.Children.Should().HaveCount(2); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => obj.Name == "policy" && obj.Value == "write").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "permissions").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "permissions").Children.Should().HaveCount(1); + parsed.Children.First(obj => obj.Name == "permissions").Children + .FirstOrDefault(obj => obj.Name == "bool" && obj.Children.First().Value == "false").Should() + .NotBeNull(); + } - [Test] - public void ListOfLists() - { - var template = TerraformLoadTemplate("list_of_lists.hcl"); - var parsed = HclParser.ElementListProperty.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.Count(child => child.Children.All(grandchild => grandchild.Value == "foo")).Should().Be(1); - parsed.Children.Count(child => child.Children.All(grandchild => grandchild.Value == "bar")).Should().Be(1); - } + [Test] + public void Scientific() + { + var template = TerraformLoadTemplate("scientific.hcl"); + var parsed = HclParser.Properties.Parse(template).ToList(); + parsed.Should().HaveCount(6); + parsed.FirstOrDefault(obj => obj.Name == "a" && obj.Value == "1e-10").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "b" && obj.Value == "1e+10").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "c" && obj.Value == "1e10").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "d" && obj.Value == "1.2e-10").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "e" && obj.Value == "1.2e+10").Should().NotBeNull(); + parsed.FirstOrDefault(obj => obj.Name == "f" && obj.Value == "1.2e10").Should().NotBeNull(); + } - [Test] - public void ListOfMaps() - { - var template = TerraformLoadTemplate("list_of_maps.hcl"); - var parsed = HclParser.ElementListProperty.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.Count(child => child.Children.All(grandchild => grandchild.Value == "someval1")).Should() - .Be(1); - parsed.Children.Count(child => child.Children.Any(grandchild => grandchild.Value == "someval2")).Should() - .Be(1); - parsed.Children.Count(child => child.Children.Any(grandchild => grandchild.Value == "someextraval")) - .Should().Be(1); - } + [Test] + public void SliceExpand() + { + var template = TerraformLoadTemplate("slice_expand.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => obj.Name == "service" && obj.Value == "my-service-0").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "service" && obj.Value == "my-service-0").Children.Should() + .HaveCount(1); + parsed.Children.First(obj => obj.Name == "service" && obj.Value == "my-service-0") + .Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "value").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "service" && obj.Value == "my-service-1").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "service" && obj.Value == "my-service-1") + .Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "value").Should().NotBeNull(); + } - [Test] - public void Multiline() - { - var template = TerraformLoadTemplate("multiline.hcl"); - var parsed = HclParser.ElementMultilineProperty.Parse(template); - parsed.Value.Should().Match("\nbar\nbaz\n"); - } + [Test] + public void Structure() + { + var template = TerraformLoadTemplate("structure.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => + obj.Type == HclElement.CommentType && obj.Value == " This is a test structure for the lexer") + .Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "baz").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo" && obj.Value == "baz").Children.Should().HaveCount(2); + parsed.Children.First(obj => obj.Name == "foo" && obj.Value == "baz") + .Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "7").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo" && obj.Value == "baz") + .Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); + } - [Test] - public void MultilineIndented() - { - var template = TerraformLoadTemplate("multiline_indented.hcl"); - var parsed = HclParser.ElementMultilineProperty.Parse(template); - parsed.Value.Should().Match("\n bar\n baz\n "); - } + [Test] + public void StructureFlatMap() + { + var template = TerraformLoadTemplate("structure_flatmap.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); + parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "7")) + .Should().NotBeNull(); + parsed.Children + .FirstOrDefault(obj => obj.Children.All(child => child.Name == "foo" && child.Value == "bar")).Should() + .NotBeNull(); + } - [Test] - public void MultilineLiteral() - { - var template = TerraformLoadTemplate("multiline_literal.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(1); - parsed.First().Name.Should().Match("multiline_literal"); - parsed.First().Value.Should().Match("hello\n world"); - } + [Test] + public void StructureList() + { + var template = TerraformLoadTemplate("structure_list.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); + parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "7")) + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "12")) + .Should().NotBeNull(); + } - [Test] - public void MultilineLiteralHil() - { - var template = TerraformLoadTemplate("multiline_literal_with_hil.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(1); - parsed.First().Name.Should().Match("multiline_literal_with_hil"); - parsed.First().Value.Should().Match("${hello\n world}"); - } + [Test] + public void StructureMulti() + { + var template = TerraformLoadTemplate("structure_multi.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); + parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "7")) + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "12")) + .Should().NotBeNull(); + } - [Test] - public void MultilineNoEOF() - { - var template = TerraformLoadTemplate("multiline_no_eof.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(2); - parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "\nbar\nbaz\n").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "value").Should().NotBeNull(); - } + [Test] + public void Structure2() + { + var template = TerraformLoadTemplate("structure2.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(3); + parsed.Children.FirstOrDefault(obj => + obj.Type == HclElement.CommentType && obj.Value == " This is a test structure for the lexer") + .Should() + .NotBeNull(); + parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); + parsed.Children + .FirstOrDefault(obj => obj.Children?.All(child => child.Name == "key" && child.Value == "7") ?? false) + .Should().NotBeNull(); + parsed.Children + .FirstOrDefault(obj => obj.Children?.Any(child => child.Name == "foo" && child.Value == "bar") ?? false) + .Should().NotBeNull(); + } - [Test] - public void MultilineNoHangingIndent() - { - var template = TerraformLoadTemplate("multiline_no_hanging_indent.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(1); - parsed.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "\n baz\n bar\n foo\n ") - .Should().NotBeNull(); - } + [Test] + public void TerraformHeroku() + { + var template = TerraformLoadTemplate("terraform_heroku.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => obj.Name == "name" && obj.Value == "terraform-test-app").Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "config_vars").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "config_vars").Children.Should().HaveCount(1); + parsed.Children.First(obj => obj.Name == "config_vars").Children + .FirstOrDefault(obj => obj.Name == "FOO" && obj.Value == "bar").Should().NotBeNull(); + } - [Test] - public void NestedBlockComment() - { - var template = TerraformLoadTemplate("nested_block_comment.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(2); - parsed.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "\nfoo = \"bar/*\"\n") - .Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "value").Should().NotBeNull(); - } + [Test] + public void TFVars() + { + var template = TerraformLoadTemplate("tfvars.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(3); + parsed.Children.FirstOrDefault(obj => obj.Name == "regularvar" && obj.Value == "Should work").Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "map.key1" && obj.Value == "Value").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "map.key2" && obj.Value == "Other value").Should() + .NotBeNull(); + } - [Test] - public void ObjectWithBool() - { - var template = TerraformLoadTemplate("object_with_bool.hcl"); - var parsed = HclParser.NameElement.Parse(template); - parsed.Name.Should().Match("path"); - parsed.Children.Should().HaveCount(2); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => obj.Name == "policy" && obj.Value == "write").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "permissions").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "permissions").Children.Should().HaveCount(1); - parsed.Children.First(obj => obj.Name == "permissions").Children - .FirstOrDefault(obj => obj.Name == "bool" && obj.Children.First().Value == "false").Should() - .NotBeNull(); - } + [Test] + public void EscapedInterpolation() + { + var template = TerraformLoadTemplate("escaped_interpolation.txt"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children + .FirstOrDefault(obj => obj.Name == "one" && obj.Value == "$${replace(var.sub_domain, \".\", \"\\.\")}") + .Should().NotBeNull(); + } - [Test] - public void Scientific() - { - var template = TerraformLoadTemplate("scientific.hcl"); - var parsed = HclParser.Properties.Parse(template).ToList(); - parsed.Should().HaveCount(6); - parsed.FirstOrDefault(obj => obj.Name == "a" && obj.Value == "1e-10").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "b" && obj.Value == "1e+10").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "c" && obj.Value == "1e10").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "d" && obj.Value == "1.2e-10").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "e" && obj.Value == "1.2e+10").Should().NotBeNull(); - parsed.FirstOrDefault(obj => obj.Name == "f" && obj.Value == "1.2e10").Should().NotBeNull(); - } + [Test] + public void ArrayComment() + { + var template = TerraformLoadTemplate("array_comment.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.First().Children.FirstOrDefault(obj => obj.Value == "1").Should().NotBeNull(); + parsed.Children.First().Children.FirstOrDefault(obj => obj.Value == "2").Should().NotBeNull(); + parsed.Children.First().Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType).Should() + .NotBeNull(); + } - [Test] - public void SliceExpand() - { - var template = TerraformLoadTemplate("slice_expand.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => obj.Name == "service" && obj.Value == "my-service-0").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "service" && obj.Value == "my-service-0").Children.Should() - .HaveCount(1); - parsed.Children.First(obj => obj.Name == "service" && obj.Value == "my-service-0") - .Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "value").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "service" && obj.Value == "my-service-1").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "service" && obj.Value == "my-service-1") - .Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "value").Should().NotBeNull(); - } + [Test] + public void AssignDeep() + { + var template = TerraformLoadTemplate("assign_deep.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.First().Children.First().Children.FirstOrDefault(obj => obj.Name == "foo").Should() + .NotBeNull(); + parsed.Children.First().Children.First().Children.First(obj => obj.Name == "foo").Children.First().Children + .FirstOrDefault(obj => obj.Name == "bar").Should().NotBeNull(); + } - [Test] - public void Structure() - { - var template = TerraformLoadTemplate("structure.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => - obj.Type == HclElement.CommentType && obj.Value == " This is a test structure for the lexer") - .Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "baz").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo" && obj.Value == "baz").Children.Should().HaveCount(2); - parsed.Children.First(obj => obj.Name == "foo" && obj.Value == "baz") - .Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "7").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo" && obj.Value == "baz") - .Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); - } + [Test] + public void Comment() + { + var template = TerraformLoadTemplate("comment.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(7); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Foo").Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Bar ").Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "\n/*\nBaz\n") + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Another") + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Multiple") + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Lines").Should() + .NotBeNull(); + } - [Test] - public void StructureFlatMap() - { - var template = TerraformLoadTemplate("structure_flatmap.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); - parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "7")) - .Should().NotBeNull(); - parsed.Children - .FirstOrDefault(obj => obj.Children.All(child => child.Name == "foo" && child.Value == "bar")).Should() - .NotBeNull(); - } + [Test] + public void CommentCrlf() + { + var template = TerraformLoadTemplate("comment_crlf.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(7); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Foo").Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Bar ").Should() + .NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "\n/*\nBaz\n") + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Another") + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Multiple") + .Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Lines").Should() + .NotBeNull(); + } - [Test] - public void StructureList() - { - var template = TerraformLoadTemplate("structure_list.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); - parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "7")) - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "12")) - .Should().NotBeNull(); - } + [Test] + public void CommentLastLine() + { + var template = TerraformLoadTemplate("comment_lastline.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "foo").Should() + .NotBeNull(); + } - [Test] - public void StructureMulti() - { - var template = TerraformLoadTemplate("structure_multi.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); - parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "7")) - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Children.All(child => child.Name == "key" && child.Value == "12")) - .Should().NotBeNull(); - } + [Test] + public void CommentSingle() + { + var template = TerraformLoadTemplate("comment_single.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Hello").Should() + .NotBeNull(); + } - [Test] - public void Structure2() - { - var template = TerraformLoadTemplate("structure2.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(3); - parsed.Children.FirstOrDefault(obj => - obj.Type == HclElement.CommentType && obj.Value == " This is a test structure for the lexer") - .Should() - .NotBeNull(); - parsed.Children.Count(obj => obj.Name == "foo").Should().Be(2); - parsed.Children - .FirstOrDefault(obj => obj.Children?.All(child => child.Name == "key" && child.Value == "7") ?? false) - .Should().NotBeNull(); - parsed.Children - .FirstOrDefault(obj => obj.Children?.Any(child => child.Name == "foo" && child.Value == "bar") ?? false) - .Should().NotBeNull(); - } + [Test] + public void Complex() + { + var template = TerraformLoadTemplate("complex.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(8); + parsed.Children.FirstOrDefault(obj => obj.Name == "variable" && obj.Value == "groups").Should().NotBeNull(); + + parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "aws").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children + .FirstOrDefault(obj => obj.Name == "access_key" && obj.Value == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children + .FirstOrDefault(obj => obj.Name == "secret_key" && obj.Value == "bar").Should().NotBeNull(); + + parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "do").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "do").Children + .FirstOrDefault(obj => obj.Name == "api_key" && obj.Value == "${var.foo}").Should().NotBeNull(); + + parsed.Children + .FirstOrDefault(obj => + obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Should() + .NotBeNull(); + parsed.Children.First(obj => + obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Children + .FirstOrDefault(obj => obj.Name == "count" && obj.Value == "5").Should().NotBeNull(); + + parsed.Children + .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Children + .FirstOrDefault(obj => obj.Name == "ami" && obj.Value == "${var.foo}").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Children + .FirstOrDefault(obj => obj.Name == "security_groups").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Children + .FirstOrDefault(obj => obj.Name == "network_interface").Should().NotBeNull(); + + parsed.Children + .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Children + .FirstOrDefault(obj => + obj.Name == "security_groups" && obj.Value == "${aws_security_group.firewall.*.id}").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Children + .FirstOrDefault(obj => obj.Name == "VPC" && obj.Value == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Children + .FirstOrDefault(obj => obj.Name == "depends_on").Should().NotBeNull(); + + parsed.Children.FirstOrDefault(obj => obj.Name == "output" && obj.Value == "web_ip").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "output" && obj.Value == "web_ip").Children + .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "${aws_instance.web.private_ip}").Should() + .NotBeNull(); + } - [Test] - public void TerraformHeroku() - { - var template = TerraformLoadTemplate("terraform_heroku.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => obj.Name == "name" && obj.Value == "terraform-test-app").Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "config_vars").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "config_vars").Children.Should().HaveCount(1); - parsed.Children.First(obj => obj.Name == "config_vars").Children - .FirstOrDefault(obj => obj.Name == "FOO" && obj.Value == "bar").Should().NotBeNull(); - } + [Test] + public void ComplexUnicode() + { + var template = TerraformLoadTemplate("complex_unicode.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(8); + parsed.Children.FirstOrDefault(obj => obj.Name == "a۰۱۸" && obj.Value == "foo").Should().NotBeNull(); + } - [Test] - public void TFVars() - { - var template = TerraformLoadTemplate("tfvars.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(3); - parsed.Children.FirstOrDefault(obj => obj.Name == "regularvar" && obj.Value == "Should work").Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "map.key1" && obj.Value == "Value").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "map.key2" && obj.Value == "Other value").Should() - .NotBeNull(); - } + [Test] + public void ComplexCrlf() + { + var template = TerraformLoadTemplate("complex_crlf.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(8); + parsed.Children.FirstOrDefault(obj => obj.Name == "variable" && obj.Value == "groups").Should().NotBeNull(); + + parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "aws").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children + .FirstOrDefault(obj => obj.Name == "access_key" && obj.Value == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children + .FirstOrDefault(obj => obj.Name == "secret_key" && obj.Value == "bar").Should().NotBeNull(); + + parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "do").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "do").Children + .FirstOrDefault(obj => obj.Name == "api_key" && obj.Value == "${var.foo}").Should().NotBeNull(); + + parsed.Children + .FirstOrDefault(obj => + obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Should() + .NotBeNull(); + parsed.Children.First(obj => + obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Children + .FirstOrDefault(obj => obj.Name == "count" && obj.Value == "5").Should().NotBeNull(); + + parsed.Children + .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Children + .FirstOrDefault(obj => obj.Name == "ami" && obj.Value == "${var.foo}").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Children + .FirstOrDefault(obj => obj.Name == "security_groups").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") + .Children + .FirstOrDefault(obj => obj.Name == "network_interface").Should().NotBeNull(); + + parsed.Children + .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Children + .FirstOrDefault(obj => + obj.Name == "security_groups" && obj.Value == "${aws_security_group.firewall.*.id}").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Children + .FirstOrDefault(obj => obj.Name == "VPC" && obj.Value == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") + .Children + .FirstOrDefault(obj => obj.Name == "depends_on").Should().NotBeNull(); + + parsed.Children.FirstOrDefault(obj => obj.Name == "output" && obj.Value == "web_ip").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "output" && obj.Value == "web_ip").Children + .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "${aws_instance.web.private_ip}").Should() + .NotBeNull(); + } - [Test] - public void EscapedInterpolation() - { - var template = TerraformLoadTemplate("escaped_interpolation.txt"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children - .FirstOrDefault(obj => obj.Name == "one" && obj.Value == "$${replace(var.sub_domain, \".\", \"\\.\")}") - .Should().NotBeNull(); - } + [Test] + public void ComplexKey() + { + var template = TerraformLoadTemplate("complex_key.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo.bar" && obj.Value == "baz").Should().NotBeNull(); + } - [Test] - public void ArrayComment() + [Test] + public void KeyWithoutValue() + { + try { - var template = TerraformLoadTemplate("array_comment.hcl"); + var template = TerraformLoadTemplate("key_without_value.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.First().Children.FirstOrDefault(obj => obj.Value == "1").Should().NotBeNull(); - parsed.Children.First().Children.FirstOrDefault(obj => obj.Value == "2").Should().NotBeNull(); - parsed.Children.First().Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType).Should() - .NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void AssignDeep() + catch (ParseException) { - var template = TerraformLoadTemplate("assign_deep.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.First().Children.First().Children.FirstOrDefault(obj => obj.Name == "foo").Should() - .NotBeNull(); - parsed.Children.First().Children.First().Children.First(obj => obj.Name == "foo").Children.First().Children - .FirstOrDefault(obj => obj.Name == "bar").Should().NotBeNull(); + // all good } + } - [Test] - public void Comment() - { - var template = TerraformLoadTemplate("comment.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(7); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Foo").Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Bar ").Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "\n/*\nBaz\n") - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Another") - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Multiple") - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Lines").Should() - .NotBeNull(); - } + [Test] + public void List() + { + var template = TerraformLoadTemplate("list.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "1").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "2").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "foo").Should() + .NotBeNull(); + } - [Test] - public void CommentCrlf() - { - var template = TerraformLoadTemplate("comment_crlf.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(7); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Foo").Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Bar ").Should() - .NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "\n/*\nBaz\n") - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Another") - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Multiple") - .Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Lines").Should() - .NotBeNull(); - } + [Test] + public void ListComma() + { + var template = TerraformLoadTemplate("list_comma.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "1").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "2").Should() + .NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "foo").Should() + .NotBeNull(); + } - [Test] - public void CommentLastLine() - { - var template = TerraformLoadTemplate("comment_lastline.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == "foo").Should() - .NotBeNull(); - } + [Test] + public void Multiple() + { + var template = TerraformLoadTemplate("multiple.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(2); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "7").Should().NotBeNull(); + } - [Test] - public void CommentSingle() + [Test] + public void ObjectKeyAssignWithoutValue() + { + try { - var template = TerraformLoadTemplate("comment_single.hcl"); + var template = TerraformLoadTemplate("object_key_assign_without_value.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Type == HclElement.CommentType && obj.Value == " Hello").Should() - .NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void Complex() + catch (ParseException) { - var template = TerraformLoadTemplate("complex.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(8); - parsed.Children.FirstOrDefault(obj => obj.Name == "variable" && obj.Value == "groups").Should().NotBeNull(); - - parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "aws").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children - .FirstOrDefault(obj => obj.Name == "access_key" && obj.Value == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children - .FirstOrDefault(obj => obj.Name == "secret_key" && obj.Value == "bar").Should().NotBeNull(); - - parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "do").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "do").Children - .FirstOrDefault(obj => obj.Name == "api_key" && obj.Value == "${var.foo}").Should().NotBeNull(); - - parsed.Children - .FirstOrDefault(obj => - obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Should() - .NotBeNull(); - parsed.Children.First(obj => - obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Children - .FirstOrDefault(obj => obj.Name == "count" && obj.Value == "5").Should().NotBeNull(); - - parsed.Children - .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Children - .FirstOrDefault(obj => obj.Name == "ami" && obj.Value == "${var.foo}").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Children - .FirstOrDefault(obj => obj.Name == "security_groups").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Children - .FirstOrDefault(obj => obj.Name == "network_interface").Should().NotBeNull(); - - parsed.Children - .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Children - .FirstOrDefault(obj => - obj.Name == "security_groups" && obj.Value == "${aws_security_group.firewall.*.id}").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Children - .FirstOrDefault(obj => obj.Name == "VPC" && obj.Value == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Children - .FirstOrDefault(obj => obj.Name == "depends_on").Should().NotBeNull(); - - parsed.Children.FirstOrDefault(obj => obj.Name == "output" && obj.Value == "web_ip").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "output" && obj.Value == "web_ip").Children - .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "${aws_instance.web.private_ip}").Should() - .NotBeNull(); + // all good } + } - [Test] - public void ComplexUnicode() + [Test] + public void ObjectKeyAssignWithoutValue2() + { + try { - var template = TerraformLoadTemplate("complex_unicode.hcl"); + var template = TerraformLoadTemplate("object_key_assign_without_value2.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(8); - parsed.Children.FirstOrDefault(obj => obj.Name == "a۰۱۸" && obj.Value == "foo").Should().NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void ComplexCrlf() + catch (ParseException) { - var template = TerraformLoadTemplate("complex_crlf.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(8); - parsed.Children.FirstOrDefault(obj => obj.Name == "variable" && obj.Value == "groups").Should().NotBeNull(); - - parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "aws").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children - .FirstOrDefault(obj => obj.Name == "access_key" && obj.Value == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "aws").Children - .FirstOrDefault(obj => obj.Name == "secret_key" && obj.Value == "bar").Should().NotBeNull(); - - parsed.Children.FirstOrDefault(obj => obj.Name == "provider" && obj.Value == "do").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "provider" && obj.Value == "do").Children - .FirstOrDefault(obj => obj.Name == "api_key" && obj.Value == "${var.foo}").Should().NotBeNull(); - - parsed.Children - .FirstOrDefault(obj => - obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Should() - .NotBeNull(); - parsed.Children.First(obj => - obj.Name == "resource" && obj.Value == "aws_security_group" && obj.Type == "firewall").Children - .FirstOrDefault(obj => obj.Name == "count" && obj.Value == "5").Should().NotBeNull(); - - parsed.Children - .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Children - .FirstOrDefault(obj => obj.Name == "ami" && obj.Value == "${var.foo}").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Children - .FirstOrDefault(obj => obj.Name == "security_groups").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "web") - .Children - .FirstOrDefault(obj => obj.Name == "network_interface").Should().NotBeNull(); - - parsed.Children - .FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Children - .FirstOrDefault(obj => - obj.Name == "security_groups" && obj.Value == "${aws_security_group.firewall.*.id}").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Children - .FirstOrDefault(obj => obj.Name == "VPC" && obj.Value == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "resource" && obj.Value == "aws_instance" && obj.Type == "db") - .Children - .FirstOrDefault(obj => obj.Name == "depends_on").Should().NotBeNull(); - - parsed.Children.FirstOrDefault(obj => obj.Name == "output" && obj.Value == "web_ip").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "output" && obj.Value == "web_ip").Children - .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "${aws_instance.web.private_ip}").Should() - .NotBeNull(); + // all good } + } - [Test] - public void ComplexKey() + [Test] + public void ObjectKeyAssignWithoutValue3() + { + try { - var template = TerraformLoadTemplate("complex_key.hcl"); + var template = TerraformLoadTemplate("object_key_assign_without_value3.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo.bar" && obj.Value == "baz").Should().NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void KeyWithoutValue() + catch (ParseException) { - try - { - var template = TerraformLoadTemplate("key_without_value.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + // all good } + } - [Test] - public void List() + [Test] + public void ObjectKeyWithoutValue() + { + try { - var template = TerraformLoadTemplate("list.hcl"); + var template = TerraformLoadTemplate("object_key_without_value.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "1").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "2").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "foo").Should() - .NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void ListComma() + catch (ParseException) { - var template = TerraformLoadTemplate("list_comma.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "1").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "2").Should() - .NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children.FirstOrDefault(obj => obj.Value == "foo").Should() - .NotBeNull(); + // all good } + } - [Test] - public void Multiple() - { - var template = TerraformLoadTemplate("multiple.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(2); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "key" && obj.Value == "7").Should().NotBeNull(); - } + [Test] + public void ObjectListComma() + { + var template = TerraformLoadTemplate("object_list_comma.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children + .FirstOrDefault(obj => obj.Name == "one" && obj.Value == "1").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children + .FirstOrDefault(obj => obj.Name == "two" && obj.Value == "2").Should().NotBeNull(); + } - [Test] - public void ObjectKeyAssignWithoutValue() - { - try - { - var template = TerraformLoadTemplate("object_key_assign_without_value.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } - } + [Test] + public void StructureBasic() + { + var template = TerraformLoadTemplate("structure_basic.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children + .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "7").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children + .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "8").Should().NotBeNull(); + parsed.Children.First(obj => obj.Name == "foo").Children + .FirstOrDefault(obj => obj.Name == "complex::value" && obj.Value == "9").Should().NotBeNull(); + } - [Test] - public void ObjectKeyAssignWithoutValue2() - { - try - { - var template = TerraformLoadTemplate("object_key_assign_without_value2.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } - } + [Test] + public void StructureEmpty() + { + var template = TerraformLoadTemplate("structure_empty.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(1); + parsed.Children.FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "foo" && obj.Type == "bar") + .Should().NotBeNull(); + } - [Test] - public void ObjectKeyAssignWithoutValue3() - { - try - { - var template = TerraformLoadTemplate("object_key_assign_without_value3.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } - } + [Test] + public void Types() + { + var template = TerraformLoadTemplate("types.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + parsed.Children.Should().HaveCount(7); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "7").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "baz").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "-12").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "3.14159").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "true").Should().NotBeNull(); + parsed.Children.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "false").Should().NotBeNull(); + } - [Test] - public void ObjectKeyWithoutValue() + [Test] + public void MultilineNoMarker() + { + try { - try - { - var template = TerraformLoadTemplate("object_key_without_value.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + var template = TerraformLoadTemplate("multiline_no_marker.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + throw new Exception("Parsing should have failed"); } - - [Test] - public void ObjectListComma() + catch (ParseException) { - var template = TerraformLoadTemplate("object_list_comma.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children - .FirstOrDefault(obj => obj.Name == "one" && obj.Value == "1").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children - .FirstOrDefault(obj => obj.Name == "two" && obj.Value == "2").Should().NotBeNull(); + // all good } + } - [Test] - public void StructureBasic() + [Test] + public void UnterminatedObject() + { + try { - var template = TerraformLoadTemplate("structure_basic.hcl"); + var template = TerraformLoadTemplate("unterminated_object.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children - .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "7").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children - .FirstOrDefault(obj => obj.Name == "value" && obj.Value == "8").Should().NotBeNull(); - parsed.Children.First(obj => obj.Name == "foo").Children - .FirstOrDefault(obj => obj.Name == "complex::value" && obj.Value == "9").Should().NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void StructureEmpty() + catch (ParseException) { - var template = TerraformLoadTemplate("structure_empty.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(1); - parsed.Children.FirstOrDefault(obj => obj.Name == "resource" && obj.Value == "foo" && obj.Type == "bar") - .Should().NotBeNull(); + // all good } + } - [Test] - public void Types() + [Test] + public void UnterminatedObject2() + { + try { - var template = TerraformLoadTemplate("types.hcl"); + var template = TerraformLoadTemplate("unterminated_object_2.hcl"); var parsed = HclParser.HclTemplate.Parse(template); - parsed.Children.Should().HaveCount(7); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "bar").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "7").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "baz").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "-12").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "3.14159").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "foo" && obj.Value == "true").Should().NotBeNull(); - parsed.Children.FirstOrDefault(obj => obj.Name == "bar" && obj.Value == "false").Should().NotBeNull(); + throw new Exception("Parsing should have failed"); } - - [Test] - public void MultilineNoMarker() + catch (ParseException) { - try - { - var template = TerraformLoadTemplate("multiline_no_marker.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + // all good } + } - [Test] - public void UnterminatedObject() + [Test] + [Ignore( + "Need to fix this. Leads to a false positive, but that is OK for now, a known issue. The parser works for our needs as long as it never has false negatives, so false positives are ok now. but some of the feedback in the bug bash was to provide better error messages for invalid scripts, which means having a more accurate parser")] + public void ArrayComment2() + { + try { - try - { - var template = TerraformLoadTemplate("unterminated_object.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + var template = TerraformLoadTemplate("array_comment_2.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + throw new Exception("Parsing should have failed"); } - - [Test] - public void UnterminatedObject2() + catch (ParseException) { - try - { - var template = TerraformLoadTemplate("unterminated_object_2.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + // all good } + } - [Test] - [Ignore( - "Need to fix this. Leads to a false positive, but that is OK for now, a known issue. The parser works for our needs as long as it never has false negatives, so false positives are ok now. but some of the feedback in the bug bash was to provide better error messages for invalid scripts, which means having a more accurate parser")] - public void ArrayComment2() + [Test] + public void GitCrypt() + { + try { - try - { - var template = TerraformLoadTemplate("array_comment_2.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + var template = TerraformLoadTemplate("git_crypt.hcl"); + var parsed = HclParser.HclTemplate.Parse(template); + throw new Exception("Parsing should have failed"); } - - [Test] - public void GitCrypt() + catch (ParseException) { - try - { - var template = TerraformLoadTemplate("git_crypt.hcl"); - var parsed = HclParser.HclTemplate.Parse(template); - throw new Exception("Parsing should have failed"); - } - catch (ParseException) - { - // all good - } + // all good } + } - [Test] - public void NumberRegex() - { - HclParser.NumberRegex.Match("1.0").Value.Should().Match("1.0"); - HclParser.NumberRegex.Match("-1.0").Value.Should().Match("-1.0"); - HclParser.NumberRegex.Match("-1.02").Value.Should().Match("-1.02"); - HclParser.NumberRegex.Match("-100000.02").Value.Should().Match("-100000.02"); - HclParser.NumberRegex.Match("1e-10").Value.Should().Match("1e-10"); - HclParser.NumberRegex.Match("1e+10").Value.Should().Match("1e+10"); - HclParser.NumberRegex.Match("1e10").Value.Should().Match("1e10"); - HclParser.NumberRegex.Match("1.2e-10").Value.Should().Match("1.2e-10"); - HclParser.NumberRegex.Match("1.2e10").Value.Should().Match("1.2e10"); - HclParser.NumberRegex.Match("1").Value.Should().Match("1"); - HclParser.NumberRegex.Match("1000").Value.Should().Match("1000"); - HclParser.NumberRegex.Match("0x1").Value.Should().Match("0x1"); - HclParser.NumberRegex.Match("-0x1").Value.Should().Match("-0x1"); - } + [Test] + public void NumberRegex() + { + HclParser.NumberRegex.Match("1.0").Value.Should().Match("1.0"); + HclParser.NumberRegex.Match("-1.0").Value.Should().Match("-1.0"); + HclParser.NumberRegex.Match("-1.02").Value.Should().Match("-1.02"); + HclParser.NumberRegex.Match("-100000.02").Value.Should().Match("-100000.02"); + HclParser.NumberRegex.Match("1e-10").Value.Should().Match("1e-10"); + HclParser.NumberRegex.Match("1e+10").Value.Should().Match("1e+10"); + HclParser.NumberRegex.Match("1e10").Value.Should().Match("1e10"); + HclParser.NumberRegex.Match("1.2e-10").Value.Should().Match("1.2e-10"); + HclParser.NumberRegex.Match("1.2e10").Value.Should().Match("1.2e10"); + HclParser.NumberRegex.Match("1").Value.Should().Match("1"); + HclParser.NumberRegex.Match("1000").Value.Should().Match("1000"); + HclParser.NumberRegex.Match("0x1").Value.Should().Match("0x1"); + HclParser.NumberRegex.Match("-0x1").Value.Should().Match("-0x1"); + } - [Test] - public void TestVariableParsing() - { - var template = @"variable ""test"" { + [Test] + public void TestVariableParsing() + { + var template = @"variable ""test"" { type = ""string"" } @@ -1322,11 +1322,10 @@ public void TestVariableParsing() type = ""map"" }"; - var result = HclParser.HclTemplate.Parse(template); - result.Children.Count().Should().Be(3); - result.Children.First(c => c.Value == "test").Child.Value.Should().Be("string"); - result.Children.First(c => c.Value == "list").Child.Value.Should().Be("list"); - result.Children.First(c => c.Value == "map").Child.Value.Should().Be("map"); - } + var result = HclParser.HclTemplate.Parse(template); + result.Children.Count().Should().Be(3); + result.Children.First(c => c.Value == "test").Child.Value.Should().Be("string"); + result.Children.First(c => c.Value == "list").Child.Value.Should().Be("list"); + result.Children.First(c => c.Value == "map").Child.Value.Should().Be("map"); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/TerraformTemplateLoader.cs b/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/TerraformTemplateLoader.cs index b572999..94d0e49 100644 --- a/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/TerraformTemplateLoader.cs +++ b/source/Octopus.Core.Parsers.Hcl.Tests/Octopus/CoreParsers/Hcl/TerraformTemplateLoader.cs @@ -1,18 +1,16 @@ -using System; -using System.IO; +using System.IO; using System.Reflection; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class TerraformTemplateLoader { - public class TerraformTemplateLoader + protected string TerraformLoadTemplate(string fileName, string directory = "TemplateSamples") { - protected string TerraformLoadTemplate(string fileName, string directory = "TemplateSamples") - { - var templatesPath = Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - Path.Combine("Octopus", "CoreParsers", "Hcl", directory)); + var templatesPath = Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + Path.Combine("Octopus", "CoreParsers", "Hcl", directory)); - return HclParser.NormalizeLineEndings(File.ReadAllText(Path.Combine(templatesPath, fileName))).Trim(); - } + return HclParser.NormalizeLineEndings(File.ReadAllText(Path.Combine(templatesPath, fileName))).Trim(); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclCommentElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclCommentElement.cs index a2b2bea..e2c2fdd 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclCommentElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclCommentElement.cs @@ -1,20 +1,19 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a single line comment +/// +public class HclCommentElement : HclElement { - /// - /// Represents a single line comment - /// - public class HclCommentElement : HclElement - { - public override string Type => CommentType; + public override string Type => CommentType; - public override string ProcessedValue => Value ?? ""; + public override string ProcessedValue => Value ?? ""; - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - return indentString + string.Join("\n", ProcessedValue.Split('\n').Select(comment => "#" + comment)); - } + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + return indentString + string.Join("\n", ProcessedValue.Split('\n').Select(comment => "#" + comment)); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclElement.cs index 4989cff..937f99f 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclElement.cs @@ -3,266 +3,265 @@ using System.Linq; using Sprache; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclElement { - public class HclElement + /// + /// The name of comment objects + /// + public const string CommentType = "#COMMENT"; + + /// + /// Name of the root document + /// + public const string RootType = "#ROOT"; + + /// + /// The type for string, number and boolean elements + /// + public const string StringType = "String"; + + /// + /// The type for string, number and boolean elements + /// + public const string NumOrBool = "NumOrBool"; + + /// + /// The type for an unquoted string or expression + /// + public const string UnquotedType = "Unquoted"; + + /// + /// The type for a math symbol + /// + public const string MathSymbol = "MathSymbol"; + + /// + /// The type for multiline string + /// + public const string HeredocStringType = "HeredocString"; + + /// + /// The type for list elements + /// + public const string ListType = "List"; + + /// + /// The type for map elements + /// + public const string MapType = "Map"; + + /// + /// The type for properties holding types like set(), map(), list(), object() or tuple() + /// + public const string TypePropertyType = "TypeProperty"; + + /// + /// The type for string, number and boolean elements + /// + public const string SimplePropertyType = "SimpleProperty"; + + /// + /// The type for string, number and boolean elements + /// + public const string HeredocStringPropertyType = "HeredocStringProperty"; + + /// + /// The type for list elements + /// + public const string ListPropertyType = "ListProperty"; + + /// + /// The type for map elements + /// + public const string MapPropertyType = "MapProperty"; + + /// + /// The type defining an object property + /// + public const string FunctionType = "FunctionType"; + + /// + /// The type defining an object property + /// + public const string ObjectPropertyType = "ObjectProperty"; + + /// + /// The type defining an tuple property + /// + public const string TuplePropertyType = "TupleProperty"; + + /// + /// The type defining an set property + /// + public const string SetPropertyType = "SetProperty"; + + /// + /// The type defining an primitave property + /// + public const string PrimitivePropertyType = "PrimitiveProperty"; + + /// + /// The name of the element, #COMMENT for comments, or #ROOT for the + /// root document element. + /// e.g. variable, resource + /// This is also the name of a property, e.g. "tags" for the property "tags" = ["a", "b"] + /// + public virtual string Name { get; set; } + + /// + /// True if the name was originally in quotes, and false otherwise + /// + public virtual bool NameQuoted { get; set; } = false; + + /// + /// Returns the string that is used for the element name. If it was originally + /// quoted, then it will be quoted here. Otherwise the plain name is returned. + /// + public virtual string OriginalName { - /// - /// The name of comment objects - /// - public const string CommentType = "#COMMENT"; - - /// - /// Name of the root document - /// - public const string RootType = "#ROOT"; - - /// - /// The type for string, number and boolean elements - /// - public const string StringType = "String"; - - /// - /// The type for string, number and boolean elements - /// - public const string NumOrBool = "NumOrBool"; - - /// - /// The type for an unquoted string or expression - /// - public const string UnquotedType = "Unquoted"; - - /// - /// The type for a math symbol - /// - public const string MathSymbol = "MathSymbol"; - - /// - /// The type for multiline string - /// - public const string HeredocStringType = "HeredocString"; - - /// - /// The type for list elements - /// - public const string ListType = "List"; - - /// - /// The type for map elements - /// - public const string MapType = "Map"; - - /// - /// The type for properties holding types like set(), map(), list(), object() or tuple() - /// - public const string TypePropertyType = "TypeProperty"; - - /// - /// The type for string, number and boolean elements - /// - public const string SimplePropertyType = "SimpleProperty"; - - /// - /// The type for string, number and boolean elements - /// - public const string HeredocStringPropertyType = "HeredocStringProperty"; - - /// - /// The type for list elements - /// - public const string ListPropertyType = "ListProperty"; - - /// - /// The type for map elements - /// - public const string MapPropertyType = "MapProperty"; - - /// - /// The type defining an object property - /// - public const string FunctionType = "FunctionType"; - - /// - /// The type defining an object property - /// - public const string ObjectPropertyType = "ObjectProperty"; - - /// - /// The type defining an tuple property - /// - public const string TuplePropertyType = "TupleProperty"; - - /// - /// The type defining an set property - /// - public const string SetPropertyType = "SetProperty"; - - /// - /// The type defining an primitave property - /// - public const string PrimitivePropertyType = "PrimitiveProperty"; - - /// - /// The name of the element, #COMMENT for comments, or #ROOT for the - /// root document element. - /// e.g. variable, resource - /// This is also the name of a property, e.g. "tags" for the property "tags" = ["a", "b"] - /// - public virtual string Name { get; set; } - - /// - /// True if the name was originally in quotes, and false otherwise - /// - public virtual bool NameQuoted { get; set; } = false; - - /// - /// Returns the string that is used for the element name. If it was originally - /// quoted, then it will be quoted here. Otherwise the plain name is returned. - /// - public virtual string OriginalName + get { - get - { - if (NameQuoted) return "\"" + EscapeQuotes(Name) + "\""; - - return Name; - } - } + if (NameQuoted) return "\"" + EscapeQuotes(Name) + "\""; - /// - /// The value of the element, or the comment contents - /// e.g. my_variable, aws_instance - /// This is also the value of a property - /// e.g. "myvalue" for "myproperty" = "myvalue". - /// For complex properties, like lists, objects, maps etc, Value is the string representation of those objects - /// e.g. "[1, 2]" for "myproperty" = [1, 2]. - /// - public virtual string Value { get; set; } - - /// - /// The processed value of the element. Defaults to the same as the Value, - /// but for specialised types this can be a refined value. - /// - public virtual string ProcessedValue => Value; - - /// - /// The type of the resource - /// e.g. ec2 - /// - public virtual string Type { get; set; } - - /// - /// Any child elements - /// - public virtual IEnumerable Children { get; set; } - - /// - /// A conveninece method to treat the Children collection as a single child - /// - public virtual HclElement Child - { - get => Children?.FirstOrDefault(); - set => Children = value is not null ? new[] { value } : null; + return Name; } + } - /// - /// Generates the string used to indent the printed output - /// - /// The indent amount - /// The indent string - protected string GetIndent(int indent) - { - return indent >= 0 - ? new string(' ', indent * 2) - : string.Empty; - } + /// + /// The value of the element, or the comment contents + /// e.g. my_variable, aws_instance + /// This is also the value of a property + /// e.g. "myvalue" for "myproperty" = "myvalue". + /// For complex properties, like lists, objects, maps etc, Value is the string representation of those objects + /// e.g. "[1, 2]" for "myproperty" = [1, 2]. + /// + public virtual string Value { get; set; } + + /// + /// The processed value of the element. Defaults to the same as the Value, + /// but for specialised types this can be a refined value. + /// + public virtual string ProcessedValue => Value; + + /// + /// The type of the resource + /// e.g. ec2 + /// + public virtual string Type { get; set; } + + /// + /// Any child elements + /// + public virtual IEnumerable Children { get; set; } + + /// + /// A conveninece method to treat the Children collection as a single child + /// + public virtual HclElement Child + { + get => Children?.FirstOrDefault(); + set => Children = value is not null ? new[] { value } : null; + } - public virtual string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; - - return indentString + OriginalName + - RunIfNotNull(ProcessedValue, a => " \"" + EscapeQuotes(a) + "\"") + - RunIfNotNull(Type, a => " \"" + EscapeQuotes(a) + "\"") + - " {" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + "}"; - } + /// + /// Generates the string used to indent the printed output + /// + /// The indent amount + /// The indent string + protected string GetIndent(int indent) + { + return indent >= 0 + ? new string(' ', indent * 2) + : string.Empty; + } - public string ToString(int indent) - { - return ToString(false, indent); - } + public virtual string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; + + return indentString + OriginalName + + RunIfNotNull(ProcessedValue, a => " \"" + EscapeQuotes(a) + "\"") + + RunIfNotNull(Type, a => " \"" + EscapeQuotes(a) + "\"") + + " {" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + "}"; + } - public override string ToString() - { - return ToString(false, 0); - } + public string ToString(int indent) + { + return ToString(false, indent); + } - public override bool Equals(object obj) - { - if (!(obj is HclElement)) - return false; + public override string ToString() + { + return ToString(false, 0); + } - var hclElement = obj as HclElement; + public override bool Equals(object obj) + { + if (!(obj is HclElement)) + return false; - if (hclElement.Name != Name) - return false; + var hclElement = obj as HclElement; - if (hclElement.Value != Value) - return false; + if (hclElement.Name != Name) + return false; - if (hclElement.Type != Type) - return false; + if (hclElement.Value != Value) + return false; - if (hclElement.Children == null && Children != null) - return false; + if (hclElement.Type != Type) + return false; - if (hclElement.Children != null && Children == null) - return false; + if (hclElement.Children == null && Children != null) + return false; - if (Children != null && hclElement.Children != null) - { - var myChildren = Children.ToArray(); - var theirChidlren = hclElement.Children.ToArray(); + if (hclElement.Children != null && Children == null) + return false; - if (myChildren.Length != theirChidlren.Length) - return false; + if (Children != null && hclElement.Children != null) + { + var myChildren = Children.ToArray(); + var theirChidlren = hclElement.Children.ToArray(); - for (var i = 0; i < myChildren.Length; ++i) - if (!myChildren[i].Equals(theirChidlren[i])) - return false; - } + if (myChildren.Length != theirChidlren.Length) + return false; - return true; + for (var i = 0; i < myChildren.Length; ++i) + if (!myChildren[i].Equals(theirChidlren[i])) + return false; } - public override int GetHashCode() - { - var hash = 17; + return true; + } - hash = hash * 23 + (Name?.GetHashCode() ?? 0); - hash = hash * 23 + (Value?.GetHashCode() ?? 0); - hash = hash * 23 + (Type?.GetHashCode() ?? 0); - Children?.ToList().ForEach(child => hash = hash * 23 + (child?.GetHashCode() ?? 0)); + public override int GetHashCode() + { + var hash = 17; - return hash; - } + hash = hash * 23 + (Name?.GetHashCode() ?? 0); + hash = hash * 23 + (Value?.GetHashCode() ?? 0); + hash = hash * 23 + (Type?.GetHashCode() ?? 0); + Children?.ToList().ForEach(child => hash = hash * 23 + (child?.GetHashCode() ?? 0)); - protected static string EscapeQuotes(string input) - { - if (input == null) throw new ArgumentException("input can not be null"); + return hash; + } - return HclParser.StringLiteralQuoteContentReverse.Parse(input); - } + protected static string EscapeQuotes(string input) + { + if (input == null) throw new ArgumentException("input can not be null"); - private static TResult RunIfNotNull(TSource input, Func command) - { - return input is null ? default : command(input); - } + return HclParser.StringLiteralQuoteContentReverse.Parse(input); + } + + private static TResult RunIfNotNull(TSource input, Func command) + { + return input is null ? default : command(input); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocElement.cs index 5f13d9a..f346aa0 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocElement.cs @@ -1,46 +1,45 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +public class HclHereDocElement : HclElement { - /// - public class HclHereDocElement : HclElement - { - /// - /// true if this heredoc is a trimmed version, and false otherwise - /// - public bool Trimmed { get; set; } + /// + /// true if this heredoc is a trimmed version, and false otherwise + /// + public bool Trimmed { get; set; } - /// - /// The Heredoc marker e.g. EOF - /// - public string Marker { get; set; } + /// + /// The Heredoc marker e.g. EOF + /// + public string Marker { get; set; } - public override string Type => HeredocStringType; + public override string Type => HeredocStringType; - /// - /// Returns the original heredoc if it is not trimmed, or the trimmed version - /// - public override string ProcessedValue + /// + /// Returns the original heredoc if it is not trimmed, or the trimmed version + /// + public override string ProcessedValue + { + get { - get - { - if (!Trimmed) return Value; + if (!Trimmed) return Value; - return Value?.Split('\n') - .Select(value => value.TrimStart()) - .Aggregate("", (total, current) => total + "\n" + current); - } + return Value?.Split('\n') + .Select(value => value.TrimStart()) + .Aggregate("", (total, current) => total + "\n" + current); } + } - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - var markerPrefix = Trimmed ? "<<-" : "<<"; - return indentString + markerPrefix + Marker + Value + Marker; - } + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + var markerPrefix = Trimmed ? "<<-" : "<<"; + return indentString + markerPrefix + Marker + Value + Marker; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocPropertyElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocPropertyElement.cs index 5d200a8..b63cdd2 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocPropertyElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclHereDocPropertyElement.cs @@ -1,20 +1,19 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +public class HclHereDocPropertyElement : HclHereDocElement { - /// - public class HclHereDocPropertyElement : HclHereDocElement - { - public override string Type => HeredocStringPropertyType; + public override string Type => HeredocStringPropertyType; - public override string ToString(bool naked, int indent) - { - if (naked) return base.ToString(true, indent); + public override string ToString(bool naked, int indent) + { + if (naked) return base.ToString(true, indent); - var indentString = GetIndent(indent); - return indentString + OriginalName + " = " + base.ToString(false, 0); - } + var indentString = GetIndent(indent); + return indentString + OriginalName + " = " + base.ToString(false, 0); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListElement.cs index 9565809..a412a74 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListElement.cs @@ -1,42 +1,41 @@ using System.Linq; using System.Text.RegularExpressions; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a list +/// +public class HclListElement : HclElement { - /// - /// Represents a list - /// - public class HclListElement : HclElement + public override string Type => ListType; + + public override string Value => ToString(-1); + + public override string ToString(bool naked, int indent) { - public override string Type => ListType; - - public override string Value => ToString(-1); - - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - return indentString + PrintArray(indent); - } - - protected string PrintArray(int indent) - { - var indentString = GetIndent(indent); - var nextIndentString = GetIndent(indent + 1); - var lineBreak = indent == -1 ? string.Empty : "\n"; - - var startArray = "[" + lineBreak + - Children?.Aggregate("", (total, child) => - { - // Comments appear without a comma at the end - var suffix = child.Type != CommentType ? ", " : ""; - return total + nextIndentString + child + suffix + lineBreak; - }); - - // Retain the comma at the end (if one exists) if the last element is a comment - if (Children?.LastOrDefault()?.Type != CommentType) - startArray = new Regex(",$").Replace(startArray.TrimEnd(), ""); - - return startArray + lineBreak + indentString + "]"; - } + var indentString = GetIndent(indent); + return indentString + PrintArray(indent); + } + + protected string PrintArray(int indent) + { + var indentString = GetIndent(indent); + var nextIndentString = GetIndent(indent + 1); + var lineBreak = indent == -1 ? string.Empty : "\n"; + + var startArray = "[" + lineBreak + + Children?.Aggregate("", (total, child) => + { + // Comments appear without a comma at the end + var suffix = child.Type != CommentType ? ", " : ""; + return total + nextIndentString + child + suffix + lineBreak; + }); + + // Retain the comma at the end (if one exists) if the last element is a comment + if (Children?.LastOrDefault()?.Type != CommentType) + startArray = new Regex(",$").Replace(startArray.TrimEnd(), ""); + + return startArray + lineBreak + indentString + "]"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListPropertyElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListPropertyElement.cs index d6b0a7d..5733dca 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListPropertyElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListPropertyElement.cs @@ -1,20 +1,19 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a list assigned to a property +/// +public class HclListPropertyElement : HclListElement { - /// - /// Represents a list assigned to a property - /// - public class HclListPropertyElement : HclListElement - { - public override string Type => ListPropertyType; + public override string Type => ListPropertyType; - public override string Value => PrintArray(-1); + public override string Value => PrintArray(-1); - public override string ToString(bool naked, int indent) - { - if (naked) return base.ToString(true, indent); + public override string ToString(bool naked, int indent) + { + if (naked) return base.ToString(true, indent); - var indentString = GetIndent(indent); - return indentString + OriginalName + " = " + PrintArray(indent); - } + var indentString = GetIndent(indent); + return indentString + OriginalName + " = " + PrintArray(indent); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListTypeElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListTypeElement.cs index e42584b..9fdbd57 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListTypeElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclListTypeElement.cs @@ -1,26 +1,25 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclListTypeElement : HclElement { - public class HclListTypeElement : HclElement - { - public override string Type => ObjectPropertyType; + public override string Type => ObjectPropertyType; - public override string ProcessedValue => Value ?? ""; + public override string ProcessedValue => Value ?? ""; - public override string Value => ToString(-1); + public override string Value => ToString(-1); - public override string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return indentString + "list(" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + ")"; - } + return indentString + "list(" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + ")"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapElement.cs index 0bc3167..1656df4 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapElement.cs @@ -1,27 +1,26 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a map +/// +public class HclMapElement : HclElement { - /// - /// Represents a map - /// - public class HclMapElement : HclElement - { - public override string Type => MapType; + public override string Type => MapType; - public override string Value => ToString(-1); + public override string Value => ToString(-1); - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return indentString + "{" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + "}"; - } + return indentString + "{" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + "}"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapPropertyElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapPropertyElement.cs index 31f46d8..9620753 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapPropertyElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapPropertyElement.cs @@ -1,33 +1,32 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a map assigned to a property +/// +public class HclMapPropertyElement : HclMapElement { - /// - /// Represents a map assigned to a property - /// - public class HclMapPropertyElement : HclMapElement - { - public override string Type => MapPropertyType; + public override string Type => MapPropertyType; - public override string Value => "{" + - string.Join(", ", - Children?.Select(child => child.ToString(-1)) ?? - Enumerable.Empty()) + - "}"; + public override string Value => "{" + + string.Join(", ", + Children?.Select(child => child.ToString(-1)) ?? + Enumerable.Empty()) + + "}"; - public override string ToString(bool naked, int indent) - { - if (naked) return base.ToString(true, indent); + public override string ToString(bool naked, int indent) + { + if (naked) return base.ToString(true, indent); - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return indentString + OriginalName + " = {" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + "}"; - } + return indentString + OriginalName + " = {" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + "}"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapTypeElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapTypeElement.cs index 1029fb4..eab5520 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapTypeElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMapTypeElement.cs @@ -1,26 +1,25 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclMapTypeElement : HclElement { - public class HclMapTypeElement : HclElement - { - public override string Type => ObjectPropertyType; + public override string Type => ObjectPropertyType; - public override string ProcessedValue => Value ?? ""; + public override string ProcessedValue => Value ?? ""; - public override string Value => ToString(-1); + public override string Value => ToString(-1); - public override string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return indentString + "map(" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + ")"; - } + return indentString + "map(" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + ")"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMultiLineCommentElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMultiLineCommentElement.cs index 9bde6e5..450f0de 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMultiLineCommentElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclMultiLineCommentElement.cs @@ -1,16 +1,15 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a multiline comment +/// +public class HclMultiLineCommentElement : HclElement { - /// - /// Represents a multiline comment - /// - public class HclMultiLineCommentElement : HclElement - { - public override string Type => CommentType; + public override string Type => CommentType; - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - return indentString + "/*" + ProcessedValue + "*/"; - } + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + return indentString + "/*" + ProcessedValue + "*/"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclNumOrBoolElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclNumOrBoolElement.cs index 255bc5e..6d6e532 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclNumOrBoolElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclNumOrBoolElement.cs @@ -1,16 +1,15 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a string +/// +public class HclNumOrBoolElement : HclElement { - /// - /// Represents a string - /// - public class HclNumOrBoolElement : HclElement - { - public override string Type => NumOrBool; + public override string Type => NumOrBool; - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - return indentString + Value; - } + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + return indentString + Value; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclObjectTypeElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclObjectTypeElement.cs index 70bc101..24a6ea4 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclObjectTypeElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclObjectTypeElement.cs @@ -1,27 +1,26 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a map assigned to a property +/// +public class HclObjectTypeElement : HclElement { - /// - /// Represents a map assigned to a property - /// - public class HclObjectTypeElement : HclElement - { - public override string Type => ObjectPropertyType; + public override string Type => ObjectPropertyType; - public override string Value => ToString(-1); + public override string Value => ToString(-1); - public override string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return "object({" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + "})"; - } + return "object({" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + "})"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclParser.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclParser.cs index 5704955..eadd454 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclParser.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclParser.cs @@ -4,959 +4,958 @@ using System.Text.RegularExpressions; using Sprache; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// A Sprache parser for the HCL library. +/// The goal of this parser is to have no false negatives. Every valid HCL file should be +/// parsed by the Parsers in this class. +/// It it very likely that these parsers will parse templates that are not valid HCL. This +/// is OK though, as the terraform exe will ultimately be the source of truth. +/// +public class HclParser { + private const string StringKeyword = "string"; + private const string BoolKeyword = "bool"; + private const string NumberKeyword = "number"; + private const string AnyKeyword = "any"; + /// - /// A Sprache parser for the HCL library. - /// The goal of this parser is to have no false negatives. Every valid HCL file should be - /// parsed by the Parsers in this class. - /// It it very likely that these parsers will parse templates that are not valid HCL. This - /// is OK though, as the terraform exe will ultimately be the source of truth. + /// The \n char /// - public class HclParser - { - private const string StringKeyword = "string"; - private const string BoolKeyword = "bool"; - private const string NumberKeyword = "number"; - private const string AnyKeyword = "any"; - - /// - /// The \n char - /// - public const char LineBreak = (char)10; - - /// - /// New in 0.12 - the ability to mark a block as dynamic - /// - public static readonly Parser Dynamic = - Parse.String("dynamic").Text().Named("Dynamic configuration block"); - - /// - /// A regex used to normalize line endings - /// - public static readonly Regex LineEndNormalize = new Regex("\r\n?|\n"); - - /// - /// A regex that matches the numbers that can be assigned to a property - /// matches floats, scientific and hex numbers - /// - public static readonly Regex NumberRegex = new Regex(@"-?(0x)?\d+(e(\+|-)?\d+)?(\.\d*(e(\+|-)?\d+)?)?"); - - /// - /// A regex that matches true and false - /// - public static readonly Regex TrueFalse = new Regex(@"true|false", RegexOptions.IgnoreCase); - - /// - /// Represents the equals token - /// - public static readonly Parser Equal = Parse.Char('=').WithWhiteSpace(); - - /// - /// Represents the colon token. This is new in 0.12 as a way of defining maps. - /// - public static readonly Parser Colon = Parse.Char(':').WithWhiteSpace(); - - /// - /// An equals sign or colon can be used for assigment in maps. - /// - public static readonly Parser EqualsOrColon = Equal.Or(Colon); - - /// - /// Open bracket - /// - public static readonly Parser LeftBracket = Parse.Char('(').Token(); - - /// - /// Close bracket - /// - public static readonly Parser RightBracket = Parse.Char(')').Token(); - - /// - /// Array start token - /// - public static readonly Parser LeftSquareBracket = Parse.Char('[').Token(); - - /// - /// Array end token - /// - public static readonly Parser RightSquareBracket = Parse.Char(']').Token(); - - /// - /// Object start token - /// - public static readonly Parser LeftCurly = Parse.Char('{').Token(); - - /// - /// Object end token - /// - public static readonly Parser RightCurly = Parse.Char('}').Token(); - - /// - /// Comma token - /// - public static readonly Parser Comma = Parse.Char(',').Token(); - - /// - /// An escaped interpolation curly - /// - public static readonly Parser EscapedDelimiterStartCurly = - Parse.String("{{").Text().Named("Escaped delimiter"); - - /// - /// An escaped interpolation curly - /// - public static readonly Parser EscapedDelimiterEndCurly = - Parse.String("}}").Text().Named("Escaped delimiter"); - - /// - /// The start of an interpolation marker - /// - public static readonly Parser DelimiterStartInterpolated = - Parse.String("${").Text().Named("Start Interpolation"); - - /// - /// Special interpolation char - /// - public static readonly Parser DelimiterStartCurly = Parse.Char('{').Named("StartCurly"); - - /// - /// Special interpolation char - /// - public static readonly Parser DelimiterEndCurly = Parse.Char('}').Named("EndCurly"); - - /// - /// Special interpolation char - /// - public static readonly Parser DelimiterStartSquare = Parse.Char('[').Named("StartSquare"); - - /// - /// Special interpolation char - /// - public static readonly Parser DelimiterEndSquare = Parse.Char(']').Named("EndSquare"); - - /// - /// Escaped quote - /// - public static readonly Parser EscapedDelimiterQuote = - Parse.String("\\\"").Text().Named("Escaped delimiter"); - - /// - /// Escape char - /// - public static readonly Parser SingleEscapeQuote = - Parse.String("\\").Text().Named("Single escape character"); - - /// - /// Double escape - /// - public static readonly Parser DoubleEscapeQuote = - Parse.String("\\\\").Text().Named("Escaped escape character"); - - /// - /// Quote char - /// - public static readonly Parser DelimiterQuote = Parse.Char('"').Named("Delimiter"); - - /// - /// Start of interpolation - /// - public static readonly Parser DelimiterInterpolation = Parse.Char('$').Named("Interpolated"); - - /// - /// An escaped interpolation start - /// - public static readonly Parser EscapedDelimiterInterpolation = - Parse.Char('$').Repeat(2).Text().Named("Escaped Interpolated"); - - /// - /// An escaped interpolation start - /// - public static readonly Parser DoubleEscapedDelimiterInterpolation = - Parse.Char('$').Repeat(4).Text().Named("Escaped Interpolated"); - - /// - /// A section of a string that does not have any special interpolation tokens - /// - public static readonly Parser SimpleLiteralCurly = - Parse.AnyChar - .Except(EscapedDelimiterStartCurly) - .Except(DelimiterStartInterpolated) - .Except(DelimiterEndCurly) - .Many().Text().Named("Literal without escape/delimiter character"); - - /// - /// A string made up of regular text and interpolation string - /// - public static readonly Parser StringLiteralCurly = - from start in DelimiterStartInterpolated - from v in StringLiteralCurly - .Or(EscapedDelimiterStartCurly) - .Or(EscapedDelimiterEndCurly) - .Or(SimpleLiteralCurly).Many() - from end in DelimiterEndCurly - select start + string.Concat(v) + end; - - /// - /// Any characters that are not escaped. - /// - public static readonly Parser SimpleLiteralQuote = Parse.AnyChar - .Except(SingleEscapeQuote) - .Except(DelimiterQuote) + public const char LineBreak = (char)10; + + /// + /// New in 0.12 - the ability to mark a block as dynamic + /// + public static readonly Parser Dynamic = + Parse.String("dynamic").Text().Named("Dynamic configuration block"); + + /// + /// A regex used to normalize line endings + /// + public static readonly Regex LineEndNormalize = new("\r\n?|\n"); + + /// + /// A regex that matches the numbers that can be assigned to a property + /// matches floats, scientific and hex numbers + /// + public static readonly Regex NumberRegex = new(@"-?(0x)?\d+(e(\+|-)?\d+)?(\.\d*(e(\+|-)?\d+)?)?"); + + /// + /// A regex that matches true and false + /// + public static readonly Regex TrueFalse = new(@"true|false", RegexOptions.IgnoreCase); + + /// + /// Represents the equals token + /// + public static readonly Parser Equal = Parse.Char('=').WithWhiteSpace(); + + /// + /// Represents the colon token. This is new in 0.12 as a way of defining maps. + /// + public static readonly Parser Colon = Parse.Char(':').WithWhiteSpace(); + + /// + /// An equals sign or colon can be used for assigment in maps. + /// + public static readonly Parser EqualsOrColon = Equal.Or(Colon); + + /// + /// Open bracket + /// + public static readonly Parser LeftBracket = Parse.Char('(').Token(); + + /// + /// Close bracket + /// + public static readonly Parser RightBracket = Parse.Char(')').Token(); + + /// + /// Array start token + /// + public static readonly Parser LeftSquareBracket = Parse.Char('[').Token(); + + /// + /// Array end token + /// + public static readonly Parser RightSquareBracket = Parse.Char(']').Token(); + + /// + /// Object start token + /// + public static readonly Parser LeftCurly = Parse.Char('{').Token(); + + /// + /// Object end token + /// + public static readonly Parser RightCurly = Parse.Char('}').Token(); + + /// + /// Comma token + /// + public static readonly Parser Comma = Parse.Char(',').Token(); + + /// + /// An escaped interpolation curly + /// + public static readonly Parser EscapedDelimiterStartCurly = + Parse.String("{{").Text().Named("Escaped delimiter"); + + /// + /// An escaped interpolation curly + /// + public static readonly Parser EscapedDelimiterEndCurly = + Parse.String("}}").Text().Named("Escaped delimiter"); + + /// + /// The start of an interpolation marker + /// + public static readonly Parser DelimiterStartInterpolated = + Parse.String("${").Text().Named("Start Interpolation"); + + /// + /// Special interpolation char + /// + public static readonly Parser DelimiterStartCurly = Parse.Char('{').Named("StartCurly"); + + /// + /// Special interpolation char + /// + public static readonly Parser DelimiterEndCurly = Parse.Char('}').Named("EndCurly"); + + /// + /// Special interpolation char + /// + public static readonly Parser DelimiterStartSquare = Parse.Char('[').Named("StartSquare"); + + /// + /// Special interpolation char + /// + public static readonly Parser DelimiterEndSquare = Parse.Char(']').Named("EndSquare"); + + /// + /// Escaped quote + /// + public static readonly Parser EscapedDelimiterQuote = + Parse.String("\\\"").Text().Named("Escaped delimiter"); + + /// + /// Escape char + /// + public static readonly Parser SingleEscapeQuote = + Parse.String("\\").Text().Named("Single escape character"); + + /// + /// Double escape + /// + public static readonly Parser DoubleEscapeQuote = + Parse.String("\\\\").Text().Named("Escaped escape character"); + + /// + /// Quote char + /// + public static readonly Parser DelimiterQuote = Parse.Char('"').Named("Delimiter"); + + /// + /// Start of interpolation + /// + public static readonly Parser DelimiterInterpolation = Parse.Char('$').Named("Interpolated"); + + /// + /// An escaped interpolation start + /// + public static readonly Parser EscapedDelimiterInterpolation = + Parse.Char('$').Repeat(2).Text().Named("Escaped Interpolated"); + + /// + /// An escaped interpolation start + /// + public static readonly Parser DoubleEscapedDelimiterInterpolation = + Parse.Char('$').Repeat(4).Text().Named("Escaped Interpolated"); + + /// + /// A section of a string that does not have any special interpolation tokens + /// + public static readonly Parser SimpleLiteralCurly = + Parse.AnyChar .Except(EscapedDelimiterStartCurly) .Except(DelimiterStartInterpolated) + .Except(DelimiterEndCurly) .Many().Text().Named("Literal without escape/delimiter character"); - /// - /// Matches the plain text in a string, or the Interpolation block - /// - public static readonly Parser StringLiteralQuoteContent = + /// + /// A string made up of regular text and interpolation string + /// + public static readonly Parser StringLiteralCurly = + from start in DelimiterStartInterpolated + from v in StringLiteralCurly + .Or(EscapedDelimiterStartCurly) + .Or(EscapedDelimiterEndCurly) + .Or(SimpleLiteralCurly).Many() + from end in DelimiterEndCurly + select start + string.Concat(v) + end; + + /// + /// Any characters that are not escaped. + /// + public static readonly Parser SimpleLiteralQuote = Parse.AnyChar + .Except(SingleEscapeQuote) + .Except(DelimiterQuote) + .Except(EscapedDelimiterStartCurly) + .Except(DelimiterStartInterpolated) + .Many().Text().Named("Literal without escape/delimiter character"); + + /// + /// Matches the plain text in a string, or the Interpolation block + /// + public static readonly Parser StringLiteralQuoteContent = + from curly in StringLiteralCurly.Optional() + from content in EscapedDelimiterQuote + .Or(DoubleEscapeQuote) + .Or(SingleEscapeQuote) + .Or(EscapedDelimiterInterpolation) + .Or(DoubleEscapedDelimiterInterpolation) + .Or(EscapedDelimiterStartCurly) + .Or(EscapedDelimiterEndCurly) + .Or(SimpleLiteralQuote).Many() + select curly.GetOrDefault() + Regex.Unescape(string.Concat(content)); + + + /// + /// Matches the plain text in a string, or the Interpolation block + /// + public static readonly Parser StringLiteralQuoteContentReverse = + from combined in ( from curly in StringLiteralCurly.Optional() - from content in EscapedDelimiterQuote - .Or(DoubleEscapeQuote) - .Or(SingleEscapeQuote) - .Or(EscapedDelimiterInterpolation) - .Or(DoubleEscapedDelimiterInterpolation) - .Or(EscapedDelimiterStartCurly) - .Or(EscapedDelimiterEndCurly) - .Or(SimpleLiteralQuote).Many() - select curly.GetOrDefault() + Regex.Unescape(string.Concat(content)); - - - /// - /// Matches the plain text in a string, or the Interpolation block - /// - public static readonly Parser StringLiteralQuoteContentReverse = - from combined in ( - from curly in StringLiteralCurly.Optional() - from content in - EscapedDelimiterInterpolation - .Or(Parse.AnyChar.Except(DelimiterStartInterpolated).Many().Text()) - select curly.GetOrDefault() + EscapeString(content)).Many() - select string.Concat(combined); - - /// - /// Matches the plain text in a string, or the Interpolation block - /// - public static readonly Parser StringLiteralQuoteContentNoInterpolation = - from content in StringLiteralCurly - .Or(EscapedDelimiterQuote) - .Or(DoubleEscapeQuote) - .Or(SingleEscapeQuote) - .Or(EscapedDelimiterInterpolation) - .Or(DoubleEscapedDelimiterInterpolation) - .Or(SimpleLiteralQuote).Many() - select string.Concat(content); - - /// - /// Represents a multiline comment e.g. - /// /* - /// Some text goes here - /// */ - /// - public static readonly Parser MultilineComment = - (from open in Parse.String("/*") - from content in Parse.AnyChar.Except(Parse.String("*/")) - .Or(Parse.Char(LineBreak)) - .Many().Text() - from last in Parse.String("*/") - select new HclMultiLineCommentElement { Value = content }).Token().Named("Multiline Comment"); - - /// - public static readonly Parser> HereDoc = - (from open in Parse.Char('<').Repeat(2).Text() - from indentMarker in Parse.Char('-').Optional() - from marker in Parse.AnyChar.Except(Parse.Char(LineBreak)).Many().Text() - from lineBreak in Parse.Char(LineBreak) - from rest in Parse.AnyChar.Except(Parse.String(marker)) - .Or(Parse.Char(LineBreak)) - .Many().Text() - from last in Parse.String(marker) - select Tuple.Create(marker, indentMarker.IsDefined, lineBreak + rest)).Token(); - - /// - /// Represents the "//" used to start a comment - /// - public static readonly Parser> ForwardSlashCommentStart = - from open in Parse.Char('/').Repeat(2) - select open; - - /// - /// Represents the "#" used to start a comment - /// - public static readonly Parser> HashCommentStart = - from open in Parse.Char('#').Once() - select open; - - /// - /// Represents a single line comment - /// - public static readonly Parser SingleLineComment = - ( - from open in ForwardSlashCommentStart.Or(HashCommentStart) - from content in Parse.AnyChar.Except(Parse.Char(LineBreak)).Many().Text().Optional() - select new HclCommentElement { Value = content.GetOrDefault() } - ).Token().Named("Single line comment"); - - public static readonly Parser IdentifierPlain = - from value in Parse.Regex(@"(\d|\w|[_\-.])+").Text().Token() - select value; - - /// - /// Identifiers can be wrapped in quotes to indicate their names are variables. New in 0.12 - /// - public static readonly Parser IdentifierVariable = - from value in Parse.Regex(@"\((\d|\w|[_\-.])+\)").Text().Token() - select value; - - public static readonly Parser Identifier = - from value in IdentifierPlain.Or(IdentifierVariable) - select value; - - /// - /// Represents an indexer in an unquoted string. e.g. a = myvar[b] - /// This is lenient, consuming everything between balanced square brackets. - /// - public static readonly Parser ListOrIndexText = - from open in Parse.Char('[') from content in - StringLiteralQuoteUnTokenised - .Or(Parse.AnyChar - .Except(Parse.Char('[')) - .Except(Parse.Char(']')) - .Except(Parse.Char('"')) - .Except(Parse.Char('\'')) - .Many().Text()) - .Or(ListOrIndexText) - .Many() - .Optional() - from close in Parse.Char(']') - select open + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + close; + EscapedDelimiterInterpolation + .Or(Parse.AnyChar.Except(DelimiterStartInterpolated).Many().Text()) + select curly.GetOrDefault() + EscapeString(content)).Many() + select string.Concat(combined); - public static readonly Parser GroupText = - from open in Parse.Char('(').Token() - from content in - LogicSymbol - .Or(StringLiteralQuoteUnTokenised) - .Or(Parse.AnyChar - .Except(Parse.Char('(')) - .Except(Parse.Char(')')) - .Except(Parse.Char('"')) - .Except(Parse.Char('\'')) - .Many().Text()) - .Or(GroupText) - .Many() - .Optional() - from close in Parse.Char(')') - select open + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + close; + /// + /// Matches the plain text in a string, or the Interpolation block + /// + public static readonly Parser StringLiteralQuoteContentNoInterpolation = + from content in StringLiteralCurly + .Or(EscapedDelimiterQuote) + .Or(DoubleEscapeQuote) + .Or(SingleEscapeQuote) + .Or(EscapedDelimiterInterpolation) + .Or(DoubleEscapedDelimiterInterpolation) + .Or(SimpleLiteralQuote).Many() + select string.Concat(content); - public static readonly Parser CurlyGroupText = - from open in Parse.Char('{').Token() - from content in - StringLiteralQuoteUnTokenised - .Or(Parse.AnyChar - .Except(Parse.Char('{')) - .Except(Parse.Char('}')) - .Except(Parse.Char('"')) - .Except(Parse.Char('\'')) - .Many().Text()) - .Or(CurlyGroupText) - .Many() - .Optional() - from close in Parse.Char('}') - select open + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + close; - - /// - /// Math symbols. These are used to indicate places in unquoted values where line breaks can be placed. - /// New in 0.12 - /// - public static readonly Parser LogicSymbol = - from mathOperator in - Parse.String("*") - .Or(Parse.String("/")) - .Or(Parse.String("%")) - .Or(Parse.String("+")) - .Or(Parse.String("-")) - .Or(Parse.String(">=")) - .Or(Parse.String("<=")) - .Or(Parse.String("<")) - .Or(Parse.String(">")) - .Or(Parse.String("!=")) - .Or(Parse.String("==")) - .Or(Parse.String("&&")) - .Or(Parse.String("||")) - .Or(Parse.String("?")) - .Or(Parse.String(":")) - .Or(Parse.String("=")) - .Text() - .RequiredToken() - select " " + mathOperator + " "; - - /// - /// Match quoted string content, and include the quotes in the result. This is used to match quoted strings - /// in a larger unquoted property value. - /// - public static readonly Parser StringLiteralQuoteUnTokenised = - from start in DelimiterQuote - from content in StringLiteralQuoteContent.Many().Optional() - from end in DelimiterQuote - select "\"" + string.Concat(content.GetOrDefault()) + "\""; - - /// - /// Match quoted string content, and include the quotes in the result. This is used to build up string values, - /// so it is untokenized. - /// - public static readonly Parser StringLiteralQuoteUnTokenisedUnQuoted = - from start in DelimiterQuote - from content in StringLiteralQuoteContent.Many().Optional() - from end in DelimiterQuote - select string.Concat(content.GetOrDefault()); - - /// - /// Matches multiple StringLiteralQuoteContent to make up the string. This is used to match block identifiers, - /// and so is a token. - /// - public static readonly Parser StringLiteralQuote = - StringLiteralQuoteUnTokenisedUnQuoted.Token(); - - /// - /// Matches an unquoted value. This is a very generalised parser designed to capture fields that can be - /// simple expressions like: - /// var.vpc_cidr_block - /// Complex expressions spanning multiple lines like: - /// a == b - /// ? c - /// : d - /// Mixtures of quoted and unquoted strings: - /// "a" == b ? "ccc" : d + 1 - /// Inline lists or objects: - /// "a" == b ? [var.vpc_cidr_block] : {var = "hi there"} - /// For loops: - /// [ - /// for key, value in module.bootstrap.assets_dist : - /// format("##### %s\n%s", key, value) - /// ] - /// This parser does not attempt to extract any individual elements out of the value (i.e. we are not building - /// a calculator here). - /// New in 0.12 - /// - public static readonly Parser UnquotedContent = - /* - * An unquoted string must begin with any character expect for a quote - * (which would make it a quoted string), a less than (which would make it a HereDoc), - * hash (which would make it a comment), or whitespace (which is not significant at the - * start of the string). - * - * We can start with parentheses, curly brackets and square brackets. These catch - * math grouping, and for loops that build up lists or objects. - */ - from start in Parse.AnyChar - .Except(Parse.Char('[')) - .Except(Parse.Char(']')) - .Except(Parse.Char('{')) - .Except(Parse.Char('}')) - .Except(Parse.Char('(')) - .Except(Parse.Char(')')) - .Except(Parse.Char('<')) - .Except(Parse.Char('#')) - .Except(Parse.Char('"')) - .Except(Parse.Char('\'')) - .Except(Parse.WhiteSpace) - .Once().Text() - .Or(GroupText) + /// + /// Represents a multiline comment e.g. + /// /* + /// Some text goes here + /// */ + /// + public static readonly Parser MultilineComment = + (from open in Parse.String("/*") + from content in Parse.AnyChar.Except(Parse.String("*/")) + .Or(Parse.Char(LineBreak)) + .Many().Text() + from last in Parse.String("*/") + select new HclMultiLineCommentElement { Value = content }).Token().Named("Multiline Comment"); + + /// + public static readonly Parser> HereDoc = + (from open in Parse.Char('<').Repeat(2).Text() + from indentMarker in Parse.Char('-').Optional() + from marker in Parse.AnyChar.Except(Parse.Char(LineBreak)).Many().Text() + from lineBreak in Parse.Char(LineBreak) + from rest in Parse.AnyChar.Except(Parse.String(marker)) + .Or(Parse.Char(LineBreak)) + .Many().Text() + from last in Parse.String(marker) + select Tuple.Create(marker, indentMarker.IsDefined, lineBreak + rest)).Token(); + + /// + /// Represents the "//" used to start a comment + /// + public static readonly Parser> ForwardSlashCommentStart = + from open in Parse.Char('/').Repeat(2) + select open; + + /// + /// Represents the "#" used to start a comment + /// + public static readonly Parser> HashCommentStart = + from open in Parse.Char('#').Once() + select open; + + /// + /// Represents a single line comment + /// + public static readonly Parser SingleLineComment = + ( + from open in ForwardSlashCommentStart.Or(HashCommentStart) + from content in Parse.AnyChar.Except(Parse.Char(LineBreak)).Many().Text().Optional() + select new HclCommentElement { Value = content.GetOrDefault() } + ).Token().Named("Single line comment"); + + public static readonly Parser IdentifierPlain = + from value in Parse.Regex(@"(\d|\w|[_\-.])+").Text().Token() + select value; + + /// + /// Identifiers can be wrapped in quotes to indicate their names are variables. New in 0.12 + /// + public static readonly Parser IdentifierVariable = + from value in Parse.Regex(@"\((\d|\w|[_\-.])+\)").Text().Token() + select value; + + public static readonly Parser Identifier = + from value in IdentifierPlain.Or(IdentifierVariable) + select value; + + /// + /// Represents an indexer in an unquoted string. e.g. a = myvar[b] + /// This is lenient, consuming everything between balanced square brackets. + /// + public static readonly Parser ListOrIndexText = + from open in Parse.Char('[') + from content in + StringLiteralQuoteUnTokenised + .Or(Parse.AnyChar + .Except(Parse.Char('[')) + .Except(Parse.Char(']')) + .Except(Parse.Char('"')) + .Except(Parse.Char('\'')) + .Many().Text()) .Or(ListOrIndexText) + .Many() + .Optional() + from close in Parse.Char(']') + select open + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + close; + + public static readonly Parser GroupText = + from open in Parse.Char('(').Token() + from content in + LogicSymbol + .Or(StringLiteralQuoteUnTokenised) + .Or(Parse.AnyChar + .Except(Parse.Char('(')) + .Except(Parse.Char(')')) + .Except(Parse.Char('"')) + .Except(Parse.Char('\'')) + .Many().Text()) + .Or(GroupText) + .Many() + .Optional() + from close in Parse.Char(')') + select open + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + close; + + public static readonly Parser CurlyGroupText = + from open in Parse.Char('{').Token() + from content in + StringLiteralQuoteUnTokenised + .Or(Parse.AnyChar + .Except(Parse.Char('{')) + .Except(Parse.Char('}')) + .Except(Parse.Char('"')) + .Except(Parse.Char('\'')) + .Many().Text()) + .Or(CurlyGroupText) + .Many() + .Optional() + from close in Parse.Char('}') + select open + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + close; + + /// + /// Math symbols. These are used to indicate places in unquoted values where line breaks can be placed. + /// New in 0.12 + /// + public static readonly Parser LogicSymbol = + from mathOperator in + Parse.String("*") + .Or(Parse.String("/")) + .Or(Parse.String("%")) + .Or(Parse.String("+")) + .Or(Parse.String("-")) + .Or(Parse.String(">=")) + .Or(Parse.String("<=")) + .Or(Parse.String("<")) + .Or(Parse.String(">")) + .Or(Parse.String("!=")) + .Or(Parse.String("==")) + .Or(Parse.String("&&")) + .Or(Parse.String("||")) + .Or(Parse.String("?")) + .Or(Parse.String(":")) + .Or(Parse.String("=")) + .Text() + .RequiredToken() + select " " + mathOperator + " "; + + /// + /// Match quoted string content, and include the quotes in the result. This is used to match quoted strings + /// in a larger unquoted property value. + /// + public static readonly Parser StringLiteralQuoteUnTokenised = + from start in DelimiterQuote + from content in StringLiteralQuoteContent.Many().Optional() + from end in DelimiterQuote + select "\"" + string.Concat(content.GetOrDefault()) + "\""; + + /// + /// Match quoted string content, and include the quotes in the result. This is used to build up string values, + /// so it is untokenized. + /// + public static readonly Parser StringLiteralQuoteUnTokenisedUnQuoted = + from start in DelimiterQuote + from content in StringLiteralQuoteContent.Many().Optional() + from end in DelimiterQuote + select string.Concat(content.GetOrDefault()); + + /// + /// Matches multiple StringLiteralQuoteContent to make up the string. This is used to match block identifiers, + /// and so is a token. + /// + public static readonly Parser StringLiteralQuote = + StringLiteralQuoteUnTokenisedUnQuoted.Token(); + + /// + /// Matches an unquoted value. This is a very generalised parser designed to capture fields that can be + /// simple expressions like: + /// var.vpc_cidr_block + /// Complex expressions spanning multiple lines like: + /// a == b + /// ? c + /// : d + /// Mixtures of quoted and unquoted strings: + /// "a" == b ? "ccc" : d + 1 + /// Inline lists or objects: + /// "a" == b ? [var.vpc_cidr_block] : {var = "hi there"} + /// For loops: + /// [ + /// for key, value in module.bootstrap.assets_dist : + /// format("##### %s\n%s", key, value) + /// ] + /// This parser does not attempt to extract any individual elements out of the value (i.e. we are not building + /// a calculator here). + /// New in 0.12 + /// + public static readonly Parser UnquotedContent = + /* + * An unquoted string must begin with any character expect for a quote + * (which would make it a quoted string), a less than (which would make it a HereDoc), + * hash (which would make it a comment), or whitespace (which is not significant at the + * start of the string). + * + * We can start with parentheses, curly brackets and square brackets. These catch + * math grouping, and for loops that build up lists or objects. + */ + from start in Parse.AnyChar + .Except(Parse.Char('[')) + .Except(Parse.Char(']')) + .Except(Parse.Char('{')) + .Except(Parse.Char('}')) + .Except(Parse.Char('(')) + .Except(Parse.Char(')')) + .Except(Parse.Char('<')) + .Except(Parse.Char('#')) + .Except(Parse.Char('"')) + .Except(Parse.Char('\'')) + .Except(Parse.WhiteSpace) + .Once().Text() + .Or(GroupText) + .Or(ListOrIndexText) + .Or(CurlyGroupText) + .Or(StringLiteralQuoteUnTokenised) + /* + * Once we enter an unquoted string, we need to understand where the content ends. + * We assume any opening bracket will have a matching closing bracket, and consume everything (line breaks + * included) between them. We also assume that any math symbol can have the right hand side on a new line. + * + * We also don't consume commas, which only make sense inside a list. + * + * However most of these excluded chars can be included in a quoted string via the StringLiteralQuoteUnTokenised + * parser. + */ + from content in + ListOrIndexText .Or(CurlyGroupText) + .Or(GroupText) + .Or(LogicSymbol) .Or(StringLiteralQuoteUnTokenised) - /* - * Once we enter an unquoted string, we need to understand where the content ends. - * We assume any opening bracket will have a matching closing bracket, and consume everything (line breaks - * included) between them. We also assume that any math symbol can have the right hand side on a new line. - * - * We also don't consume commas, which only make sense inside a list. - * - * However most of these excluded chars can be included in a quoted string via the StringLiteralQuoteUnTokenised - * parser. - */ - from content in - ListOrIndexText - .Or(CurlyGroupText) - .Or(GroupText) - .Or(LogicSymbol) - .Or(StringLiteralQuoteUnTokenised) - .Or(Parse.AnyChar - .Except(Parse.Char('[')) - .Except(Parse.Char(']')) - .Except(Parse.Char('{')) - .Except(Parse.Char('}')) - .Except(Parse.Char('(')) - .Except(Parse.Char(')')) - .Except(Parse.Char(',')) - .Except(Parse.Char('"')) - .Except(LogicSymbol) - .Except(Parse.LineEnd) - .Many() - .Text()) + .Or(Parse.AnyChar + .Except(Parse.Char('[')) + .Except(Parse.Char(']')) + .Except(Parse.Char('{')) + .Except(Parse.Char('}')) + .Except(Parse.Char('(')) + .Except(Parse.Char(')')) + .Except(Parse.Char(',')) + .Except(Parse.Char('"')) + .Except(LogicSymbol) + .Except(Parse.LineEnd) .Many() - .Optional() - select new HclUnquotedExpressionElement - { - Value = start + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) - }; - - /// - /// Represents the various values that can be assigned to properties - /// i.e. quoted text, numbers and booleans - /// - public static readonly Parser PropertyValue = - (from value in (from str in StringLiteralQuoteUnTokenisedUnQuoted.WithWhiteSpace() - select new HclStringElement { Value = str } as HclElement) - .Or(from number in Parse.Regex(NumberRegex).WithWhiteSpace() - select new HclNumOrBoolElement { Value = number }) - .Or(from boolean in Parse.Regex(TrueFalse).WithWhiteSpace() - select new HclNumOrBoolElement { Value = boolean }) - // A simple property ends at the end of the line, the end of the file, a comment, comma, end brackets, or comments - // Note that we don't consume delimiters like colons, brackets or comment starts - from endOfLine in Parse.LineTerminator.Or(Parse.Regex(@"[#{}\[\],]|//|/\*")).PreviewRequired() - select value - ).Token(); - - /// - /// New in 0.12 - An primitive definition without quotes - /// - public static readonly Parser UnquotedPrimitiveTypeProperty = - (from value in Parse.String(StringKeyword) - .Or(Parse.String(NumberKeyword)) - .Or(Parse.String(BoolKeyword)) - .Or(Parse.String(AnyKeyword)) - .Text() - select new HclPrimitiveTypeElement { Value = value }).Token(); - - /// - /// New in 0.12 - An primitive definition with quotes - /// - public static readonly Parser QuotedPrimitiveTypeProperty = - (from startQuote in DelimiterQuote - from value in Parse.String(StringKeyword) - .Or(Parse.String(NumberKeyword)) - .Or(Parse.String(BoolKeyword)) - .Or(Parse.String(AnyKeyword)) - .Text() - from endQuote in DelimiterQuote - select new HclPrimitiveTypeElement { Value = value }).Token(); - - /// - /// New in 0.12 - An primitive definition - /// - public static readonly Parser PrimitiveTypeProperty = - UnquotedPrimitiveTypeProperty - .Or(QuotedPrimitiveTypeProperty); - - /// - /// New in 0.12 - An object definition. Todo: Add comment elements. - /// - public static readonly Parser ObjectTypeProperty = - (from objectType in Parse.String("object(").Token() - from openCurly in LeftCurly - from content in - ( - from value in ElementTypedObjectProperty - .Or(PrimitiveTypeObjectProperty) - from comma in Comma.Optional() - select value - ).Token().Many() - from closeCurly in RightCurly - from closeBracket in RightBracket - select new HclObjectTypeElement { Children = content }).Token(); - - /// - /// New in 0.12 - An set definition - /// - public static readonly Parser SetTypeProperty = - (from objectType in Parse.String("set(").Token() - from value in MapTypeProperty - .Or(ObjectTypeProperty) - .Or(ListTypeProperty) - .Or(SetTypeProperty) - .Or(TupleTypeProperty) - .Or(PrimitiveTypeProperty) - from closeBracket in RightBracket - select new HclSetTypeElement { Child = value }).Token(); - - /// - /// New in 0.12 - An list definition - /// - public static readonly Parser ListTypeProperty = - (from objectType in Parse.String("list(").Token() - from value in MapTypeProperty - .Or(ObjectTypeProperty) - .Or(ListTypeProperty) - .Or(SetTypeProperty) - .Or(TupleTypeProperty) - .Or(PrimitiveTypeProperty) - from closeBracket in RightBracket - select new HclListTypeElement { Child = value }).Token(); - - /// - /// New in 0.12 - An tuple definition. - /// - public static readonly Parser TupleTypeProperty = - (from objectType in Parse.String("tuple(").Token() - from openSquare in LeftSquareBracket - from content in - ( - from value in MapTypeProperty - .Or(ObjectTypeProperty) - .Or(ListTypeProperty) - .Or(SetTypeProperty) - .Or(TupleTypeProperty) - .Or(PrimitiveTypeProperty) - from comma in Comma.Optional() - select value - ).Token().Many() - from closeSquare in RightSquareBracket - from closeBracket in RightBracket - select new HclTupleTypeElement { Children = content }).Token(); - - /// - /// New in 0.12 - An map definition - /// - public static readonly Parser MapTypeProperty = - (from objectType in Parse.String("map(").Token() - from value in MapTypeProperty - .Or(ObjectTypeProperty) - .Or(ListTypeProperty) - .Or(SetTypeProperty) - .Or(TupleTypeProperty) - .Or(PrimitiveTypeProperty) - from closeBracket in RightBracket - select new HclMapTypeElement { Child = value }).Token(); - - /// - /// The value of an individual item in a list - /// - public static readonly Parser LiteralListValue = - from value in PropertyValue - .Or(UnquotedContent) - select value; - - /// - /// The value of an individual heredoc item in a list - /// - public static readonly Parser HereDocListValue = - from hereDoc in HereDoc - select new HclHereDocElement - { - Marker = hereDoc.Item1, - Trimmed = hereDoc.Item2, - Value = hereDoc.Item3 - }; - - /// - /// Represents the contents of a map/object - /// - public static readonly Parser MapValue = - ( - from lbracket in LeftCurly - from content in Properties.Optional() - from rbracket in RightCurly - select new HclMapElement { Children = content.GetOrDefault() } + .Text()) + .Many() + .Optional() + select new HclUnquotedExpressionElement + { + Value = start + string.Join(string.Empty, content.GetOrDefault() ?? Enumerable.Empty()) + }; + + /// + /// Represents the various values that can be assigned to properties + /// i.e. quoted text, numbers and booleans + /// + public static readonly Parser PropertyValue = + (from value in (from str in StringLiteralQuoteUnTokenisedUnQuoted.WithWhiteSpace() + select new HclStringElement { Value = str } as HclElement) + .Or(from number in Parse.Regex(NumberRegex).WithWhiteSpace() + select new HclNumOrBoolElement { Value = number }) + .Or(from boolean in Parse.Regex(TrueFalse).WithWhiteSpace() + select new HclNumOrBoolElement { Value = boolean }) + // A simple property ends at the end of the line, the end of the file, a comment, comma, end brackets, or comments + // Note that we don't consume delimiters like colons, brackets or comment starts + from endOfLine in Parse.LineTerminator.Or(Parse.Regex(@"[#{}\[\],]|//|/\*")).PreviewRequired() + select value ).Token(); - /// - /// Represents a list/tuple/set. Lists can be embedded. - /// - public static readonly Parser ListValue = - ( - from open in LeftSquareBracket + /// + /// New in 0.12 - An primitive definition without quotes + /// + public static readonly Parser UnquotedPrimitiveTypeProperty = + (from value in Parse.String(StringKeyword) + .Or(Parse.String(NumberKeyword)) + .Or(Parse.String(BoolKeyword)) + .Or(Parse.String(AnyKeyword)) + .Text() + select new HclPrimitiveTypeElement { Value = value }).Token(); + + /// + /// New in 0.12 - An primitive definition with quotes + /// + public static readonly Parser QuotedPrimitiveTypeProperty = + (from startQuote in DelimiterQuote + from value in Parse.String(StringKeyword) + .Or(Parse.String(NumberKeyword)) + .Or(Parse.String(BoolKeyword)) + .Or(Parse.String(AnyKeyword)) + .Text() + from endQuote in DelimiterQuote + select new HclPrimitiveTypeElement { Value = value }).Token(); + + /// + /// New in 0.12 - An primitive definition + /// + public static readonly Parser PrimitiveTypeProperty = + UnquotedPrimitiveTypeProperty + .Or(QuotedPrimitiveTypeProperty); + + /// + /// New in 0.12 - An object definition. Todo: Add comment elements. + /// + public static readonly Parser ObjectTypeProperty = + (from objectType in Parse.String("object(").Token() + from openCurly in LeftCurly from content in ( - from embeddedValues in ListValue - .Or(MapValue) - .Or(LiteralListValue) - .Or(HereDocListValue) - .Or(SingleLineComment) - .Or(MultilineComment) + from value in ElementTypedObjectProperty + .Or(PrimitiveTypeObjectProperty) from comma in Comma.Optional() - select embeddedValues + select value ).Token().Many() - from close in RightSquareBracket - select new HclListElement { Children = content } - ).Token(); + from closeCurly in RightCurly + from closeBracket in RightBracket + select new HclObjectTypeElement { Children = content }).Token(); - /// - /// Represents a value that can be assigned to a property. - /// Note equals or colons used to separated keys from values: - /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values - /// - public static readonly Parser UnquotedNameUnquotedElementProperty = - from name in Identifier - from eql in EqualsOrColon - from value in UnquotedContent - select new HclUnquotedExpressionPropertyElement { Name = name, Child = value, NameQuoted = false }; - - /// - /// Represents a value that can be assigned to a property - /// Note equals or colons used to separated keys from values: - /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values - /// - public static readonly Parser QuotedNameUnquotedElementProperty = - from name in StringLiteralQuote - from eql in EqualsOrColon - from value in UnquotedContent - select new HclUnquotedExpressionPropertyElement { Name = name, Child = value, NameQuoted = true }; - - /// - /// Represents a value that can be assigned to a property - /// Note equals or colons used to separated keys from values: - /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values - /// - public static readonly Parser ElementProperty = - from name in Identifier - from eql in EqualsOrColon - from value in PropertyValue - select new HclSimplePropertyElement { Name = name, Child = value, NameQuoted = false }; - - /// - /// Represents a value that can be assigned to a property - /// Note equals or colons used to separated keys from values: - /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values - /// - public static readonly Parser QuotedElementProperty = - from name in StringLiteralQuote - from eql in EqualsOrColon - from value in PropertyValue - select new HclSimplePropertyElement { Name = name, Child = value, NameQuoted = true }; - - /// - /// Represents a multiline string - /// - public static readonly Parser ElementMultilineProperty = - from name in Identifier - from eql in Equal - from value in HereDoc - select new HclHereDocPropertyElement - { - Name = name, - NameQuoted = false, - Marker = value.Item1, - Trimmed = value.Item2, - Value = value.Item3 - }; - - /// - /// Represents a multiline string - /// - public static readonly Parser QuotedHclElementMultilineProperty = - from name in StringLiteralQuote - from eql in Equal - from value in HereDoc - select new HclHereDocPropertyElement - { - Name = name, - NameQuoted = true, - Marker = value.Item1, - Trimmed = value.Item2, - Value = value.Item3 - }; - - /// - /// Represents a list property - /// - public static readonly Parser ElementListProperty = - from name in Identifier.Or(StringLiteralQuote) - from eql in Equal - from value in ListValue - select new HclListPropertyElement { Name = name, Children = value.Children, NameQuoted = false }; - - /// - /// Represents a list property - /// - public static readonly Parser QuotedHclElementListProperty = - from name in StringLiteralQuote - from eql in Equal - from value in ListValue - select new HclListPropertyElement { Name = name, Children = value.Children, NameQuoted = true }; - - /// - /// Represent a map assigned to a named value - /// - public static readonly Parser ElementMapProperty = - from name in Identifier.Or(StringLiteralQuote) - from eql in Equal - from properties in MapValue - select new HclMapPropertyElement { Name = name, Children = properties.Children }; - - /// - /// New in 0.12 - Represent a property holding a type - /// - public static readonly Parser ElementTypedObjectProperty = - (from name in Identifier.Or(StringLiteralQuote) - from eql in Equal + /// + /// New in 0.12 - An set definition + /// + public static readonly Parser SetTypeProperty = + (from objectType in Parse.String("set(").Token() + from value in MapTypeProperty + .Or(ObjectTypeProperty) + .Or(ListTypeProperty) + .Or(SetTypeProperty) + .Or(TupleTypeProperty) + .Or(PrimitiveTypeProperty) + from closeBracket in RightBracket + select new HclSetTypeElement { Child = value }).Token(); + + /// + /// New in 0.12 - An list definition + /// + public static readonly Parser ListTypeProperty = + (from objectType in Parse.String("list(").Token() + from value in MapTypeProperty + .Or(ObjectTypeProperty) + .Or(ListTypeProperty) + .Or(SetTypeProperty) + .Or(TupleTypeProperty) + .Or(PrimitiveTypeProperty) + from closeBracket in RightBracket + select new HclListTypeElement { Child = value }).Token(); + + /// + /// New in 0.12 - An tuple definition. + /// + public static readonly Parser TupleTypeProperty = + (from objectType in Parse.String("tuple(").Token() + from openSquare in LeftSquareBracket + from content in + ( from value in MapTypeProperty .Or(ObjectTypeProperty) .Or(ListTypeProperty) .Or(SetTypeProperty) .Or(TupleTypeProperty) - select new HclTypePropertyElement { Name = name, Child = value, NameQuoted = false }).Token(); - - /// - /// New in 0.12 - An plain type definition - /// - public static readonly Parser PrimitiveTypeObjectProperty = - (from name in Identifier.Or(StringLiteralQuote) - from eql in Equal - from value in PrimitiveTypeProperty - select new HclTypePropertyElement { Name = name, Child = value, NameQuoted = false }).Token(); - - /// - /// Represents a named element with child properties - /// - public static readonly Parser NameElement = - from dynamic in Dynamic.Optional() - from name in Identifier.Or(StringLiteralQuote) - from lbracket in LeftCurly - from properties in Properties.Optional() - from rbracket in RightCurly - select new HclElement { Name = name, Children = properties.GetOrDefault() }; - - /// - /// Represents a named element with a value and child properties - /// - public static readonly Parser NameValueElement = - from dynamic in Dynamic.Optional() - from name in Identifier - from eql in Equal.Optional() - from value in Identifier.Or(StringLiteralQuote) - from lbracket in LeftCurly - from properties in Properties.Optional() - from rbracket in RightCurly - select new HclElement { Name = name, Value = value, Children = properties.GetOrDefault() }; - - /// - /// Represents named elements with values and types. These are things like resources. - /// - public static readonly Parser NameValueTypeElement = - from dynamic in Dynamic.Optional() - from name in Identifier - from value in Identifier.Or(StringLiteralQuote) - from type in Identifier.Or(StringLiteralQuote) - from lbracket in LeftCurly - from properties in Properties.Optional() - from rbracket in RightCurly - select new HclElement { Name = name, Value = value, Type = type, Children = properties.GetOrDefault() }; - - /// - /// Represents the properties that can be added to an element - /// - public static readonly Parser> Properties = - (from value in NameElement - .Or(ElementTypedObjectProperty) - .Or(NameValueElement) - .Or(NameValueTypeElement) - .Or(ElementProperty) - .Or(QuotedElementProperty) - .Or(ElementListProperty) - .Or(QuotedHclElementListProperty) - .Or(ElementMapProperty) - .Or(ElementMultilineProperty) - .Or(QuotedHclElementMultilineProperty) - .Or(SingleLineComment) - .Or(MultilineComment) - .Or(UnquotedNameUnquotedElementProperty) - .Or(QuotedNameUnquotedElementProperty) + .Or(PrimitiveTypeProperty) from comma in Comma.Optional() - select value).Many().Token(); - - /// - /// The top level document. If you are parsing a HCL file, this is the Parser to use. - /// This is just a collection of child objects. - /// - public static readonly Parser HclTemplate = - from children in Properties.End() - select new HclRootElement { Children = children }; - - /// - /// Replace line breaks with the Unix style line breaks - /// - /// The text to normalize - /// The text with normalized line breaks - public static string NormalizeLineEndings(string template) + select value + ).Token().Many() + from closeSquare in RightSquareBracket + from closeBracket in RightBracket + select new HclTupleTypeElement { Children = content }).Token(); + + /// + /// New in 0.12 - An map definition + /// + public static readonly Parser MapTypeProperty = + (from objectType in Parse.String("map(").Token() + from value in MapTypeProperty + .Or(ObjectTypeProperty) + .Or(ListTypeProperty) + .Or(SetTypeProperty) + .Or(TupleTypeProperty) + .Or(PrimitiveTypeProperty) + from closeBracket in RightBracket + select new HclMapTypeElement { Child = value }).Token(); + + /// + /// The value of an individual item in a list + /// + public static readonly Parser LiteralListValue = + from value in PropertyValue + .Or(UnquotedContent) + select value; + + /// + /// The value of an individual heredoc item in a list + /// + public static readonly Parser HereDocListValue = + from hereDoc in HereDoc + select new HclHereDocElement { - return LineEndNormalize.Replace(template, "\n"); - } + Marker = hereDoc.Item1, + Trimmed = hereDoc.Item2, + Value = hereDoc.Item3 + }; - public static string EscapeString(string template) + /// + /// Represents the contents of a map/object + /// + public static readonly Parser MapValue = + ( + from lbracket in LeftCurly + from content in Properties.Optional() + from rbracket in RightCurly + select new HclMapElement { Children = content.GetOrDefault() } + ).Token(); + + /// + /// Represents a list/tuple/set. Lists can be embedded. + /// + public static readonly Parser ListValue = + ( + from open in LeftSquareBracket + from content in + ( + from embeddedValues in ListValue + .Or(MapValue) + .Or(LiteralListValue) + .Or(HereDocListValue) + .Or(SingleLineComment) + .Or(MultilineComment) + from comma in Comma.Optional() + select embeddedValues + ).Token().Many() + from close in RightSquareBracket + select new HclListElement { Children = content } + ).Token(); + + /// + /// Represents a value that can be assigned to a property. + /// Note equals or colons used to separated keys from values: + /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values + /// + public static readonly Parser UnquotedNameUnquotedElementProperty = + from name in Identifier + from eql in EqualsOrColon + from value in UnquotedContent + select new HclUnquotedExpressionPropertyElement { Name = name, Child = value, NameQuoted = false }; + + /// + /// Represents a value that can be assigned to a property + /// Note equals or colons used to separated keys from values: + /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values + /// + public static readonly Parser QuotedNameUnquotedElementProperty = + from name in StringLiteralQuote + from eql in EqualsOrColon + from value in UnquotedContent + select new HclUnquotedExpressionPropertyElement { Name = name, Child = value, NameQuoted = true }; + + /// + /// Represents a value that can be assigned to a property + /// Note equals or colons used to separated keys from values: + /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values + /// + public static readonly Parser ElementProperty = + from name in Identifier + from eql in EqualsOrColon + from value in PropertyValue + select new HclSimplePropertyElement { Name = name, Child = value, NameQuoted = false }; + + /// + /// Represents a value that can be assigned to a property + /// Note equals or colons used to separated keys from values: + /// https://github.com/hashicorp/hcl/blob/hcl2/hclsyntax/spec.md#collection-values + /// + public static readonly Parser QuotedElementProperty = + from name in StringLiteralQuote + from eql in EqualsOrColon + from value in PropertyValue + select new HclSimplePropertyElement { Name = name, Child = value, NameQuoted = true }; + + /// + /// Represents a multiline string + /// + public static readonly Parser ElementMultilineProperty = + from name in Identifier + from eql in Equal + from value in HereDoc + select new HclHereDocPropertyElement { - return template - .Replace("\\", "\\\\") - .Replace("\n", "\\n") - .Replace("\a", "\\a") - .Replace("\b", "\\b") - .Replace("\f", "\\f") - .Replace("\r", "\\r") - .Replace("\t", "\\t") - .Replace("\v", "\\v") - .Replace("\"", "\\\""); - } + Name = name, + NameQuoted = false, + Marker = value.Item1, + Trimmed = value.Item2, + Value = value.Item3 + }; + + /// + /// Represents a multiline string + /// + public static readonly Parser QuotedHclElementMultilineProperty = + from name in StringLiteralQuote + from eql in Equal + from value in HereDoc + select new HclHereDocPropertyElement + { + Name = name, + NameQuoted = true, + Marker = value.Item1, + Trimmed = value.Item2, + Value = value.Item3 + }; + + /// + /// Represents a list property + /// + public static readonly Parser ElementListProperty = + from name in Identifier.Or(StringLiteralQuote) + from eql in Equal + from value in ListValue + select new HclListPropertyElement { Name = name, Children = value.Children, NameQuoted = false }; + + /// + /// Represents a list property + /// + public static readonly Parser QuotedHclElementListProperty = + from name in StringLiteralQuote + from eql in Equal + from value in ListValue + select new HclListPropertyElement { Name = name, Children = value.Children, NameQuoted = true }; + + /// + /// Represent a map assigned to a named value + /// + public static readonly Parser ElementMapProperty = + from name in Identifier.Or(StringLiteralQuote) + from eql in Equal + from properties in MapValue + select new HclMapPropertyElement { Name = name, Children = properties.Children }; + + /// + /// New in 0.12 - Represent a property holding a type + /// + public static readonly Parser ElementTypedObjectProperty = + (from name in Identifier.Or(StringLiteralQuote) + from eql in Equal + from value in MapTypeProperty + .Or(ObjectTypeProperty) + .Or(ListTypeProperty) + .Or(SetTypeProperty) + .Or(TupleTypeProperty) + select new HclTypePropertyElement { Name = name, Child = value, NameQuoted = false }).Token(); + + /// + /// New in 0.12 - An plain type definition + /// + public static readonly Parser PrimitiveTypeObjectProperty = + (from name in Identifier.Or(StringLiteralQuote) + from eql in Equal + from value in PrimitiveTypeProperty + select new HclTypePropertyElement { Name = name, Child = value, NameQuoted = false }).Token(); + + /// + /// Represents a named element with child properties + /// + public static readonly Parser NameElement = + from dynamic in Dynamic.Optional() + from name in Identifier.Or(StringLiteralQuote) + from lbracket in LeftCurly + from properties in Properties.Optional() + from rbracket in RightCurly + select new HclElement { Name = name, Children = properties.GetOrDefault() }; + + /// + /// Represents a named element with a value and child properties + /// + public static readonly Parser NameValueElement = + from dynamic in Dynamic.Optional() + from name in Identifier + from eql in Equal.Optional() + from value in Identifier.Or(StringLiteralQuote) + from lbracket in LeftCurly + from properties in Properties.Optional() + from rbracket in RightCurly + select new HclElement { Name = name, Value = value, Children = properties.GetOrDefault() }; + + /// + /// Represents named elements with values and types. These are things like resources. + /// + public static readonly Parser NameValueTypeElement = + from dynamic in Dynamic.Optional() + from name in Identifier + from value in Identifier.Or(StringLiteralQuote) + from type in Identifier.Or(StringLiteralQuote) + from lbracket in LeftCurly + from properties in Properties.Optional() + from rbracket in RightCurly + select new HclElement { Name = name, Value = value, Type = type, Children = properties.GetOrDefault() }; + + /// + /// Represents the properties that can be added to an element + /// + public static readonly Parser> Properties = + (from value in NameElement + .Or(ElementTypedObjectProperty) + .Or(NameValueElement) + .Or(NameValueTypeElement) + .Or(ElementProperty) + .Or(QuotedElementProperty) + .Or(ElementListProperty) + .Or(QuotedHclElementListProperty) + .Or(ElementMapProperty) + .Or(ElementMultilineProperty) + .Or(QuotedHclElementMultilineProperty) + .Or(SingleLineComment) + .Or(MultilineComment) + .Or(UnquotedNameUnquotedElementProperty) + .Or(QuotedNameUnquotedElementProperty) + from comma in Comma.Optional() + select value).Many().Token(); + + /// + /// The top level document. If you are parsing a HCL file, this is the Parser to use. + /// This is just a collection of child objects. + /// + public static readonly Parser HclTemplate = + from children in Properties.End() + select new HclRootElement { Children = children }; + + /// + /// Replace line breaks with the Unix style line breaks + /// + /// The text to normalize + /// The text with normalized line breaks + public static string NormalizeLineEndings(string template) + { + return LineEndNormalize.Replace(template, "\n"); } - internal static class HclSpracheExtensions + public static string EscapeString(string template) { - /// - /// Like Token(), but whitespace is required - /// - public static Parser RequiredToken(this Parser parser) - { - if (parser == null) throw new ArgumentNullException(nameof(parser)); - - return from leading in Parse.WhiteSpace.AtLeastOnce() - from item in parser - from trailing in Parse.WhiteSpace.AtLeastOnce() - select item; - } - - /// - /// An option to Token() which does not consume line breaks - /// - public static Parser WithWhiteSpace(this Parser parser) - { - if (parser == null) throw new ArgumentNullException(nameof(parser)); - - return from leading in Parse.WhiteSpace.Except(Parse.LineEnd).Many() - from item in parser - from trailing in Parse.WhiteSpace.Except(Parse.LineEnd).Many() - select item; - } - - /// - /// Matches the parser, but does not consume the matched result. This is much like a positive lookahead - /// in a regex. - /// - public static Parser PreviewRequired(this Parser parser) + return template + .Replace("\\", "\\\\") + .Replace("\n", "\\n") + .Replace("\a", "\\a") + .Replace("\b", "\\b") + .Replace("\f", "\\f") + .Replace("\r", "\\r") + .Replace("\t", "\\t") + .Replace("\v", "\\v") + .Replace("\"", "\\\""); + } +} + +internal static class HclSpracheExtensions +{ + /// + /// Like Token(), but whitespace is required + /// + public static Parser RequiredToken(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return from leading in Parse.WhiteSpace.AtLeastOnce() + from item in parser + from trailing in Parse.WhiteSpace.AtLeastOnce() + select item; + } + + /// + /// An option to Token() which does not consume line breaks + /// + public static Parser WithWhiteSpace(this Parser parser) + { + if (parser == null) throw new ArgumentNullException(nameof(parser)); + + return from leading in Parse.WhiteSpace.Except(Parse.LineEnd).Many() + from item in parser + from trailing in Parse.WhiteSpace.Except(Parse.LineEnd).Many() + select item; + } + + /// + /// Matches the parser, but does not consume the matched result. This is much like a positive lookahead + /// in a regex. + /// + public static Parser PreviewRequired(this Parser parser) + { + if (parser == null) + throw new ArgumentNullException(nameof(parser)); + return i => { - if (parser == null) - throw new ArgumentNullException(nameof(parser)); - return i => - { - var result = parser(i); - return result.WasSuccessful - ? Result.Success(result.Value, i) - : Result.Failure(i, "Failed the preview", result.Expectations); - }; - } + var result = parser(i); + return result.WasSuccessful + ? Result.Success(result.Value, i) + : Result.Failure(i, "Failed the preview", result.Expectations); + }; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclPrimitiveTypeElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclPrimitiveTypeElement.cs index f9a9424..4d63aa9 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclPrimitiveTypeElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclPrimitiveTypeElement.cs @@ -1,14 +1,13 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclPrimitiveTypeElement : HclElement { - public class HclPrimitiveTypeElement : HclElement - { - public override string Type => PrimitivePropertyType; + public override string Type => PrimitivePropertyType; - public override string ProcessedValue => Value ?? ""; + public override string ProcessedValue => Value ?? ""; - public override string ToString(bool naked, int indent) - { - return "\"" + Value + "\""; - } + public override string ToString(bool naked, int indent) + { + return "\"" + Value + "\""; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclRootElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclRootElement.cs index a0cad26..11c0b13 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclRootElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclRootElement.cs @@ -1,17 +1,16 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents the document root +/// +public class HclRootElement : HclElement { - /// - /// Represents the document root - /// - public class HclRootElement : HclElement - { - public override string Type => RootType; + public override string Type => RootType; - public override string ToString(bool naked, int indent) - { - return string.Join("\n", Children?.Select(child => child.ToString(indent)) ?? Enumerable.Empty()); - } + public override string ToString(bool naked, int indent) + { + return string.Join("\n", Children?.Select(child => child.ToString(indent)) ?? Enumerable.Empty()); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSetTypeElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSetTypeElement.cs index 144358a..cf4bd91 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSetTypeElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSetTypeElement.cs @@ -1,26 +1,25 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclSetTypeElement : HclElement { - public class HclSetTypeElement : HclElement - { - public override string Type => SetPropertyType; + public override string Type => SetPropertyType; - public override string ProcessedValue => Value ?? ""; + public override string ProcessedValue => Value ?? ""; - public override string Value => ToString(-1); + public override string Value => ToString(-1); - public override string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return "set(" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + ")"; - } + return "set(" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + ")"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSimplePropertyElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSimplePropertyElement.cs index e36c50f..54b629b 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSimplePropertyElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclSimplePropertyElement.cs @@ -1,35 +1,34 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a simple value (string, number or boolean) assigned to a property +/// +public class HclSimplePropertyElement : HclElement { + public override string Type => SimplePropertyType; + /// - /// Represents a simple value (string, number or boolean) assigned to a property + /// This class used to be a simple name/value mapping. It was updated to defer the value to the + /// children, but for compatibility the Value property returns the children's values. /// - public class HclSimplePropertyElement : HclElement + public override string Value { - public override string Type => SimplePropertyType; - - /// - /// This class used to be a simple name/value mapping. It was updated to defer the value to the - /// children, but for compatibility the Value property returns the children's values. - /// - public override string Value - { - get => string.Join( - string.Empty, - Children?.Select(child => child.Value) ?? Enumerable.Empty()); - set { } - } + get => string.Join( + string.Empty, + Children?.Select(child => child.Value) ?? Enumerable.Empty()); + set { } + } - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - var children = string.Join( - string.Empty, - Children?.Select(child => child.ToString(naked, -1)) ?? Enumerable.Empty()); - if (naked) return children; + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + var children = string.Join( + string.Empty, + Children?.Select(child => child.ToString(naked, -1)) ?? Enumerable.Empty()); + if (naked) return children; - return indentString + OriginalName + " = " + children; - } + return indentString + OriginalName + " = " + children; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclStringElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclStringElement.cs index a45ccaf..08f6830 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclStringElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclStringElement.cs @@ -1,25 +1,24 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a string +/// +public class HclStringElement : HclElement { - /// - /// Represents a string - /// - public class HclStringElement : HclElement - { - public override string Type => StringType; + public override string Type => StringType; - public override string ToString(bool naked, int indent) - { - /* - * ToString() is designed to return the HCL representation of this element. Naked was an older option - * that was used to indicate that only the Value was to be returned. For string elements, naked meant - * to return the value without any quotes. - * - * It is better to use the Value property for this use case, but this logic is retained for compatibility. - */ - if (naked) return ProcessedValue; + public override string ToString(bool naked, int indent) + { + /* + * ToString() is designed to return the HCL representation of this element. Naked was an older option + * that was used to indicate that only the Value was to be returned. For string elements, naked meant + * to return the value without any quotes. + * + * It is better to use the Value property for this use case, but this logic is retained for compatibility. + */ + if (naked) return ProcessedValue; - var indentString = GetIndent(indent); - return indentString + "\"" + EscapeQuotes(ProcessedValue) + "\""; - } + var indentString = GetIndent(indent); + return indentString + "\"" + EscapeQuotes(ProcessedValue) + "\""; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTupleTypeElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTupleTypeElement.cs index edf9da8..e0d5d71 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTupleTypeElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTupleTypeElement.cs @@ -1,26 +1,25 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclTupleTypeElement : HclElement { - public class HclTupleTypeElement : HclElement - { - public override string Type => TuplePropertyType; + public override string Type => TuplePropertyType; - public override string ProcessedValue => Value ?? ""; + public override string ProcessedValue => Value ?? ""; - public override string Value => ToString(-1); + public override string Value => ToString(-1); - public override string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var lineBreak = indent == -1 ? string.Empty : "\n"; - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var lineBreak = indent == -1 ? string.Empty : "\n"; + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return indentString + "tuple([" + lineBreak + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + - lineBreak + indentString + "])"; - } + return indentString + "tuple([" + lineBreak + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()) + + lineBreak + indentString + "])"; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTypePropertyElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTypePropertyElement.cs index 77ca33d..e920607 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTypePropertyElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclTypePropertyElement.cs @@ -1,26 +1,25 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents a string assigned to a property +/// +public class HclTypePropertyElement : HclElement { - /// - /// Represents a string assigned to a property - /// - public class HclTypePropertyElement : HclElement - { - public override string Type => TypePropertyType; + public override string Type => TypePropertyType; - public override string Value => - string.Join(",", Children?.Select(child => child.ToString(-1)) ?? Enumerable.Empty()); + public override string Value => + string.Join(",", Children?.Select(child => child.ToString(-1)) ?? Enumerable.Empty()); - public override string ToString(bool naked, int indent) - { - var indentString = indent == -1 ? string.Empty : GetIndent(indent); - var nextIndent = indent == -1 ? -1 : indent + 1; - var separator = indent == -1 ? ", " : "\n"; + public override string ToString(bool naked, int indent) + { + var indentString = indent == -1 ? string.Empty : GetIndent(indent); + var nextIndent = indent == -1 ? -1 : indent + 1; + var separator = indent == -1 ? ", " : "\n"; - return indentString + OriginalName + " = " + - string.Join(separator, - Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()); - } + return indentString + OriginalName + " = " + + string.Join(separator, + Children?.Select(child => child.ToString(nextIndent)) ?? Enumerable.Empty()); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionElement.cs index c372a7b..c881b91 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionElement.cs @@ -1,15 +1,14 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents the collection of values that can make up an unquoted property value +/// +public class HclUnquotedExpressionElement : HclElement { - /// - /// Represents the collection of values that can make up an unquoted property value - /// - public class HclUnquotedExpressionElement : HclElement - { - public override string Type => UnquotedType; + public override string Type => UnquotedType; - public override string ToString(bool naked, int indent) - { - return Value; - } + public override string ToString(bool naked, int indent) + { + return Value; } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionPropertyElement.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionPropertyElement.cs index d25a98b..0189728 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionPropertyElement.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HclUnquotedExpressionPropertyElement.cs @@ -1,25 +1,24 @@ using System.Linq; -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +/// +/// Represents an unquoted expression assigned to a property. These can be simple unquoted strings, or +/// more complex with function calls, math and ternary statements. +/// +public class HclUnquotedExpressionPropertyElement : HclElement { - /// - /// Represents an unquoted expression assigned to a property. These can be simple unquoted strings, or - /// more complex with function calls, math and ternary statements. - /// - public class HclUnquotedExpressionPropertyElement : HclElement - { - public override string Type => SimplePropertyType; + public override string Type => SimplePropertyType; - public override string Value => - string.Join(" ", Children?.Select(child => child.ToString(-1)) ?? Enumerable.Empty()); + public override string Value => + string.Join(" ", Children?.Select(child => child.ToString(-1)) ?? Enumerable.Empty()); - public override string ToString(bool naked, int indent) - { - var indentString = GetIndent(indent); - if (naked) return ProcessedValue; + public override string ToString(bool naked, int indent) + { + var indentString = GetIndent(indent); + if (naked) return ProcessedValue; - return indentString + OriginalName + " = " + - string.Join(" ", Children?.Select(child => child.ToString(-1)) ?? Enumerable.Empty()); - } + return indentString + OriginalName + " = " + + string.Join(" ", Children?.Select(child => child.ToString(-1)) ?? Enumerable.Empty()); } } \ No newline at end of file diff --git a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HcllMathSymbol.cs b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HcllMathSymbol.cs index a203e10..66d672f 100644 --- a/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HcllMathSymbol.cs +++ b/source/Octopus.Core.Parsers.Hcl/Octopus/CoreParsers/Hcl/HcllMathSymbol.cs @@ -1,12 +1,11 @@ -namespace Octopus.CoreParsers.Hcl +namespace Octopus.CoreParsers.Hcl; + +public class HclMathSymbol : HclElement { - public class HclMathSymbol : HclElement - { - public override string Type => MathSymbol; + public override string Type => MathSymbol; - public override string ToString(bool naked, int indent) - { - return Value; - } + public override string ToString(bool naked, int indent) + { + return Value; } } \ No newline at end of file diff --git a/source/Solution Items/VersionInfo.cs b/source/Solution Items/VersionInfo.cs index 39c917e..ccfcaa9 100644 --- a/source/Solution Items/VersionInfo.cs +++ b/source/Solution Items/VersionInfo.cs @@ -6,11 +6,10 @@ [assembly: AssemblyInformationalVersion("0.0.0-local")] [assembly: AssemblyFileVersion("0.0.0.0")] -namespace Octopus +namespace Octopus; + +internal static class GitVersionInformation { - internal static class GitVersionInformation - { - public static string BranchName = "UNKNOWNBRANCH"; - public static string NuGetVersion = "0.0.0-local"; - } + public static string BranchName = "UNKNOWNBRANCH"; + public static string NuGetVersion = "0.0.0-local"; } \ No newline at end of file