language-docker-12.0.0: src/Language/Docker/PrettyPrint.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
module Language.Docker.PrettyPrint where
import Data.List.NonEmpty as NonEmpty (NonEmpty (..), toList)
import Data.Set (Set)
import Data.String (fromString)
import Data.Text (Text)
import Prettyprinter
import Prettyprinter.Internal (Doc (Empty))
import Prettyprinter.Render.Text (renderLazy)
import Language.Docker.Syntax
import Prelude hiding ((<>), (>>))
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.Builder as B
data EscapeAccum
= EscapeAccum
{ buffer :: !B.Builder,
count :: !Int,
escaping :: !Bool
}
-- | Pretty print a 'Dockerfile' to a 'Text'
prettyPrint :: Dockerfile -> L.Text
prettyPrint = renderLazy . layoutPretty opts . prettyPrintDockerfile
where
opts = LayoutOptions Unbounded
prettyPrintDockerfile :: [InstructionPos Text] -> Doc ann
prettyPrintDockerfile instr = doPrint instr <> "\n"
where
doPrint ips =
let ?esc = findEscapeChar ips
in (vsep . fmap prettyPrintInstructionPos ) ips
findEscapeChar :: [InstructionPos args] -> Char
findEscapeChar [] = defaultEsc
findEscapeChar (i:is) =
case i of
InstructionPos {instruction = (Pragma (Escape (EscapeChar c)))} -> c
InstructionPos {instruction = (Pragma _)} -> findEscapeChar is
_ -> defaultEsc
-- | Pretty print a 'InstructionPos' to a 'Doc'
prettyPrintInstructionPos :: (?esc :: Char) => InstructionPos Text -> Doc ann
prettyPrintInstructionPos (InstructionPos i _ _) = prettyPrintInstruction i
prettyPrintImage :: Image -> Doc ann
prettyPrintImage (Image Nothing name) = pretty name
prettyPrintImage (Image (Just (Registry reg)) name) = pretty reg <> "/" <> pretty name
prettyPrintBaseImage :: BaseImage -> Doc ann
prettyPrintBaseImage BaseImage {..} = do
prettyPlatform platform
prettyPrintImage image
prettyTag tag
prettyDigest digest
prettyAlias alias
where
(>>) = (<>)
prettyPlatform maybePlatform =
case maybePlatform of
Nothing -> mempty
Just p -> "--platform=" <> pretty p <> " "
prettyTag maybeTag =
case maybeTag of
Nothing -> mempty
Just (Tag p) -> ":" <> pretty p
prettyAlias maybeAlias =
case maybeAlias of
Nothing -> mempty
Just (ImageAlias a) -> " AS " <> pretty a
prettyDigest maybeDigest =
case maybeDigest of
Nothing -> mempty
Just (Digest d) -> "@" <> pretty d
prettyPrintPairs :: (?esc :: Char) => Pairs -> Doc ann
prettyPrintPairs ps = align $ sepLine $ fmap prettyPrintPair ps
where
sepLine = concatWith (\x y -> x <> " " <> pretty ?esc <> line <> y)
prettyPrintPair :: (?esc :: Char) => (Text, Text) -> Doc ann
prettyPrintPair (k, v) = pretty k <> pretty '=' <> doubleQoute v
prettyPrintArguments :: (?esc :: Char) => Arguments Text -> Doc ann
prettyPrintArguments (ArgumentsList as) = prettyPrintJSON (Text.words as)
prettyPrintArguments (ArgumentsText as) = hsep (fmap helper (Text.words as))
where
helper "&&" = pretty ?esc <> "\n &&"
helper a = pretty a
prettyPrintJSON :: (?esc :: Char) => [Text] -> Doc ann
prettyPrintJSON args = list (fmap doubleQoute args)
doubleQoute :: (?esc :: Char) => Text -> Doc ann
doubleQoute w = enclose dquote dquote (pretty (escapeQuotes w))
escapeQuotes :: (?esc :: Char) => Text -> L.Text
escapeQuotes text =
case Text.foldr accumulate (EscapeAccum mempty 0 False) text of
EscapeAccum buffer _ False -> B.toLazyText buffer
EscapeAccum buffer count True ->
case count `mod` 2 of
0 -> B.toLazyText (B.singleton ?esc <> buffer)
_ -> B.toLazyText buffer
where
accumulate '"' EscapeAccum {buffer, escaping = False} =
EscapeAccum (B.singleton '"' <> buffer) 0 True
accumulate c EscapeAccum {buffer, escaping = True, count}
| c == ?esc = EscapeAccum (B.singleton ?esc <> buffer) (count + 1) True
| even count = EscapeAccum (B.singleton c <> B.singleton ?esc <> buffer) 0 False
| otherwise = EscapeAccum (B.singleton c <> buffer) 0 False -- It was already escaped
accumulate c EscapeAccum {buffer, escaping = False} =
EscapeAccum (B.singleton c <> buffer) 0 False
prettyPrintPortSpec :: PortSpec -> Doc ann
prettyPrintPortSpec (PortSpec port) = pretty port
prettyPrintPortSpec (PortRangeSpec portrange) = pretty portrange
prettyPrintFileList :: NonEmpty SourcePath -> TargetPath -> Doc ann
prettyPrintFileList sources (TargetPath dest) =
let ending =
case (Text.isSuffixOf "/" dest, sources) of
(True, _) -> "" -- If the target ends with / then no extra ending is needed
(_, _fst :| _snd : _) -> "/" -- More than one source means that the target should end in /
_ -> ""
in hsep $ [pretty s | SourcePath s <- toList sources] ++ [pretty dest <> ending]
prettyPrintChown :: Chown -> Doc ann
prettyPrintChown chown =
case chown of
Chown c -> "--chown=" <> pretty c
NoChown -> mempty
prettyPrintChmod :: Chmod -> Doc ann
prettyPrintChmod chmod =
case chmod of
Chmod c -> "--chmod=" <> pretty c
NoChmod -> mempty
prettyPrintLink :: Link -> Doc ann
prettyPrintLink link =
case link of
Link -> "--link"
NoLink -> mempty
prettyPrintCopySource :: CopySource -> Doc ann
prettyPrintCopySource source =
case source of
CopySource c -> "--from=" <> pretty c
NoSource -> mempty
prettyPrintDuration :: Text -> Maybe Duration -> Doc ann
prettyPrintDuration flagName = maybe mempty pp
where
pp (Duration d) = pretty flagName <> pretty (show d)
prettyPrintRetries :: Maybe Retries -> Doc ann
prettyPrintRetries = maybe mempty pp
where
pp (Retries r) = "--retries=" <> pretty r
prettyPrintRunMount :: (?esc :: Char) => Set RunMount -> Doc ann
prettyPrintRunMount set =
foldl (<>) "" (map printSingleMount (Set.toList set))
where
printSingleMount mount = "--mount="
<> case mount of
BindMount BindOpts {..} ->
"type=bind"
<> printTarget bTarget
<> maybe mempty printSource bSource
<> maybe mempty printFromImage bFromImage
<> maybe mempty printReadOnly bReadOnly
CacheMount CacheOpts {..} ->
"type=cache"
<> printTarget cTarget
<> maybe mempty printSharing cSharing
<> maybe mempty printId cCacheId
<> maybe mempty printFromImage cFromImage
<> maybe mempty printSource cSource
<> maybe mempty printMode cMode
<> maybe mempty printUid cUid
<> maybe mempty printGid cGid
<> maybe mempty printReadOnly cReadOnly
SshMount SecretOpts {..} ->
"type=ssh"
<> maybe mempty printTarget sTarget
<> maybe mempty printId sCacheId
<> maybe mempty printSource sSource
<> maybe mempty printMode sMode
<> maybe mempty printUid sUid
<> maybe mempty printGid sGid
<> maybe mempty printRequired sIsRequired
SecretMount SecretOpts {..} ->
"type=secret"
<> maybe mempty printTarget sTarget
<> maybe mempty printId sCacheId
<> maybe mempty printSource sSource
<> maybe mempty printMode sMode
<> maybe mempty printUid sUid
<> maybe mempty printGid sGid
<> maybe mempty printRequired sIsRequired
TmpfsMount TmpOpts {..} -> "type=tmpfs" <> printTarget tTarget
printQuotable str
| Text.any (== '"') str = doubleQoute str
| otherwise = pretty str
printTarget (TargetPath t) = ",target=" <> printQuotable t
printSource (SourcePath s) = ",source=" <> printQuotable s
printFromImage f = ",from=" <> printQuotable f
printSharing sharing = ",sharing="
<> case sharing of
Shared -> "shared"
Private -> "private"
Locked -> "locked"
printId i = ",id=" <> printQuotable i
printMode m = ",mode=" <> pretty m
printUid uid = ",uid=" <> pretty uid
printGid gid = ",gid=" <> pretty gid
printReadOnly True = ",ro"
printReadOnly False = ",rw"
printRequired True = ",required"
printRequired False = mempty
prettyPrintRunNetwork :: Maybe RunNetwork -> Doc ann
prettyPrintRunNetwork Nothing = mempty
prettyPrintRunNetwork (Just NetworkHost) = "--network=host"
prettyPrintRunNetwork (Just NetworkNone) = "--network=none"
prettyPrintRunNetwork (Just NetworkDefault) = "--network=default"
prettyPrintRunSecurity :: Maybe RunSecurity -> Doc ann
prettyPrintRunSecurity Nothing = mempty
prettyPrintRunSecurity (Just Sandbox) = "--security=sandbox"
prettyPrintRunSecurity (Just Insecure) = "--security=insecure"
prettyPrintPragma :: PragmaDirective -> Doc ann
prettyPrintPragma (Escape (EscapeChar esc)) = "escape = " <> pretty esc
prettyPrintPragma (Syntax (SyntaxImage img)) = "syntax = " <> prettyPrintImage img
prettyPrintInstruction :: (?esc :: Char) => Instruction Text -> Doc ann
prettyPrintInstruction i =
case i of
Maintainer m -> do
"MAINTAINER"
pretty m
Arg a Nothing -> do
"ARG"
pretty a
Arg k (Just v) -> do
"ARG"
pretty k <> "=" <> pretty v
Entrypoint e -> do
"ENTRYPOINT"
prettyPrintArguments e
Stopsignal s -> do
"STOPSIGNAL"
pretty s
Workdir w -> do
"WORKDIR"
pretty w
Expose (Ports ps) -> do
"EXPOSE"
hsep (fmap prettyPrintPortSpec ps)
Volume dir -> do
"VOLUME"
pretty dir
Run (RunArgs c RunFlags {mount, network, security}) -> do
"RUN"
prettyPrintRunMount mount
prettyPrintRunNetwork network
prettyPrintRunSecurity security
prettyPrintArguments c
Copy
CopyArgs {sourcePaths, targetPath}
CopyFlags {chmodFlag, chownFlag, linkFlag, sourceFlag} -> do
"COPY"
prettyPrintChown chownFlag
prettyPrintChmod chmodFlag
prettyPrintLink linkFlag
prettyPrintCopySource sourceFlag
prettyPrintFileList sourcePaths targetPath
Cmd c -> do
"CMD"
prettyPrintArguments c
Label l -> do
"LABEL"
prettyPrintPairs l
Env ps -> do
"ENV"
prettyPrintPairs ps
User u -> do
"USER"
pretty u
Pragma p -> do
pretty '#'
prettyPrintPragma p
Comment s -> do
pretty '#'
pretty s
OnBuild i' -> do
"ONBUILD"
prettyPrintInstruction i'
From b -> do
"FROM"
prettyPrintBaseImage b
Add
AddArgs {sourcePaths, targetPath}
AddFlags {chownFlag, chmodFlag, linkFlag} -> do
"ADD"
prettyPrintChown chownFlag
prettyPrintChmod chmodFlag
prettyPrintLink linkFlag
prettyPrintFileList sourcePaths targetPath
Shell args -> do
"SHELL"
prettyPrintArguments args
Healthcheck NoCheck -> "HEALTHCHECK NONE"
Healthcheck (Check CheckArgs {..}) -> do
"HEALTHCHECK"
prettyPrintDuration "--interval=" interval
prettyPrintDuration "--timeout=" timeout
prettyPrintDuration "--start-period=" startPeriod
prettyPrintRetries retries
"CMD"
prettyPrintArguments checkCommand
where
(>>) = spaceCat
spaceCat :: Doc ann -> Doc ann -> Doc ann
spaceCat a Empty = a
spaceCat Empty b = b
spaceCat a b = a <+> b