diff --git a/src/Language/Docker/Parser/Prelude.hs b/src/Language/Docker/Parser/Prelude.hs index 1ca7bf9..8abb430 100644 --- a/src/Language/Docker/Parser/Prelude.hs +++ b/src/Language/Docker/Parser/Prelude.hs @@ -368,12 +368,23 @@ someUnless name predicate = do applyPredicate = many $ choice - [ castToSpace <$> escapedLineBreaks, + [ castToSpace <$> try insignificantLineBreak, takeWhile1P (Just name) (\c -> not (isSpaceNl c || predicate c)), - takeWhile1P Nothing (\c -> c == ?esc && not (predicate c)) - <* notFollowedBy (char '\n') + try $ + takeWhile1P Nothing (\c -> c == ?esc && not (predicate c)) + <* notFollowedBy (char '\n') ] + -- An escaped line break followed by whitespace stands for a space (see + -- 'escapedLineBreaks'). Wherever a space ends the token being parsed, such + -- a line break ends it as well instead of being pulled into it. It is left + -- unconsumed, so that it can be parsed as the separator it is. + insignificantLineBreak = do + ws <- escapedLineBreaks + when (ws == FoundWhitespace && predicate ' ') $ + fail "escaped line break separating two tokens" + pure ws + comment :: Parser Text comment = do void $ char '#' diff --git a/test/Language/Docker/ParseAddSpec.hs b/test/Language/Docker/ParseAddSpec.hs index f1e1c5e..01a9e80 100644 --- a/test/Language/Docker/ParseAddSpec.hs +++ b/test/Language/Docker/ParseAddSpec.hs @@ -30,6 +30,14 @@ spec = do ) def ] + it "ADD with a line continuation and no space before the backslash" $ + let file = Text.unlines ["ADD foo.json\\", " /root/foo.json"] + in assertAst + file + [ Add + ( AddArgs [ SourcePath "foo.json" ] ( TargetPath "/root/foo.json" ) ) + def + ] it "list of quoted files" $ let file = Text.unlines ["ADD [\"foo\", \"bar\", \"baz\", \"/app\"]"] in assertAst diff --git a/test/Language/Docker/ParseCopySpec.hs b/test/Language/Docker/ParseCopySpec.hs index 25aeb49..609fbfe 100644 --- a/test/Language/Docker/ParseCopySpec.hs +++ b/test/Language/Docker/ParseCopySpec.hs @@ -33,6 +33,29 @@ spec = do ) def ] + it "COPY with a line continuation and no space before the backslash" $ + let file = Text.unlines ["COPY foo.json\\", " /root/foo.json"] + in assertAst + file + [ Copy + ( CopyArgs [ SourcePath "foo.json" ] ( TargetPath "/root/foo.json" ) ) + def + ] + it "multifiles COPY with a line continuation and no space before the backslash" $ + let file = Text.unlines ["COPY foo bar\\", " baz /app"] + in assertAst + file + [ Copy + ( CopyArgs + (fmap SourcePath ["foo", "bar", "baz"]) + (TargetPath "/app") + ) + def + ] + it "COPY with a line continuation that is not followed by whitespace" $ + -- Docker joins these two lines without inserting a space, which leaves + -- COPY with a single argument. + expectFail (Text.unlines ["COPY foo.json\\", "/root/foo.json"]) it "list of quoted files" $ let file = Text.unlines ["COPY [\"foo\", \"bar\", \"baz\", \"/app\"]"] in assertAst