Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion src/Language/Docker/PrettyPrint.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

module Language.Docker.PrettyPrint where

import Data.Char (isSpace)
import Data.List.NonEmpty as NonEmpty (NonEmpty (..), toList)
import Data.Set (Set)
import Data.String (fromString)
Expand Down Expand Up @@ -94,11 +95,42 @@

prettyPrintArguments :: (?esc :: Char) => Arguments Text -> Doc ann
prettyPrintArguments (ArgumentsList as) = prettyPrintJSON (Text.words as)
prettyPrintArguments (ArgumentsText as) = hsep (fmap helper (Text.words as))
prettyPrintArguments (ArgumentsText as) = hsep (fmap helper (quotedWords as))
where
helper "&&" = pretty ?esc <> "\n &&"
helper a = pretty a

data Quoting
= Unquoted
| InSingle
| InDouble
deriving (Eq)

-- | Like 'Text.words', except that a quoted section stays part of the word it
-- belongs to. Cutting a quoted section into words would let its spacing be
-- rewritten, and would let an operator written inside it, such as @&&@, pass
-- for one that separates commands.
quotedWords :: Text -> [Text]
quotedWords = go Unquoted [] []
where
go quoting word acc text =
case Text.uncons text of
Nothing -> reverse (emit word acc)
Just (c, rest)
| quoting == Unquoted, isSpace c -> go Unquoted [] (emit word acc) rest
| c == '\\',
quoting /= InSingle,
Just (c', rest') <- Text.uncons rest ->
go quoting (c' : c : word) acc rest'
| c == '\'', quoting == Unquoted -> go InSingle (c : word) acc rest
| c == '\'', quoting == InSingle -> go Unquoted (c : word) acc rest
| c == '"', quoting == Unquoted -> go InDouble (c : word) acc rest
| c == '"', quoting == InDouble -> go Unquoted (c : word) acc rest
| otherwise -> go quoting (c : word) acc rest

emit [] acc = acc
emit word acc = Text.pack (reverse word) : acc

prettyPrintJSON :: (?esc :: Char) => [Text] -> Doc ann
prettyPrintJSON args = list (fmap doubleQoute args)

Expand Down Expand Up @@ -293,7 +325,7 @@
CopyArgs {sourcePaths, targetPath}
CopyFlags {chmodFlag, chownFlag, linkFlag, parentsFlag, sourceFlag, excludeFlags} -> do
"COPY"
prettyPrintChown chownFlag

Check warning on line 328 in src/Language/Docker/PrettyPrint.hs

View workflow job for this annotation

GitHub Actions / hlint

Suggestion in prettyPrintInstruction in module Language.Docker.PrettyPrint: Reduce duplication ▫︎ Found: "prettyPrintChown chownFlag\nprettyPrintChmod chmodFlag\npretty linkFlag\n" ▫︎ Perhaps: "Combine with src/Language/Docker/PrettyPrint.hs:364:9-34"
prettyPrintChmod chmodFlag
pretty linkFlag
pretty parentsFlag
Expand Down
12 changes: 12 additions & 0 deletions test/Language/Docker/PrettyPrintSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ spec = do
let run = Run ( RunArgs ( ArgumentsText "foobar" ) def )
in assertPretty "RUN foobar" run

it "keeps && inside a quoted string" $ do
let run = Run ( RunArgs ( ArgumentsText "grep -F 'cd dir && ./run.sh ' /opt/Makefile" ) def )
in assertPretty "RUN grep -F 'cd dir && ./run.sh ' /opt/Makefile" run

it "keeps repeated spaces inside a quoted string" $ do
let run = Run ( RunArgs ( ArgumentsText "echo 'a b'" ) def )
in assertPretty "RUN echo 'a b'" run

it "still breaks the line on an unquoted &&" $ do
let run = Run ( RunArgs ( ArgumentsText "apt-get update && apt-get install -y curl" ) def )
in assertPretty "RUN apt-get update \\\n && apt-get install -y curl" run

it "RUN in JSON format" $ do
let run = Run ( RunArgs ( ArgumentsList "foobar barfoo" ) def )
in assertPretty "RUN [\"foobar\", \"barfoo\"]" run
Expand Down
Loading