diff --git a/src/DockerfileModel/DockerfileModel.Tests/DockerfileBuilderTests.cs b/src/DockerfileModel/DockerfileModel.Tests/DockerfileBuilderTests.cs index f6c6db3..4490d82 100644 --- a/src/DockerfileModel/DockerfileModel.Tests/DockerfileBuilderTests.cs +++ b/src/DockerfileModel/DockerfileModel.Tests/DockerfileBuilderTests.cs @@ -45,7 +45,8 @@ public void BuildAllConstructs() .ShellInstruction("cmd") .StopSignalInstruction("1") .UserInstruction("test") - .VolumeInstruction("path"); + .VolumeInstruction("path") + .WorkdirInstruction("path"); string expectedOutput = "ADD src dst" + Environment.NewLine + @@ -68,7 +69,8 @@ public void BuildAllConstructs() "SHELL [\"cmd\"]" + Environment.NewLine + "STOPSIGNAL 1" + Environment.NewLine + "USER test" + Environment.NewLine + - "VOLUME [\"path\"]" + Environment.NewLine; + "VOLUME [\"path\"]" + Environment.NewLine + + "WORKDIR path" + Environment.NewLine; Assert.Equal(expectedOutput, builder.Dockerfile.ToString()); Assert.Equal(expectedOutput, builder.ToString()); diff --git a/src/DockerfileModel/DockerfileModel.Tests/WorkdirInstructionTests.cs b/src/DockerfileModel/DockerfileModel.Tests/WorkdirInstructionTests.cs new file mode 100644 index 0000000..2e98190 --- /dev/null +++ b/src/DockerfileModel/DockerfileModel.Tests/WorkdirInstructionTests.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using DockerfileModel.Tokens; +using Sprache; +using Xunit; + +using static DockerfileModel.Tests.TokenValidator; + +namespace DockerfileModel.Tests +{ + public class WorkdirInstructionTests + { + [Theory] + [MemberData(nameof(ParseTestInput))] + public void Parse(WorkdirInstructionParseTestScenario scenario) + { + if (scenario.ParseExceptionPosition is null) + { + WorkdirInstruction result = WorkdirInstruction.Parse(scenario.Text, scenario.EscapeChar); + Assert.Equal(scenario.Text, result.ToString()); + Assert.Collection(result.Tokens, scenario.TokenValidators); + scenario.Validate?.Invoke(result); + } + else + { + ParseException exception = Assert.Throws( + () => WorkdirInstruction.Parse(scenario.Text, scenario.EscapeChar)); + Assert.Equal(scenario.ParseExceptionPosition.Line, exception.Position.Line); + Assert.Equal(scenario.ParseExceptionPosition.Column, exception.Position.Column); + } + } + + [Theory] + [MemberData(nameof(CreateTestInput))] + public void Create(CreateTestScenario scenario) + { + WorkdirInstruction result = new WorkdirInstruction(scenario.Path); + Assert.Collection(result.Tokens, scenario.TokenValidators); + scenario.Validate?.Invoke(result); + } + + [Fact] + public void Path() + { + WorkdirInstruction result = new WorkdirInstruction("/test"); + Assert.Equal("/test", result.Path); + Assert.Equal("/test", result.PathToken.Value); + Assert.Equal("WORKDIR /test", result.ToString()); + + result.Path = "/test2"; + Assert.Equal("/test2", result.Path); + Assert.Equal("/test2", result.PathToken.Value); + Assert.Equal("WORKDIR /test2", result.ToString()); + + result.PathToken.Value = "/test3"; + Assert.Equal("/test3", result.Path); + Assert.Equal("/test3", result.PathToken.Value); + Assert.Equal("WORKDIR /test3", result.ToString()); + + result.PathToken = new LiteralToken("/test4"); + Assert.Equal("/test4", result.Path); + Assert.Equal("/test4", result.PathToken.Value); + Assert.Equal("WORKDIR /test4", result.ToString()); + + Assert.Throws(() => result.Path = null); + Assert.Throws(() => result.Path = ""); + Assert.Throws(() => result.PathToken = null); + } + + [Fact] + public void PathWithVariables() + { + WorkdirInstruction result = new WorkdirInstruction("$var"); + TestHelper.TestVariablesWithLiteral(() => result.PathToken, "var", canContainVariables: true); + } + + public static IEnumerable ParseTestInput() + { + WorkdirInstructionParseTestScenario[] testInputs = new WorkdirInstructionParseTestScenario[] + { + new WorkdirInstructionParseTestScenario + { + Text = "WORKDIR /test", + TokenValidators = new Action[] + { + token => ValidateKeyword(token, "WORKDIR"), + token => ValidateWhitespace(token, " "), + token => ValidateLiteral(token, "/test") + }, + Validate = result => + { + Assert.Empty(result.Comments); + Assert.Equal("WORKDIR", result.InstructionName); + Assert.Equal("/test", result.Path); + } + }, + new WorkdirInstructionParseTestScenario + { + Text = "WORKDIR $TEST", + TokenValidators = new Action[] + { + token => ValidateKeyword(token, "WORKDIR"), + token => ValidateWhitespace(token, " "), + token => ValidateAggregate(token, "$TEST", + token => ValidateAggregate(token, "$TEST", + token => ValidateString(token, "TEST"))) + } + }, + new WorkdirInstructionParseTestScenario + { + Text = "WORKDIR`\n /test", + EscapeChar = '`', + TokenValidators = new Action[] + { + token => ValidateKeyword(token, "WORKDIR"), + token => ValidateLineContinuation(token, '`', "\n"), + token => ValidateWhitespace(token, " "), + token => ValidateLiteral(token, "/test") + } + } + }; + + return testInputs.Select(input => new object[] { input }); + } + + public static IEnumerable CreateTestInput() + { + CreateTestScenario[] testInputs = new CreateTestScenario[] + { + new CreateTestScenario + { + Path = "/test", + TokenValidators = new Action[] + { + token => ValidateKeyword(token, "WORKDIR"), + token => ValidateWhitespace(token, " "), + token => ValidateLiteral(token, "/test") + } + } + }; + + return testInputs.Select(input => new object[] { input }); + } + + public class WorkdirInstructionParseTestScenario : ParseTestScenario + { + public char EscapeChar { get; set; } + } + + public class CreateTestScenario : TestScenario + { + public string Path { get; set; } + } + } +} diff --git a/src/DockerfileModel/DockerfileModel/DockerfileBuilder.cs b/src/DockerfileModel/DockerfileModel/DockerfileBuilder.cs index 6f9c44b..2fcaaa3 100644 --- a/src/DockerfileModel/DockerfileModel/DockerfileBuilder.cs +++ b/src/DockerfileModel/DockerfileModel/DockerfileBuilder.cs @@ -204,6 +204,12 @@ public DockerfileBuilder VolumeInstruction(IEnumerable paths) => public DockerfileBuilder VolumeInstruction(Action configureBuilder) => ParseTokens(configureBuilder, DockerfileModel.VolumeInstruction.Parse); + public DockerfileBuilder WorkdirInstruction(string path) => + AddConstruct(new WorkdirInstruction(path, EscapeChar)); + + public DockerfileBuilder WorkdirInstruction(Action configureBuilder) => + ParseTokens(configureBuilder, DockerfileModel.WorkdirInstruction.Parse); + private DockerfileBuilder ParseTokens(Action configureBuilder, Func parseConstruct) { TokenBuilder builder = new TokenBuilder diff --git a/src/DockerfileModel/DockerfileModel/Instruction.cs b/src/DockerfileModel/DockerfileModel/Instruction.cs index 596e139..ff75bde 100644 --- a/src/DockerfileModel/DockerfileModel/Instruction.cs +++ b/src/DockerfileModel/DockerfileModel/Instruction.cs @@ -29,7 +29,7 @@ public abstract class Instruction : DockerfileConstruct, ICommentable { "STOPSIGNAL", StopSignalInstruction.Parse }, { "USER", UserInstruction.Parse }, { "VOLUME", VolumeInstruction.Parse }, - { "WORKDIR", GenericInstruction.Parse }, + { "WORKDIR", WorkdirInstruction.Parse }, }; protected Instruction(IEnumerable tokens) : base(tokens) diff --git a/src/DockerfileModel/DockerfileModel/WorkdirInstruction.cs b/src/DockerfileModel/DockerfileModel/WorkdirInstruction.cs new file mode 100644 index 0000000..54e67d9 --- /dev/null +++ b/src/DockerfileModel/DockerfileModel/WorkdirInstruction.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Linq; +using DockerfileModel.Tokens; +using Sprache; +using Validation; +using static DockerfileModel.ParseHelper; + +namespace DockerfileModel +{ + public class WorkdirInstruction : Instruction + { + public WorkdirInstruction(string path, char escapeChar = Dockerfile.DefaultEscapeChar) + : this(GetTokens(path, escapeChar)) + { + } + + private WorkdirInstruction(IEnumerable tokens) : base(tokens) + { + } + + public string Path + { + get => PathToken.Value; + set + { + Requires.NotNullOrEmpty(value, nameof(value)); + PathToken.Value = value; + } + } + + public LiteralToken PathToken + { + get => Tokens.OfType().First(); + set + { + Requires.NotNull(value, nameof(value)); + SetToken(PathToken, value); + } + } + + public static WorkdirInstruction Parse(string text, char escapeChar = Dockerfile.DefaultEscapeChar) => + new WorkdirInstruction(GetTokens(text, GetInnerParser(escapeChar))); + + public static Parser GetParser(char escapeChar = Dockerfile.DefaultEscapeChar) => + from tokens in GetInnerParser(escapeChar) + select new WorkdirInstruction(tokens); + + internal static Parser> GetInnerParser(char escapeChar) => + Instruction("WORKDIR", escapeChar, GetArgsParser(escapeChar)); + + private static IEnumerable GetTokens(string path, char escapeChar) + { + Requires.NotNullOrEmpty(path, nameof(path)); + return GetTokens($"WORKDIR {path}", GetInnerParser(escapeChar)); + } + + private static Parser> GetArgsParser(char escapeChar) => + ArgTokens(LiteralWithVariables(escapeChar, whitespaceMode: WhitespaceMode.Allowed).AsEnumerable(), escapeChar); + } +}