packages feed

language-docker 1.0.0 → 2.0.0

raw patch · 18 files changed

+1006/−315 lines, 18 filesdep +semigroupsdep +timedep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: semigroups, time

Dependency ranges changed: base

API changes (from Hackage documentation)

- Language.Docker: EOL :: Instruction
- Language.Docker: instruction :: InstructionPos -> Instruction
- Language.Docker: port :: Integer -> Ports
- Language.Docker: sourcename :: InstructionPos -> Filename
- Language.Docker: type Destination = String
- Language.Docker: type Source = String
- Language.Docker.EDSL: port :: Integer -> Ports
- Language.Docker.EDSL.Quasi: filterEOL :: [InstructionPos] -> [InstructionPos]
- Language.Docker.EDSL.Types: Add :: Source -> Destination -> next -> EInstruction next
- Language.Docker.EDSL.Types: Copy :: Source -> Destination -> next -> EInstruction next
- Language.Docker.Lexer: identifier :: Parser String
- Language.Docker.Lexer: lexer :: TokenParser ()
- Language.Docker.Lexer: reservedOp :: String -> Parser ()
- Language.Docker.Parser: eolInstruction :: Parser Instruction
- Language.Docker.Parser: quotedValue :: Parser String
- Language.Docker.Parser: rawValue :: Parser String
- Language.Docker.Parser: remainingPairs :: Parser Pairs
- Language.Docker.Syntax: EOL :: Instruction
- Language.Docker.Syntax: data Ports
- Language.Docker.Syntax: instance Data.String.IsString Language.Docker.Syntax.Ports
- Language.Docker.Syntax: instruction :: InstructionPos -> Instruction
- Language.Docker.Syntax: sourcename :: InstructionPos -> Filename
- Language.Docker.Syntax: type Destination = String
- Language.Docker.Syntax: type Source = String
+ Language.Docker: AddArgs :: NonEmpty SourcePath -> TargetPath -> Chown -> AddArgs
+ Language.Docker: Check :: CheckArgs -> Check
+ Language.Docker: CheckArgs :: Arguments -> Maybe Duration -> Maybe Duration -> Maybe Duration -> Maybe Retries -> CheckArgs
+ Language.Docker: Chown :: String -> Chown
+ Language.Docker: CopyArgs :: NonEmpty SourcePath -> TargetPath -> Chown -> CopySource -> CopyArgs
+ Language.Docker: CopySource :: String -> CopySource
+ Language.Docker: NoCheck :: Check
+ Language.Docker: NoChown :: Chown
+ Language.Docker: NoSource :: CopySource
+ Language.Docker: SourcePath :: String -> SourcePath
+ Language.Docker: TargetPath :: String -> TargetPath
+ Language.Docker: [$sel:checkCommand:CheckArgs] :: CheckArgs -> Arguments
+ Language.Docker: [$sel:chownFlag:AddArgs] :: AddArgs -> Chown
+ Language.Docker: [$sel:chownFlag:CopyArgs] :: CopyArgs -> Chown
+ Language.Docker: [$sel:instruction:InstructionPos] :: InstructionPos -> Instruction
+ Language.Docker: [$sel:interval:CheckArgs] :: CheckArgs -> Maybe Duration
+ Language.Docker: [$sel:lineNumber:InstructionPos] :: InstructionPos -> Linenumber
+ Language.Docker: [$sel:retries:CheckArgs] :: CheckArgs -> Maybe Retries
+ Language.Docker: [$sel:sourceFlag:CopyArgs] :: CopyArgs -> CopySource
+ Language.Docker: [$sel:sourcePaths:AddArgs] :: AddArgs -> NonEmpty SourcePath
+ Language.Docker: [$sel:sourcePaths:CopyArgs] :: CopyArgs -> NonEmpty SourcePath
+ Language.Docker: [$sel:sourcename:InstructionPos] :: InstructionPos -> Filename
+ Language.Docker: [$sel:startPeriod:CheckArgs] :: CheckArgs -> Maybe Duration
+ Language.Docker: [$sel:targetPath:AddArgs] :: AddArgs -> TargetPath
+ Language.Docker: [$sel:targetPath:CopyArgs] :: CopyArgs -> TargetPath
+ Language.Docker: [$sel:timeout:CheckArgs] :: CheckArgs -> Maybe Duration
+ Language.Docker: [$sel:unSourcePath:SourcePath] :: SourcePath -> String
+ Language.Docker: [$sel:unTargetPath:TargetPath] :: TargetPath -> String
+ Language.Docker: aliased :: EBaseImage -> String -> EBaseImage
+ Language.Docker: check :: String -> Check
+ Language.Docker: data AddArgs
+ Language.Docker: data Check
+ Language.Docker: data CheckArgs
+ Language.Docker: data Chown
+ Language.Docker: data CopyArgs
+ Language.Docker: data CopySource
+ Language.Docker: healthcheck :: forall m_aCPR. MonadFree EInstruction m_aCPR => Check -> m_aCPR ()
+ Language.Docker: interval :: Check -> Integer -> Check
+ Language.Docker: newtype SourcePath
+ Language.Docker: newtype TargetPath
+ Language.Docker: retries :: Check -> Integer -> Check
+ Language.Docker: startPeriod :: Check -> Integer -> Check
+ Language.Docker: tcpPort :: Integer -> Port
+ Language.Docker: timeout :: Check -> Integer -> Check
+ Language.Docker: udpPort :: Integer -> Port
+ Language.Docker: variablePort :: String -> Port
+ Language.Docker.EDSL: addArgs :: forall m_aCOZ. MonadFree EInstruction m_aCOZ => NonEmpty SourcePath -> TargetPath -> Chown -> m_aCOZ ()
+ Language.Docker.EDSL: aliased :: EBaseImage -> String -> EBaseImage
+ Language.Docker.EDSL: check :: String -> Check
+ Language.Docker.EDSL: copyArgs :: forall m_aCPd. MonadFree EInstruction m_aCPd => NonEmpty SourcePath -> TargetPath -> Chown -> CopySource -> m_aCPd ()
+ Language.Docker.EDSL: interval :: Check -> Integer -> Check
+ Language.Docker.EDSL: noCheck :: Check
+ Language.Docker.EDSL: portRange :: Integer -> Integer -> Port
+ Language.Docker.EDSL: retries :: Check -> Integer -> Check
+ Language.Docker.EDSL: startPeriod :: Check -> Integer -> Check
+ Language.Docker.EDSL: tcpPort :: Integer -> Port
+ Language.Docker.EDSL: timeout :: Check -> Integer -> Check
+ Language.Docker.EDSL: udpPort :: Integer -> Port
+ Language.Docker.EDSL: variablePort :: String -> Port
+ Language.Docker.EDSL.Types: AddArgs :: (NonEmpty SourcePath) -> TargetPath -> Chown -> next -> EInstruction next
+ Language.Docker.EDSL.Types: CopyArgs :: (NonEmpty SourcePath) -> TargetPath -> Chown -> CopySource -> next -> EInstruction next
+ Language.Docker.Lexer: caseInsensitiveChar :: Char -> Parser Char
+ Language.Docker.Lexer: caseInsensitiveString :: String -> Parser String
+ Language.Docker.Lexer: charsWithEscapedSpaces :: String -> Parser String
+ Language.Docker.Lexer: space :: Parser ()
+ Language.Docker.Lexer: spaces :: Parser ()
+ Language.Docker.Lexer: spaces1 :: Parser ()
+ Language.Docker.Lexer: symbol :: String -> Parser String
+ Language.Docker.Lexer: whiteSpace :: Parser ()
+ Language.Docker.Parser: CFlagInvalid :: (String, String) -> CheckFlag
+ Language.Docker.Parser: FlagChown :: Chown -> CopyFlag
+ Language.Docker.Parser: FlagInterval :: Duration -> CheckFlag
+ Language.Docker.Parser: FlagInvalid :: (String, String) -> CopyFlag
+ Language.Docker.Parser: FlagRetries :: Retries -> CheckFlag
+ Language.Docker.Parser: FlagSource :: CopySource -> CopyFlag
+ Language.Docker.Parser: FlagStartPeriod :: Duration -> CheckFlag
+ Language.Docker.Parser: FlagTimeout :: Duration -> CheckFlag
+ Language.Docker.Parser: anyFlag :: Parser (String, String)
+ Language.Docker.Parser: checkFlag :: Parser CheckFlag
+ Language.Docker.Parser: chown :: Parser Chown
+ Language.Docker.Parser: copyFlag :: Parser CopyFlag
+ Language.Docker.Parser: copySource :: Parser CopySource
+ Language.Docker.Parser: data CheckFlag
+ Language.Docker.Parser: data CopyFlag
+ Language.Docker.Parser: doubleQuotedValue :: Parser String
+ Language.Docker.Parser: durationFlag :: String -> Parser Duration
+ Language.Docker.Parser: fileList :: String -> (NonEmpty SourcePath -> TargetPath -> Instruction) -> Parser Instruction
+ Language.Docker.Parser: imageAlias :: Parser ImageAlias
+ Language.Docker.Parser: maybeImageAlias :: Parser (Maybe ImageAlias)
+ Language.Docker.Parser: pairsList :: Parser Pairs
+ Language.Docker.Parser: port :: Parser Port
+ Language.Docker.Parser: portInt :: Parser Port
+ Language.Docker.Parser: portRange :: Parser Port
+ Language.Docker.Parser: portVariable :: Parser Port
+ Language.Docker.Parser: portWithProtocol :: Parser Port
+ Language.Docker.Parser: ports :: Parser Ports
+ Language.Docker.Parser: retriesFlag :: Parser Retries
+ Language.Docker.Parser: singlePair :: Parser Pairs
+ Language.Docker.Parser: singleQuotedValue :: Parser String
+ Language.Docker.Parser: unexpectedFlag :: String -> String -> Parser a
+ Language.Docker.Parser: unquotedString :: String -> Parser String
+ Language.Docker.PrettyPrint: prettyPrintChown :: Chown -> Doc
+ Language.Docker.PrettyPrint: prettyPrintCopySource :: CopySource -> Doc
+ Language.Docker.PrettyPrint: prettyPrintDuration :: String -> Maybe Duration -> Doc
+ Language.Docker.PrettyPrint: prettyPrintFileList :: NonEmpty SourcePath -> TargetPath -> Doc
+ Language.Docker.PrettyPrint: prettyPrintPort :: Port -> Doc
+ Language.Docker.PrettyPrint: prettyPrintRetries :: Maybe Retries -> Doc
+ Language.Docker.Syntax: AddArgs :: NonEmpty SourcePath -> TargetPath -> Chown -> AddArgs
+ Language.Docker.Syntax: Check :: CheckArgs -> Check
+ Language.Docker.Syntax: CheckArgs :: Arguments -> Maybe Duration -> Maybe Duration -> Maybe Duration -> Maybe Retries -> CheckArgs
+ Language.Docker.Syntax: Chown :: String -> Chown
+ Language.Docker.Syntax: CopyArgs :: NonEmpty SourcePath -> TargetPath -> Chown -> CopySource -> CopyArgs
+ Language.Docker.Syntax: CopySource :: String -> CopySource
+ Language.Docker.Syntax: Duration :: DiffTime -> Duration
+ Language.Docker.Syntax: ImageAlias :: String -> ImageAlias
+ Language.Docker.Syntax: NoCheck :: Check
+ Language.Docker.Syntax: NoChown :: Chown
+ Language.Docker.Syntax: NoSource :: CopySource
+ Language.Docker.Syntax: Port :: Integer -> Protocol -> Port
+ Language.Docker.Syntax: PortRange :: Integer -> Integer -> Port
+ Language.Docker.Syntax: Retries :: Int -> Retries
+ Language.Docker.Syntax: SourcePath :: String -> SourcePath
+ Language.Docker.Syntax: TCP :: Protocol
+ Language.Docker.Syntax: TargetPath :: String -> TargetPath
+ Language.Docker.Syntax: UDP :: Protocol
+ Language.Docker.Syntax: [$sel:checkCommand:CheckArgs] :: CheckArgs -> Arguments
+ Language.Docker.Syntax: [$sel:chownFlag:AddArgs] :: AddArgs -> Chown
+ Language.Docker.Syntax: [$sel:chownFlag:CopyArgs] :: CopyArgs -> Chown
+ Language.Docker.Syntax: [$sel:durationTime:Duration] :: Duration -> DiffTime
+ Language.Docker.Syntax: [$sel:instruction:InstructionPos] :: InstructionPos -> Instruction
+ Language.Docker.Syntax: [$sel:interval:CheckArgs] :: CheckArgs -> Maybe Duration
+ Language.Docker.Syntax: [$sel:lineNumber:InstructionPos] :: InstructionPos -> Linenumber
+ Language.Docker.Syntax: [$sel:retries:CheckArgs] :: CheckArgs -> Maybe Retries
+ Language.Docker.Syntax: [$sel:sourceFlag:CopyArgs] :: CopyArgs -> CopySource
+ Language.Docker.Syntax: [$sel:sourcePaths:AddArgs] :: AddArgs -> NonEmpty SourcePath
+ Language.Docker.Syntax: [$sel:sourcePaths:CopyArgs] :: CopyArgs -> NonEmpty SourcePath
+ Language.Docker.Syntax: [$sel:sourcename:InstructionPos] :: InstructionPos -> Filename
+ Language.Docker.Syntax: [$sel:startPeriod:CheckArgs] :: CheckArgs -> Maybe Duration
+ Language.Docker.Syntax: [$sel:targetPath:AddArgs] :: AddArgs -> TargetPath
+ Language.Docker.Syntax: [$sel:targetPath:CopyArgs] :: CopyArgs -> TargetPath
+ Language.Docker.Syntax: [$sel:timeout:CheckArgs] :: CheckArgs -> Maybe Duration
+ Language.Docker.Syntax: [$sel:times:Retries] :: Retries -> Int
+ Language.Docker.Syntax: [$sel:unImageAlias:ImageAlias] :: ImageAlias -> String
+ Language.Docker.Syntax: [$sel:unPorts:Ports] :: Ports -> [Port]
+ Language.Docker.Syntax: [$sel:unSourcePath:SourcePath] :: SourcePath -> String
+ Language.Docker.Syntax: [$sel:unTargetPath:TargetPath] :: TargetPath -> String
+ Language.Docker.Syntax: data AddArgs
+ Language.Docker.Syntax: data Check
+ Language.Docker.Syntax: data CheckArgs
+ Language.Docker.Syntax: data Chown
+ Language.Docker.Syntax: data CopyArgs
+ Language.Docker.Syntax: data CopySource
+ Language.Docker.Syntax: data Port
+ Language.Docker.Syntax: data Protocol
+ Language.Docker.Syntax: instance Data.String.IsString Language.Docker.Syntax.ImageAlias
+ Language.Docker.Syntax: instance Data.String.IsString Language.Docker.Syntax.SourcePath
+ Language.Docker.Syntax: instance Data.String.IsString Language.Docker.Syntax.TargetPath
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.AddArgs
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.Check
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.CheckArgs
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.Chown
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.CopyArgs
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.CopySource
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.Duration
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.ImageAlias
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.Port
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.Protocol
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.Retries
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.SourcePath
+ Language.Docker.Syntax: instance GHC.Classes.Eq Language.Docker.Syntax.TargetPath
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.AddArgs
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.Check
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.CheckArgs
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.Chown
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.CopyArgs
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.CopySource
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.Duration
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.ImageAlias
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.Port
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.Protocol
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.Retries
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.SourcePath
+ Language.Docker.Syntax: instance GHC.Classes.Ord Language.Docker.Syntax.TargetPath
+ Language.Docker.Syntax: instance GHC.Exts.IsList Language.Docker.Syntax.Ports
+ Language.Docker.Syntax: instance GHC.Num.Num Language.Docker.Syntax.Duration
+ Language.Docker.Syntax: instance GHC.Num.Num Language.Docker.Syntax.Retries
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.AddArgs
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.Check
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.CheckArgs
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.Chown
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.CopyArgs
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.CopySource
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.Duration
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.ImageAlias
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.Port
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.Protocol
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.Retries
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.SourcePath
+ Language.Docker.Syntax: instance GHC.Show.Show Language.Docker.Syntax.TargetPath
+ Language.Docker.Syntax: newtype Duration
+ Language.Docker.Syntax: newtype ImageAlias
+ Language.Docker.Syntax: newtype Ports
+ Language.Docker.Syntax: newtype Retries
+ Language.Docker.Syntax: newtype SourcePath
+ Language.Docker.Syntax: newtype TargetPath
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift (Data.Fixed.Fixed a)
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Data.Time.Clock.Internal.DiffTime.DiffTime
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.AddArgs
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.Check
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.CheckArgs
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.Chown
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.CopyArgs
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.CopySource
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.Duration
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.ImageAlias
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.Port
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.Protocol
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.Retries
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.SourcePath
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift Language.Docker.Syntax.TargetPath
+ Language.Docker.Syntax.Lift: instance Language.Haskell.TH.Syntax.Lift a => Language.Haskell.TH.Syntax.Lift (Data.List.NonEmpty.NonEmpty a)
- Language.Docker: Add :: Source -> Destination -> Instruction
+ Language.Docker: Add :: AddArgs -> Instruction
- Language.Docker: Arg :: String -> Instruction
+ Language.Docker: Arg :: String -> (Maybe String) -> Instruction
- Language.Docker: Copy :: Source -> Destination -> Instruction
+ Language.Docker: Copy :: CopyArgs -> Instruction
- Language.Docker: DigestedImage :: Image -> ByteString -> BaseImage
+ Language.Docker: DigestedImage :: Image -> ByteString -> (Maybe ImageAlias) -> BaseImage
- Language.Docker: EDigestedImage :: String -> ByteString -> EBaseImage
+ Language.Docker: EDigestedImage :: String -> ByteString -> (Maybe ImageAlias) -> EBaseImage
- Language.Docker: ETaggedImage :: String -> String -> EBaseImage
+ Language.Docker: ETaggedImage :: String -> String -> (Maybe ImageAlias) -> EBaseImage
- Language.Docker: EUntaggedImage :: String -> EBaseImage
+ Language.Docker: EUntaggedImage :: String -> (Maybe ImageAlias) -> EBaseImage
- Language.Docker: Healthcheck :: String -> Instruction
+ Language.Docker: Healthcheck :: Check -> Instruction
- Language.Docker: TaggedImage :: Image -> Tag -> BaseImage
+ Language.Docker: TaggedImage :: Image -> Tag -> (Maybe ImageAlias) -> BaseImage
- Language.Docker: UntaggedImage :: Image -> BaseImage
+ Language.Docker: UntaggedImage :: Image -> (Maybe ImageAlias) -> BaseImage
- Language.Docker: add :: forall m_asMu. MonadFree EInstruction m_asMu => Source -> Destination -> m_asMu ()
+ Language.Docker: add :: MonadFree EInstruction m => NonEmpty SourcePath -> TargetPath -> m ()
- Language.Docker: arg :: forall m_asNc. MonadFree EInstruction m_asNc => String -> m_asNc ()
+ Language.Docker: arg :: forall m_aCPK. MonadFree EInstruction m_aCPK => String -> Maybe String -> m_aCPK ()
- Language.Docker: cmdArgs :: forall m_asMO. MonadFree EInstruction m_asMO => Arguments -> m_asMO ()
+ Language.Docker: cmdArgs :: forall m_aCPm. MonadFree EInstruction m_aCPm => Arguments -> m_aCPm ()
- Language.Docker: comment :: forall m_asNf. MonadFree EInstruction m_asNf => String -> m_asNf ()
+ Language.Docker: comment :: forall m_aCPO. MonadFree EInstruction m_aCPO => String -> m_aCPO ()
- Language.Docker: copy :: forall m_asMH. MonadFree EInstruction m_asMH => Source -> Destination -> m_asMH ()
+ Language.Docker: copy :: MonadFree EInstruction m => NonEmpty SourcePath -> TargetPath -> m ()
- Language.Docker: embed :: forall m_asNo. MonadFree EInstruction m_asNo => [InstructionPos] -> m_asNo ()
+ Language.Docker: embed :: forall m_aCPX. MonadFree EInstruction m_aCPX => [InstructionPos] -> m_aCPX ()
- Language.Docker: entrypointArgs :: forall m_asN3. MonadFree EInstruction m_asN3 => Arguments -> m_asN3 ()
+ Language.Docker: entrypointArgs :: forall m_aCPB. MonadFree EInstruction m_aCPB => Arguments -> m_aCPB ()
- Language.Docker: env :: forall m_asN9. MonadFree EInstruction m_asN9 => Pairs -> m_asN9 ()
+ Language.Docker: env :: forall m_aCPH. MonadFree EInstruction m_aCPH => Pairs -> m_aCPH ()
- Language.Docker: expose :: forall m_asMX. MonadFree EInstruction m_asMX => Ports -> m_asMX ()
+ Language.Docker: expose :: forall m_aCPv. MonadFree EInstruction m_aCPv => Ports -> m_aCPv ()
- Language.Docker: from :: forall m_asLN. MonadFree EInstruction m_asLN => EBaseImage -> m_asLN ()
+ Language.Docker: from :: forall m_aCOk. MonadFree EInstruction m_aCOk => EBaseImage -> m_aCOk ()
- Language.Docker: label :: forall m_asMB. MonadFree EInstruction m_asMB => Pairs -> m_asMB ()
+ Language.Docker: label :: forall m_aCP7. MonadFree EInstruction m_aCP7 => Pairs -> m_aCP7 ()
- Language.Docker: liftIO :: MonadIO m => forall a. IO a -> m a
+ Language.Docker: liftIO :: MonadIO m => forall a. () => IO a -> m a
- Language.Docker: maintainer :: forall m_asN6. MonadFree EInstruction m_asN6 => String -> m_asN6 ()
+ Language.Docker: maintainer :: forall m_aCPE. MonadFree EInstruction m_aCPE => String -> m_aCPE ()
- Language.Docker: onBuildRaw :: forall m_asNl. MonadFree EInstruction m_asNl => Instruction -> m_asNl ()
+ Language.Docker: onBuildRaw :: forall m_aCPU. MonadFree EInstruction m_aCPU => Instruction -> m_aCPU ()
- Language.Docker: ports :: [Integer] -> Ports
+ Language.Docker: ports :: [Port] -> Ports
- Language.Docker: runArgs :: forall m_asML. MonadFree EInstruction m_asML => Arguments -> m_asML ()
+ Language.Docker: runArgs :: forall m_aCPj. MonadFree EInstruction m_aCPj => Arguments -> m_aCPj ()
- Language.Docker: stopSignal :: forall m_asME. MonadFree EInstruction m_asME => String -> m_asME ()
+ Language.Docker: stopSignal :: forall m_aCPa. MonadFree EInstruction m_aCPa => String -> m_aCPa ()
- Language.Docker: user :: forall m_asMy. MonadFree EInstruction m_asMy => String -> m_asMy ()
+ Language.Docker: user :: forall m_aCP4. MonadFree EInstruction m_aCP4 => String -> m_aCP4 ()
- Language.Docker: volume :: forall m_asN0. MonadFree EInstruction m_asN0 => String -> m_asN0 ()
+ Language.Docker: volume :: forall m_aCPy. MonadFree EInstruction m_aCPy => String -> m_aCPy ()
- Language.Docker: workdir :: forall m_asMU. MonadFree EInstruction m_asMU => Directory -> m_asMU ()
+ Language.Docker: workdir :: forall m_aCPs. MonadFree EInstruction m_aCPs => Directory -> m_aCPs ()
- Language.Docker.EDSL: add :: forall m_asMu. MonadFree EInstruction m_asMu => Source -> Destination -> m_asMu ()
+ Language.Docker.EDSL: add :: MonadFree EInstruction m => NonEmpty SourcePath -> TargetPath -> m ()
- Language.Docker.EDSL: arg :: forall m_asNc. MonadFree EInstruction m_asNc => String -> m_asNc ()
+ Language.Docker.EDSL: arg :: forall m_aCPK. MonadFree EInstruction m_aCPK => String -> Maybe String -> m_aCPK ()
- Language.Docker.EDSL: cmdArgs :: forall m_asMO. MonadFree EInstruction m_asMO => Arguments -> m_asMO ()
+ Language.Docker.EDSL: cmdArgs :: forall m_aCPm. MonadFree EInstruction m_aCPm => Arguments -> m_aCPm ()
- Language.Docker.EDSL: comment :: forall m_asNf. MonadFree EInstruction m_asNf => String -> m_asNf ()
+ Language.Docker.EDSL: comment :: forall m_aCPO. MonadFree EInstruction m_aCPO => String -> m_aCPO ()
- Language.Docker.EDSL: copy :: forall m_asMH. MonadFree EInstruction m_asMH => Source -> Destination -> m_asMH ()
+ Language.Docker.EDSL: copy :: MonadFree EInstruction m => NonEmpty SourcePath -> TargetPath -> m ()
- Language.Docker.EDSL: embed :: forall m_asNo. MonadFree EInstruction m_asNo => [InstructionPos] -> m_asNo ()
+ Language.Docker.EDSL: embed :: forall m_aCPX. MonadFree EInstruction m_aCPX => [InstructionPos] -> m_aCPX ()
- Language.Docker.EDSL: entrypointArgs :: forall m_asN3. MonadFree EInstruction m_asN3 => Arguments -> m_asN3 ()
+ Language.Docker.EDSL: entrypointArgs :: forall m_aCPB. MonadFree EInstruction m_aCPB => Arguments -> m_aCPB ()
- Language.Docker.EDSL: env :: forall m_asN9. MonadFree EInstruction m_asN9 => Pairs -> m_asN9 ()
+ Language.Docker.EDSL: env :: forall m_aCPH. MonadFree EInstruction m_aCPH => Pairs -> m_aCPH ()
- Language.Docker.EDSL: expose :: forall m_asMX. MonadFree EInstruction m_asMX => Ports -> m_asMX ()
+ Language.Docker.EDSL: expose :: forall m_aCPv. MonadFree EInstruction m_aCPv => Ports -> m_aCPv ()
- Language.Docker.EDSL: from :: forall m_asLN. MonadFree EInstruction m_asLN => EBaseImage -> m_asLN ()
+ Language.Docker.EDSL: from :: forall m_aCOk. MonadFree EInstruction m_aCOk => EBaseImage -> m_aCOk ()
- Language.Docker.EDSL: healthcheck :: forall m_asNi. MonadFree EInstruction m_asNi => String -> m_asNi ()
+ Language.Docker.EDSL: healthcheck :: forall m_aCPR. MonadFree EInstruction m_aCPR => Check -> m_aCPR ()
- Language.Docker.EDSL: label :: forall m_asMB. MonadFree EInstruction m_asMB => Pairs -> m_asMB ()
+ Language.Docker.EDSL: label :: forall m_aCP7. MonadFree EInstruction m_aCP7 => Pairs -> m_aCP7 ()
- Language.Docker.EDSL: maintainer :: forall m_asN6. MonadFree EInstruction m_asN6 => String -> m_asN6 ()
+ Language.Docker.EDSL: maintainer :: forall m_aCPE. MonadFree EInstruction m_aCPE => String -> m_aCPE ()
- Language.Docker.EDSL: onBuildRaw :: forall m_asNl. MonadFree EInstruction m_asNl => Instruction -> m_asNl ()
+ Language.Docker.EDSL: onBuildRaw :: forall m_aCPU. MonadFree EInstruction m_aCPU => Instruction -> m_aCPU ()
- Language.Docker.EDSL: ports :: [Integer] -> Ports
+ Language.Docker.EDSL: ports :: [Port] -> Ports
- Language.Docker.EDSL: runArgs :: forall m_asML. MonadFree EInstruction m_asML => Arguments -> m_asML ()
+ Language.Docker.EDSL: runArgs :: forall m_aCPj. MonadFree EInstruction m_aCPj => Arguments -> m_aCPj ()
- Language.Docker.EDSL: shell :: forall m_asMR. MonadFree EInstruction m_asMR => Arguments -> m_asMR ()
+ Language.Docker.EDSL: shell :: forall m_aCPp. MonadFree EInstruction m_aCPp => Arguments -> m_aCPp ()
- Language.Docker.EDSL: stopSignal :: forall m_asME. MonadFree EInstruction m_asME => String -> m_asME ()
+ Language.Docker.EDSL: stopSignal :: forall m_aCPa. MonadFree EInstruction m_aCPa => String -> m_aCPa ()
- Language.Docker.EDSL: user :: forall m_asMy. MonadFree EInstruction m_asMy => String -> m_asMy ()
+ Language.Docker.EDSL: user :: forall m_aCP4. MonadFree EInstruction m_aCP4 => String -> m_aCP4 ()
- Language.Docker.EDSL: volume :: forall m_asN0. MonadFree EInstruction m_asN0 => String -> m_asN0 ()
+ Language.Docker.EDSL: volume :: forall m_aCPy. MonadFree EInstruction m_aCPy => String -> m_aCPy ()
- Language.Docker.EDSL: workdir :: forall m_asMU. MonadFree EInstruction m_asMU => Directory -> m_asMU ()
+ Language.Docker.EDSL: workdir :: forall m_aCPs. MonadFree EInstruction m_aCPs => Directory -> m_aCPs ()
- Language.Docker.EDSL.Types: Arg :: String -> next -> EInstruction next
+ Language.Docker.EDSL.Types: Arg :: String -> (Maybe String) -> next -> EInstruction next
- Language.Docker.EDSL.Types: EDigestedImage :: String -> ByteString -> EBaseImage
+ Language.Docker.EDSL.Types: EDigestedImage :: String -> ByteString -> (Maybe ImageAlias) -> EBaseImage
- Language.Docker.EDSL.Types: ETaggedImage :: String -> String -> EBaseImage
+ Language.Docker.EDSL.Types: ETaggedImage :: String -> String -> (Maybe ImageAlias) -> EBaseImage
- Language.Docker.EDSL.Types: EUntaggedImage :: String -> EBaseImage
+ Language.Docker.EDSL.Types: EUntaggedImage :: String -> (Maybe ImageAlias) -> EBaseImage
- Language.Docker.EDSL.Types: Healthcheck :: String -> next -> EInstruction next
+ Language.Docker.EDSL.Types: Healthcheck :: Check -> next -> EInstruction next
- Language.Docker.Parser: singleValue :: Parser String
+ Language.Docker.Parser: singleValue :: String -> Parser String
- Language.Docker.Predef: addGlob :: (MonadIO m, MonadFree EInstruction m) => String -> Destination -> m ()
+ Language.Docker.Predef: addGlob :: (MonadIO m, MonadFree EInstruction m) => String -> TargetPath -> m ()
- Language.Docker.Predef: copyGlob :: (MonadIO m, MonadFree EInstruction m) => String -> Destination -> m ()
+ Language.Docker.Predef: copyGlob :: (MonadIO m, MonadFree EInstruction m) => String -> TargetPath -> m ()
- Language.Docker.Syntax: Add :: Source -> Destination -> Instruction
+ Language.Docker.Syntax: Add :: AddArgs -> Instruction
- Language.Docker.Syntax: Arg :: String -> Instruction
+ Language.Docker.Syntax: Arg :: String -> (Maybe String) -> Instruction
- Language.Docker.Syntax: Copy :: Source -> Destination -> Instruction
+ Language.Docker.Syntax: Copy :: CopyArgs -> Instruction
- Language.Docker.Syntax: DigestedImage :: Image -> ByteString -> BaseImage
+ Language.Docker.Syntax: DigestedImage :: Image -> ByteString -> (Maybe ImageAlias) -> BaseImage
- Language.Docker.Syntax: Healthcheck :: String -> Instruction
+ Language.Docker.Syntax: Healthcheck :: Check -> Instruction
- Language.Docker.Syntax: PortStr :: String -> Ports
+ Language.Docker.Syntax: PortStr :: String -> Port
- Language.Docker.Syntax: Ports :: [Integer] -> Ports
+ Language.Docker.Syntax: Ports :: [Port] -> Ports
- Language.Docker.Syntax: TaggedImage :: Image -> Tag -> BaseImage
+ Language.Docker.Syntax: TaggedImage :: Image -> Tag -> (Maybe ImageAlias) -> BaseImage
- Language.Docker.Syntax: UntaggedImage :: Image -> BaseImage
+ Language.Docker.Syntax: UntaggedImage :: Image -> (Maybe ImageAlias) -> BaseImage

Files

README.md view
@@ -1,11 +1,13 @@+[![Build Status][travis-img]][travis]+[![Hackage][hackage-img]][hackage]+[![GPL-3 licensed][license-img]][license]+ # haskell-language-docker-[Hackage](https://hackage.haskell.org/package/language-docker) -- - - Dockerfile parser, pretty-printer and embedded DSL -Provides de ability to parse docker files, a pretty-printer and EDSL for writting Dockerfiles in-Haskell.+Provides de ability to parse docker files, a pretty-printer and EDSL for+writting Dockerfiles in Haskell.  - [Parsing files](#parsing-files) - [Parsing strings](#parsing-strings)@@ -16,6 +18,7 @@ - [Using IO in the DSL](#using-io-in-the-dsl)  ## Parsing files+ ```haskell import Language.Docker main = do@@ -24,6 +27,7 @@ ```  ## Parsing strings+ ```haskell import Language.Docker main = do@@ -32,6 +36,7 @@ ```  ## Pretty-printing files+ ```haskell import Language.Docker main = do@@ -40,6 +45,7 @@ ```  ## Writing Dockerfiles in Haskell+ ```haskell {-# LANGUAGE OverloadedStrings #-} import Language.Docker@@ -51,6 +57,7 @@ ```  ## Using the QuasiQuoter+ ```haskell {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes       #-}@@ -66,17 +73,21 @@ ```  ## Templating Dockerfiles in Haskell+ ```haskell {-# LANGUAGE FlexibleContexts  #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-} import Control.Monad import Language.Docker+import Data.String (fromString)+ tags = ["7.8", "7.10", "8"] cabalSandboxBuild packageName = do     let cabalFile = packageName ++ ".cabal"     run "cabal sandbox init"     run "cabal update"-    add cabalFile ("/app/" ++ cabalFile)+    add [fromString cabalFile] (fromString $ "/app/" ++ cabalFile)     run "cabal install --only-dep -j"     add "." "/app/"     run "cabal build"@@ -94,10 +105,12 @@  ```haskell {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-} import           Language.Docker import qualified System.Directory     as Directory import qualified System.FilePath      as FilePath import qualified System.FilePath.Glob as Glob+import Data.String (fromString) main = do     str <- toDockerfileStrIO $ do         fs <- liftIO $ do@@ -105,9 +118,13 @@             fs <- Glob.glob "./test/*.hs"             return (map (FilePath.makeRelative cwd) fs)         from "ubuntu"-        mapM_ (\f -> add f ("/app/" ++ FilePath.takeFileName f)) fs+	mapM_ (\f -> add [fromString f] (fromString $ "/app/" ++ takeFileName f)) fs     putStr str ``` -## License-GPLv3+[hackage-img]: https://img.shields.io/hackage/v/language-docker.svg+[hackage]: https://hackage.haskell.org/package/language-docker+[travis-img]: https://travis-ci.org/hadolint/language-docker.svg?branch=master+[travis]: https://travis-ci.org/hadolint/language-docker+[license-img]: https://img.shields.io/badge/license-GPL--3-blue.svg+[license]: https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)
language-docker.cabal view
@@ -1,12 +1,14 @@--- This file has been generated from package.yaml by hpack version 0.17.1.+-- This file has been generated from package.yaml by hpack version 0.20.0. -- -- see: https://github.com/sol/hpack+--+-- hash: 36a6870b23b3361a5461b7bc8d6898e79dbe2fbba21bd305461ca8433e1dda3d  name:           language-docker-version:        1.0.0+version:        2.0.0 synopsis:       Dockerfile parser, pretty-printer and embedded DSL-description:     All functions for parsing, printing and writting Dockerfiles are exported through @Language.Docker@. For more fine-grained operations look for specific modules that implement a certain functionality.-                See the <https://github.com/hadolint/language-dockerfile GitHub project> for the source-code and examples. +description:    All functions for parsing, printing and writting Dockerfiles are exported through @Language.Docker@. For more fine-grained operations look for specific modules that implement a certain functionality.+                See the <https://github.com/hadolint/language-docker GitHub project> for the source-code and examples. category:       Development homepage:       https://github.com/hadolint/language-docker#readme bug-reports:    https://github.com/hadolint/language-docker/issues@@ -35,24 +37,26 @@       src   ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-orphans   build-depends:-      base >=4.8 && <5+      Glob+    , aeson+    , base >=4.8 && <5     , bytestring >=0.10+    , directory+    , filepath+    , free+    , mtl     , parsec >=3.1     , pretty+    , semigroups     , split >=0.2-    , free-    , mtl-    , transformers     , template-haskell+    , text     , th-lift     , th-lift-instances-    , text-    , yaml-    , aeson-    , Glob+    , time+    , transformers     , unordered-containers-    , directory-    , filepath+    , yaml   exposed-modules:       Language.Docker       Language.Docker.Parser@@ -76,36 +80,36 @@   hs-source-dirs:       test   build-depends:-      base >=4.8 && <5+      Glob+    , HUnit >=1.2+    , QuickCheck+    , aeson+    , base >=4.8 && <5     , bytestring >=0.10+    , directory+    , filepath+    , free+    , hspec+    , language-docker+    , mtl     , parsec >=3.1     , pretty+    , process+    , semigroups     , split >=0.2-    , free-    , mtl-    , transformers     , template-haskell+    , text     , th-lift     , th-lift-instances-    , text-    , yaml-    , aeson-    , Glob+    , time+    , transformers     , unordered-containers-    , directory-    , filepath-    , hspec-    , QuickCheck-    , language-docker-    , Glob-    , directory-    , filepath-    , process-    , HUnit >=1.2+    , yaml   other-modules:       Language.Docker.EDSL.PluginsSpec       Language.Docker.EDSL.QuasiSpec       Language.Docker.EDSLSpec       Language.Docker.ExamplesSpec       Language.Docker.ParserSpec+      Paths_language_docker   default-language: Haskell2010
src/Language/Docker.hs view
@@ -18,6 +18,7 @@     , Language.Docker.EDSL.tagged     , Language.Docker.EDSL.untagged     , Language.Docker.EDSL.digested+    , Language.Docker.EDSL.aliased       -- ** Syntax     , Language.Docker.EDSL.add     , Language.Docker.EDSL.user@@ -28,10 +29,18 @@     , Language.Docker.EDSL.runArgs     , Language.Docker.EDSL.cmd     , Language.Docker.EDSL.cmdArgs+    , Language.Docker.EDSL.healthcheck+    , Language.Docker.EDSL.check+    , Language.Docker.EDSL.interval+    , Language.Docker.EDSL.timeout+    , Language.Docker.EDSL.startPeriod+    , Language.Docker.EDSL.retries     , Language.Docker.EDSL.workdir     , Language.Docker.EDSL.expose     , Language.Docker.EDSL.ports-    , Language.Docker.EDSL.port+    , Language.Docker.EDSL.tcpPort+    , Language.Docker.EDSL.udpPort+    , Language.Docker.EDSL.variablePort     , Language.Docker.EDSL.volume     , Language.Docker.EDSL.entrypoint     , Language.Docker.EDSL.entrypointArgs@@ -53,12 +62,18 @@     , Language.Docker.Syntax.Instruction(..)     , Language.Docker.Syntax.InstructionPos(..)     , Language.Docker.Syntax.BaseImage(..)+    , Language.Docker.Syntax.SourcePath(..)+    , Language.Docker.Syntax.TargetPath(..)+    , Language.Docker.Syntax.Chown(..)+    , Language.Docker.Syntax.CopySource(..)+    , Language.Docker.Syntax.CopyArgs(..)+    , Language.Docker.Syntax.AddArgs(..)+    , Language.Docker.Syntax.Check(..)+    , Language.Docker.Syntax.CheckArgs(..)     , Language.Docker.Syntax.Image     , Language.Docker.Syntax.Tag     , Language.Docker.Syntax.Ports     , Language.Docker.Syntax.Directory-    , Language.Docker.Syntax.Source-    , Language.Docker.Syntax.Destination     , Language.Docker.Syntax.Arguments     , Language.Docker.Syntax.Pairs     , Language.Docker.Syntax.Filename@@ -66,9 +81,7 @@     -- * Re-exports from @parsec@     , ParseError     -- * Instruction and InstructionPos helpers-    , Language.Docker.Syntax.instruction     , Language.Docker.EDSL.instructionPos-    , Language.Docker.Syntax.sourcename     ) where  import qualified Control.Monad.IO.Class
src/Language/Docker/EDSL.hs view
@@ -9,6 +9,7 @@ import Control.Monad.Trans.Free (FreeT, iterTM) import Control.Monad.Writer import Data.ByteString (ByteString)+import Data.List.NonEmpty (NonEmpty)  import qualified Language.Docker.PrettyPrint as PrettyPrint import qualified Language.Docker.Syntax as Syntax@@ -45,16 +46,16 @@ runD :: MonadWriter [Syntax.Instruction] m => EInstruction (m b) -> m b runD (From bi n) =     case bi of-        EUntaggedImage bi' -> runDef Syntax.From (Syntax.UntaggedImage bi') n-        ETaggedImage bi' tg -> runDef Syntax.From (Syntax.TaggedImage bi' tg) n-        EDigestedImage bi' d -> runDef Syntax.From (Syntax.DigestedImage bi' d) n+        EUntaggedImage bi' alias -> runDef Syntax.From (Syntax.UntaggedImage bi' alias) n+        ETaggedImage bi' tg alias -> runDef Syntax.From (Syntax.TaggedImage bi' tg alias) n+        EDigestedImage bi' d alias -> runDef Syntax.From (Syntax.DigestedImage bi' d alias) n runD (CmdArgs as n) = runDef Syntax.Cmd as n runD (Shell as n) = runDef Syntax.Shell as n-runD (Add s d n) = runDef2 Syntax.Add s d n+runD (AddArgs s d c n) = runDef Syntax.Add (Syntax.AddArgs s d c) n runD (User u n) = runDef Syntax.User u n runD (Label ps n) = runDef Syntax.Label ps n runD (StopSignal s n) = runDef Syntax.Stopsignal s n-runD (Copy s d n) = runDef2 Syntax.Copy s d n+runD (CopyArgs s d c f n) = runDef Syntax.Copy (Syntax.CopyArgs s d c f) n runD (RunArgs as n) = runDef Syntax.Run as n runD (Workdir d n) = runDef Syntax.Workdir d n runD (Expose ps n) = runDef Syntax.Expose ps n@@ -62,7 +63,7 @@ runD (EntrypointArgs e n) = runDef Syntax.Entrypoint e n runD (Maintainer m n) = runDef Syntax.Maintainer m n runD (Env ps n) = runDef Syntax.Env ps n-runD (Arg s n) = runDef Syntax.Arg s n+runD (Arg k v n) = runDef2 Syntax.Arg k v n runD (Comment c n) = runDef Syntax.Comment c n runD (Healthcheck c n) = runDef Syntax.Healthcheck c n runD (OnBuildRaw i n) = runDef Syntax.OnBuild i n@@ -89,8 +90,8 @@ -- main :: IO () -- main = writeFile "something.dockerfile" $ toDockerfileStr $ do --     from (tagged "fpco/stack-build" "lts-6.9")---     add "." "/app/language-dockerfile"---     workdir "/app/language-dockerfile"+--     add ["."] "/app/language-docker"+--     workdir "/app/language-docker" --     run (words "stack build --test --only-dependencies") --     cmd (words "stack test") -- @@@ -98,20 +99,36 @@ toDockerfileStr = PrettyPrint.prettyPrint . toDockerfile  untagged :: String -> EBaseImage-untagged = EUntaggedImage+untagged = flip EUntaggedImage Nothing  tagged :: String -> String -> EBaseImage-tagged = ETaggedImage+tagged imageName tag = ETaggedImage imageName tag Nothing  digested :: String -> ByteString -> EBaseImage-digested = EDigestedImage+digested imageName hash = EDigestedImage imageName hash Nothing -ports :: [Integer] -> Syntax.Ports+aliased :: EBaseImage -> String -> EBaseImage+aliased image alias =+    case image of+        EUntaggedImage n _ -> EUntaggedImage n (Just $ Syntax.ImageAlias alias)+        ETaggedImage n t _ -> ETaggedImage n t (Just $ Syntax.ImageAlias alias)+        EDigestedImage n h _ -> EDigestedImage n h (Just $ Syntax.ImageAlias alias)++ports :: [Syntax.Port] -> Syntax.Ports ports = Syntax.Ports -port :: Integer -> Syntax.Ports-port = Syntax.Ports . (: [])+tcpPort :: Integer -> Syntax.Port+tcpPort = flip Syntax.Port Syntax.TCP +udpPort :: Integer -> Syntax.Port+udpPort = flip Syntax.Port Syntax.UDP++variablePort :: String -> Syntax.Port+variablePort varName = Syntax.PortStr ('$' : varName)++portRange :: Integer -> Integer -> Syntax.Port+portRange = Syntax.PortRange+ run :: MonadFree EInstruction m => String -> m () run = runArgs . words @@ -120,6 +137,50 @@  cmd :: MonadFree EInstruction m => String -> m () cmd = cmdArgs . words++copy :: MonadFree EInstruction m => NonEmpty Syntax.SourcePath -> Syntax.TargetPath -> m ()+copy sources dest = copyArgs sources dest Syntax.NoChown Syntax.NoSource++add :: MonadFree EInstruction m => NonEmpty Syntax.SourcePath -> Syntax.TargetPath -> m ()+add sources dest = addArgs sources dest Syntax.NoChown++check :: String -> Syntax.Check+check command =+    Syntax.Check+        Syntax.CheckArgs+        { Syntax.checkCommand = words command+        , Syntax.interval = Nothing+        , Syntax.timeout = Nothing+        , Syntax.startPeriod = Nothing+        , Syntax.retries = Nothing+        }++interval :: Syntax.Check -> Integer -> Syntax.Check+interval ch secs =+    case ch of+        Syntax.NoCheck -> Syntax.NoCheck+        Syntax.Check chArgs -> Syntax.Check chArgs {Syntax.interval = Just $ fromInteger secs}++timeout :: Syntax.Check -> Integer -> Syntax.Check+timeout ch secs =+    case ch of+        Syntax.NoCheck -> Syntax.NoCheck+        Syntax.Check chArgs -> Syntax.Check chArgs {Syntax.timeout = Just $ fromInteger secs}++startPeriod :: Syntax.Check -> Integer -> Syntax.Check+startPeriod ch secs =+    case ch of+        Syntax.NoCheck -> Syntax.NoCheck+        Syntax.Check chArgs -> Syntax.Check chArgs {Syntax.startPeriod = Just $ fromInteger secs}++retries :: Syntax.Check -> Integer -> Syntax.Check+retries ch tries =+    case ch of+        Syntax.NoCheck -> Syntax.NoCheck+        Syntax.Check chArgs -> Syntax.Check chArgs {Syntax.retries = Just $ fromInteger tries}++noCheck :: Syntax.Check+noCheck = Syntax.NoCheck  -- | ONBUILD Dockerfile instruction --
src/Language/Docker/EDSL/Quasi.hs view
@@ -9,7 +9,6 @@  import Language.Docker.EDSL import qualified Language.Docker.Parser as Parser-import Language.Docker.Syntax import Language.Docker.Syntax.Lift ()  -- | Quasiquoter for embedding dockerfiles on the EDSL@@ -30,9 +29,7 @@ edockerfileE e =     case Parser.parseString e of         Left err -> fail (show err)-        Right d ->-            let d' = filterEOL d-            in [|embed d'|]+        Right d -> [|embed d|]  dockerfile :: QuasiQuoter dockerfile =@@ -47,9 +44,4 @@ dockerfileE e =     case Parser.parseString e of         Left err -> fail (show err)-        Right d ->-            let d' = filterEOL d-            in lift d'--filterEOL :: [InstructionPos] -> [InstructionPos]-filterEOL = filter (\(InstructionPos i _ _) -> i /= EOL)+        Right d -> lift d
src/Language/Docker/EDSL/Types.hs view
@@ -3,41 +3,48 @@ module Language.Docker.EDSL.Types where  import Data.ByteString.Char8 (ByteString)+import Data.List.NonEmpty (NonEmpty) import Data.String import qualified Language.Docker.Syntax as Syntax  data EBaseImage     = EUntaggedImage String+                     (Maybe Syntax.ImageAlias)     | ETaggedImage String                    String+                   (Maybe Syntax.ImageAlias)     | EDigestedImage String                      ByteString+                     (Maybe Syntax.ImageAlias)     deriving (Show, Eq, Ord)  instance IsString EBaseImage where-    fromString = EUntaggedImage+    fromString = flip EUntaggedImage Nothing  data EInstruction next     = From EBaseImage            next-    | Add Syntax.Source-          Syntax.Destination-          next+    | AddArgs (NonEmpty Syntax.SourcePath)+              Syntax.TargetPath+              Syntax.Chown+              next     | User String            next     | Label Syntax.Pairs             next     | StopSignal String                  next-    | Copy Syntax.Source-           Syntax.Destination-           next+    | CopyArgs (NonEmpty Syntax.SourcePath)+               Syntax.TargetPath+               Syntax.Chown+               Syntax.CopySource+               next     | RunArgs Syntax.Arguments               next     | CmdArgs Syntax.Arguments               next     | Shell Syntax.Arguments-              next+            next     | Workdir Syntax.Directory               next     | Expose Syntax.Ports@@ -50,12 +57,12 @@                  next     | Env Syntax.Pairs           next-    | Arg String+    | Arg String (Maybe String)           next     | Comment String               next-    | Healthcheck String-              next+    | Healthcheck Syntax.Check+                  next     | OnBuildRaw Syntax.Instruction                  next     | Embed [Syntax.InstructionPos]
src/Language/Docker/Lexer.hs view
@@ -1,53 +1,70 @@ module Language.Docker.Lexer where -import Text.Parsec.Language (emptyDef)+import Control.Monad (void)+import Data.Char+import Text.Parsec hiding (spaces)+import Text.Parsec.Language (haskell) import Text.Parsec.String (Parser) import qualified Text.Parsec.Token as Token -lexer :: Token.TokenParser ()-lexer = Token.makeTokenParser style -- style-  where-    names =-        [ "FROM"-        , "ADD"-        , "RUN"-        , "WORKDIR"-        , "EXPOSE"-        , "VOLUME"-        , "ENTRYPOINT"-        , "MAINTAINER"-        , "ENV"-        , "LABEL"-        , "USER"-        , "SHELL"-        , "STOPSIGNAL"-        , "CMD"-        , "ONBUILD"-        , "ARG"-        , "HEALTHCHECK"-        ]-    style = emptyDef {Token.caseSensitive = False, Token.reservedNames = names}- reserved :: String -> Parser ()-reserved = Token.reserved lexer--reservedOp :: String -> Parser ()-reservedOp = Token.reservedOp lexer+reserved name =+    void $ do+        _ <- try (caseInsensitiveString name) <?> name+        spaces1 <?> "at least one space after '" ++ name ++ "' followed by its arguments"  natural :: Parser Integer-natural = Token.natural lexer+natural = zeroNumber <|> Token.decimal haskell <?> "positive number"+  where+    zeroNumber = char '0' >> return 0  commaSep :: Parser a -> Parser [a]-commaSep = Token.commaSep lexer+commaSep p = sepBy p (symbol ",")  stringLiteral :: Parser String-stringLiteral = Token.stringLiteral lexer+stringLiteral = Token.stringLiteral haskell  brackets :: Parser a -> Parser a-brackets = Token.brackets lexer+brackets = between (symbol "[") (symbol "]") -identifier :: Parser String-identifier = Token.identifier lexer+whiteSpace :: Parser ()+whiteSpace = void (char ' ' <|> char '\t') <?> "space" +space :: Parser ()+space = whiteSpace++spaces1 :: Parser ()+spaces1 = void (many1 whiteSpace <?> "at least one space")++spaces :: Parser ()+spaces = void (many whiteSpace <?> "spaces")++symbol :: String -> Parser String+symbol name = lexeme (string name)++caseInsensitiveChar :: Char -> Parser Char+caseInsensitiveChar c = char (toUpper c) <|> char (toLower c)++caseInsensitiveString :: String -> Parser String+caseInsensitiveString s = mapM caseInsensitiveChar s <?> "\"" ++ s ++ "\""++charsWithEscapedSpaces :: String -> Parser String+charsWithEscapedSpaces stopChars = do+    buf <- many1 $ noneOf ("\n\t\\ " ++ stopChars)+    try (jumpEscapeSequence buf) <|> try (backslashFollowedByChars buf) <|> return buf+  where+    backslashFollowedByChars buf = do+        backslashes <- many1 (char '\\')+        notFollowedBy (char ' ')+        rest <- charsWithEscapedSpaces stopChars+        return $ buf ++ backslashes ++ rest+    jumpEscapeSequence buf = do+        void $ string "\\ "+        rest <- charsWithEscapedSpaces stopChars+        return $ buf ++ ' ' : rest+ lexeme :: Parser a -> Parser a-lexeme = Token.lexeme lexer+lexeme p = do+    x <- p+    spaces+    return x
src/Language/Docker/Normalize.hs view
@@ -1,46 +1,85 @@-{-# LANGUAGE FlexibleContexts #-}- module Language.Docker.Normalize     ( normalizeEscapedLines     ) where -import Data.List (intercalate)-import Data.List.Split (splitOn)--escapePlaceHolder :: String-escapePlaceHolder = "\\\\"--escapeSeq :: String-escapeSeq = "\\\n"--replace :: Eq a => [a] -> [a] -> [a] -> [a]-replace old new = intercalate new . splitOn old+import Data.List (dropWhileEnd, mapAccumL)+import Data.Maybe (catMaybes) -count :: Eq a => [a] -> [a] -> Int-count s x = length (splitOn x s) - 1+data NormalizedLine+    = Continue+    | Joined !String+             !Int -trimLines :: String -> String-trimLines s = unlines $ map strip $ lines s+trimLines :: [String] -> [String]+trimLines = map strip   where     strip = lstrip . rstrip-    lstrip = dropWhile (`elem` (" \t" :: String))+    lstrip = dropWhile (`elem` " \t\r")     rstrip = reverse . lstrip . reverse -replaceEscapeSigns :: String -> String-replaceEscapeSigns = replace escapeSeq escapePlaceHolder--removeEscapePlaceholder :: String -> String-removeEscapePlaceholder = replace escapePlaceHolder " "--compensateLinebreaks :: String -> String-compensateLinebreaks s = concatMap compensate $ lines s+-- Finds all lines ending with \ and joins them with the next line using+-- a single space. If the next line is a comment, then the comment line is+-- deleted. It finally adds the same amount of new lines for each of the+-- lines it joined, in order to preserve the line count in the document.+normalize :: [String] -> [String]+normalize allLines =+    let (lastState, res) -- mapAccumL is the idea of a for loop with a variable holding+                         -- some state and another variable where we append the final result+                         -- of the looping operation. For each line in lines, apply the transform+                         -- function. This function always returns a new state, and another element+                         -- to append to the final result. The ending result of mapAccumL is the final+                         -- state variale and the resulting list of values. We initialize the loop with+                         -- the 'Continue' state, which means "no special action to do next"+         = mapAccumL transform Continue allLines+    in case lastState of+           Continue -- The last line of the document is a normal line, cleanup and return+            -> catMaybes res+           Joined l times -- The last line contains a \, so we need to add the buffered+                          -- line back to the result, pad with newlines and cleanup+            -> catMaybes res ++ [l ++ padNewlines times]   where-    compensate line = line ++ "\n" ++ genLinebreaks line-    genLinebreaks line = concat $ replicate (count line escapePlaceHolder) "\n"+    normalizeLast = dropWhileEnd (== '\\')+    -- | Checks the result of the previous operation in the loop (first argument)+    --+    -- If the previous result is a 'Joined' operation, then we merge the previous+    -- and the current line in a single line and return it.+    --+    -- If the current line ends with a \, then we produce a 'Joined' state as result+    -- of this looping operation.+    --+    -- If the previous 2 conditions are true at the same time, then we produce a new+    -- 'Joined' state holding the concatenation of the Joined buffer and the previous line+    -- and we return 'Nothing' as an indication that this line does not form part of the+    -- final result.+    transform :: NormalizedLine -> String -> (NormalizedLine, Maybe String)+    -- If we are buffering lines, and the next line is a comment,+    -- we simply ignore the comment and remember to add a newline+    transform (Joined prev times) ('#':_) = (Joined prev (times + 1), Nothing)+    -- We do the same if we are buffering lines and the next one is empty+    transform (Joined prev times) "" = (Joined prev (times + 1), Nothing)+    -- If we are buffering lines, then we check whether the current line end with \,+    -- if it does, then we merged it into the buffered state, otherwise we just yield+    -- the concatanation of the buffer and the current line as result, after padding with+    -- newlines+    transform (Joined prev times) l =+        if endsWithEscape l+            then (Joined (prev ++ ' ' : normalizeLast l) (times + 1), Nothing)+            else (Continue, Just (prev ++ ' ' : l ++ padNewlines times))+    -- When not buffering lines, then we just check if we need to start doing it by checking+    -- whether or not the current line ends with \. If it does not, then we just yield the+    -- current line as part of the result+    transform Continue l =+        if endsWithEscape l+            then (Joined (normalizeLast l) 1, Nothing)+            else (Continue, Just l)+    --+    endsWithEscape "" = False+    endsWithEscape s = last s == '\\'+    --+    padNewlines times = replicate times '\n'  -- | Remove new line escapes and join escaped lines together on one line --   to simplify parsing later on. Escapes are replaced with line breaks --   to not alter the line numbers. normalizeEscapedLines :: String -> String-normalizeEscapedLines s =-    removeEscapePlaceholder $ compensateLinebreaks $ replaceEscapeSigns $ trimLines s+normalizeEscapedLines = unlines . normalize . trimLines . lines
src/Language/Docker/Parser.hs view
@@ -1,43 +1,82 @@+{-# LANGUAGE RecordWildCards #-}+ module Language.Docker.Parser where  import Control.Monad (void) import Data.ByteString.Char8 (pack)-import Data.String-import Text.Parsec hiding (label)+import Data.List.NonEmpty (NonEmpty, fromList)+import Data.Maybe (listToMaybe)+import Data.Time.Clock (secondsToDiffTime)+import Text.Parsec hiding (label, space, spaces) import Text.Parsec.String (Parser)-import qualified Text.Parsec.Token as Token  import Language.Docker.Lexer import Language.Docker.Normalize import Language.Docker.Syntax +data CopyFlag+    = FlagChown Chown+    | FlagSource CopySource+    | FlagInvalid (String, String)++data CheckFlag+    = FlagInterval Duration+    | FlagTimeout Duration+    | FlagStartPeriod Duration+    | FlagRetries Retries+    | CFlagInvalid (String, String)+ comment :: Parser Instruction comment = do     void $ char '#'-    text <- untilEol+    text <- many (noneOf "\n")     return $ Comment text  taggedImage :: Parser BaseImage taggedImage = do-    name <- untilOccurrence ":\n"-    void $ oneOf ":"-    tag <- untilEol-    return $ TaggedImage name tag+    name <- many (noneOf "\t\n: ")+    void $ char ':'+    tag <- many1 (noneOf "\t\n: ")+    maybeAlias <- maybeImageAlias+    return $ TaggedImage name tag maybeAlias  digestedImage :: Parser BaseImage digestedImage = do-    name <- untilOccurrence "@\n"-    void $ oneOf "@"-    digest <- untilEol-    return $ DigestedImage name (pack digest)+    name <- many (noneOf "\t\n@ ")+    void $ char '@'+    digest <- many1 (noneOf "\t\n@ ")+    maybeAlias <- maybeImageAlias+    return $ DigestedImage name (pack digest) maybeAlias  untaggedImage :: Parser BaseImage untaggedImage = do-    name <- many (noneOf "\n")-    return $ UntaggedImage name+    name <- many (noneOf "\n\t:@ ")+    notInvalidTag name+    notInvalidDigest name+    maybeAlias <- maybeImageAlias+    return $ UntaggedImage name maybeAlias+  where+    notInvalidTag :: String -> Parser ()+    notInvalidTag name =+        try (notFollowedBy $ oneOf ":") <?> "no ':' or a valid image tag string (example: " +++        name ++ ":valid-tag)"+    notInvalidDigest :: String -> Parser ()+    notInvalidDigest name =+        try (notFollowedBy $ oneOf "@") <?> "no '@' or a valid digest hash (example: " +++        name ++ "@a3f42f2de)" +maybeImageAlias :: Parser (Maybe ImageAlias)+maybeImageAlias = Just <$> try (spaces >> imageAlias) <|> return Nothing++imageAlias :: Parser ImageAlias+imageAlias = do+    void $ caseInsensitiveString "AS"+    spaces1 <?> "a space followed by the image alias"+    alias <- untilOccurrence "\t\n "+    return $ ImageAlias alias+ baseImage :: Parser BaseImage-baseImage = try taggedImage <|> try digestedImage <|> try untaggedImage+baseImage = try digestedImage <|> try taggedImage <|> untaggedImage  from :: Parser Instruction from = do@@ -54,11 +93,68 @@ copy :: Parser Instruction copy = do     reserved "COPY"-    src <- many (noneOf " ")-    Token.whiteSpace lexer-    dst <- many (noneOf "\n")-    return $ Copy src dst+    flags <- copyFlag `sepEndBy` spaces1+    let chownFlags = [c | FlagChown c <- flags]+    let sourceFlags = [f | FlagSource f <- flags]+    let invalid = [i | FlagInvalid i <- flags]+    -- Let's do some validation on the flags+    case (invalid, chownFlags, sourceFlags) of+        ((k, v):_, _, _) -> unexpectedFlag k v+        (_, _:_:_, _) -> unexpected "duplicate flag: --chown"+        (_, _, _:_:_) -> unexpected "duplicate flag: --from"+        _ -> do+            let ch =+                    case chownFlags of+                        [] -> NoChown+                        c:_ -> c+            let fr =+                    case sourceFlags of+                        [] -> NoSource+                        f:_ -> f+            fileList "COPY" (\src dest -> Copy (CopyArgs src dest ch fr)) +copyFlag :: Parser CopyFlag+copyFlag =+    (FlagChown <$> try chown <?> "only one --chown") <|>+    (FlagSource <$> try copySource <?> "only one --from") <|>+    (FlagInvalid <$> try anyFlag <?> "no other flags")++chown :: Parser Chown+chown = do+    void $ string "--chown="+    ch <- many1 (noneOf "\t\n ")+    return $ Chown ch++copySource :: Parser CopySource+copySource = do+    void $ string "--from="+    src <- many1 (noneOf "\t\n ")+    return $ CopySource src++anyFlag :: Parser (String, String)+anyFlag = do+    void $ string "--"+    name <- many1 $ noneOf "\t\n= "+    void $ char '='+    val <- many $ noneOf "\t\n "+    return ("--" ++ name, val)++fileList :: String -> (NonEmpty SourcePath -> TargetPath -> Instruction) -> Parser Instruction+fileList name constr = do+    paths <-+        (try stringList <?> "an array of strings [\"src_file\", \"dest_file\"]") <|>+        (try spaceSeparated <?> "a space separated list of file paths")+    case paths of+        [_] -> unexpected $ "end of line. At least two arguments are required for " ++ name+        _ -> return $ constr (SourcePath <$> fromList (init paths)) (TargetPath $ last paths)+  where+    spaceSeparated = many (noneOf "\t\n ") `sepBy1` (try spaces1 <?> "at least another file path")+    stringList = brackets $ commaSep stringLiteral++unexpectedFlag :: String -> String -> Parser a+unexpectedFlag name "" = unexpected $ "flag " ++ name ++ " with no value"+unexpectedFlag name _ = unexpected $ "invalid flag " ++ name+ shell :: Parser Instruction shell = do     reserved "SHELL"@@ -68,41 +164,42 @@ stopsignal :: Parser Instruction stopsignal = do     reserved "STOPSIGNAL"-    args <- many (noneOf "\n")+    args <- many1 (noneOf "\n")     return $ Stopsignal args  -- We cannot use string literal because it swallows space -- and therefore have to implement quoted values by ourselves-quotedValue :: Parser String-quotedValue = do-    void $ char '"'-    literal <- untilOccurrence "\""-    void $ char '"'-    return literal+doubleQuotedValue :: Parser String+doubleQuotedValue = between (char '"') (char '"') (many $ noneOf "\n\"") -rawValue :: Parser String-rawValue = many1 (noneOf [' ', '=', '\n'])+singleQuotedValue :: Parser String+singleQuotedValue = between (void $ char '\'') (void $ char '\'') (many $ noneOf "\n'") -singleValue :: Parser String-singleValue = try quotedValue <|> try rawValue+unquotedString :: String -> Parser String+unquotedString stopChars = do+    str <- charsWithEscapedSpaces stopChars+    case str of+        '\'':_ -> unexpected $ errMsg "single" str+        '"':_ -> unexpected $ errMsg "double" str+        _ -> return str+  where+    errMsg t str = "end of " ++ t ++ " quoted string " ++ str ++ " (unmatched quote)" +singleValue :: String -> Parser String+singleValue stopChars =+    try doubleQuotedValue <|> -- Quotes or no quotes are fine+    try singleQuotedValue <|>+    (try (unquotedString stopChars) <?> "a string with no quotes")+ pair :: Parser (String, String) pair = do-    key <- rawValue-    void $ oneOf "= "-    spaces-    value <- singleValue+    key <- singleValue "="+    void $ char '='+    value <- singleValue ""     return (key, value) -pairs :: Parser Pairs-pairs = do-    _ <- many (char ' ')-    first <- pair-    next <- remainingPairs-    return (first : next)--remainingPairs :: Parser Pairs-remainingPairs = try (char ' ' >> pairs) <|> try (return [])+pairsList :: Parser Pairs+pairsList = pair `sepBy1` spaces1  label :: Parser Instruction label = do@@ -113,8 +210,13 @@ arg :: Parser Instruction arg = do     reserved "ARG"-    p <- untilEol-    return $ Arg p+    (try nameWithDefault <?> "the arg name") <|> Arg <$> untilEol <*> pure Nothing+  where+    nameWithDefault = do+        name <- many1 $ noneOf "\t\n= "+        void $ char '='+        def <- untilEol+        return $ Arg name (Just def)  env :: Parser Instruction env = do@@ -122,6 +224,16 @@     p <- pairs     return $ Env p +pairs :: Parser Pairs+pairs = try pairsList <|> try singlePair++singlePair :: Parser Pairs+singlePair = do+    key <- many (noneOf "\t\n= ")+    spaces1 <?> "a space followed by the value for the variable '" ++ key ++ "'"+    val <- untilEol+    return [(key, val)]+ user :: Parser Instruction user = do     reserved "USER"@@ -131,18 +243,57 @@ add :: Parser Instruction add = do     reserved "ADD"-    src <- untilOccurrence " "-    Token.whiteSpace lexer-    dst <- untilOccurrence "\n"-    return $ Add src dst+    flag <- lexeme copyFlag <|> return (FlagChown NoChown)+    notFollowedBy (string "--") <?> "only the --chown flag or the src and dest paths"+    case flag of+        FlagChown ch -> fileList "ADD" (\src dest -> Add (AddArgs src dest ch))+        FlagSource _ -> unexpected "flag --from"+        FlagInvalid (k, v) -> unexpectedFlag k v  expose :: Parser Instruction expose = do     reserved "EXPOSE"-    sports <- untilEol-    let port = fromString sports-    return $ Expose port+    ps <- ports+    return $ Expose ps +port :: Parser Port+port =+    (try portVariable <?> "a variable") <|> -- There a many valid representations of ports+    (try portRange <?> "a port range") <|>+    (try portWithProtocol <?> "a port with its protocol (udp/tcp)") <|>+    (try portInt <?> "a valid port number")++ports :: Parser Ports+ports = Ports <$> port `sepEndBy1` space++portRange :: Parser Port+portRange = do+    start <- natural+    void $ char '-'+    finish <- try natural+    return $ PortRange start finish++portInt :: Parser Port+portInt = do+    portNumber <- natural+    notFollowedBy (oneOf "/-")+    return $ Port portNumber TCP++portWithProtocol :: Parser Port+portWithProtocol = do+    portNumber <- natural+    void (char '/')+    proto <-+        (caseInsensitiveString "tcp" >> return TCP) <|> -- Either tcp or udp+        (caseInsensitiveString "udp" >> return UDP)+    return $ Port portNumber proto++portVariable :: Parser Port+portVariable = do+    void $ lookAhead (char '$')+    variable <- untilOccurrence "\t\n- "+    return $ PortStr variable+ run :: Parser Instruction run = do     reserved "RUN"@@ -151,7 +302,7 @@  -- Parse value until end of line is reached untilEol :: Parser String-untilEol = many (noneOf "\n")+untilEol = many1 (noneOf "\n")  untilOccurrence :: String -> Parser String untilOccurrence t = many $ noneOf t@@ -159,13 +310,13 @@ workdir :: Parser Instruction workdir = do     reserved "WORKDIR"-    directory <- many (noneOf "\n")+    directory <- untilEol     return $ Workdir directory  volume :: Parser Instruction volume = do     reserved "VOLUME"-    directory <- many (noneOf "\n")+    directory <- untilEol     return $ Volume directory  maintainer :: Parser Instruction@@ -199,17 +350,70 @@     i <- parseInstruction     return $ OnBuild i -eolInstruction :: Parser Instruction-eolInstruction = do-    eol-    return EOL- healthcheck :: Parser Instruction healthcheck = do     reserved "HEALTHCHECK"-    args <- untilEol-    return $ Healthcheck args+    Healthcheck <$> (fullCheck <|> noCheck)+  where+    noCheck = string "NONE" >> return NoCheck+    allFlags = do+        flags <- someFlags+        spaces1 <?> "another flag"+        return flags+    someFlags = do+        x <- checkFlag+        cont <- try (spaces1 >> lookAhead (string "--") >> return True) <|> return False+        if cont+            then do+                xs <- someFlags+                return (x : xs)+            else return [x]+    fullCheck = do+        flags <- allFlags <|> return []+        let intervals = [x | FlagInterval x <- flags]+        let timeouts = [x | FlagTimeout x <- flags]+        let startPeriods = [x | FlagStartPeriod x <- flags]+        let retriesD = [x | FlagRetries x <- flags]+        let invalid = [x | CFlagInvalid x <- flags]+      -- Let's do some validation on the flags+        case (invalid, intervals, timeouts, startPeriods, retriesD) of+            ((k, v):_, _, _, _, _) -> unexpectedFlag k v+            (_, _:_:_, _, _, _) -> unexpected "duplicate flag: --interval"+            (_, _, _:_:_, _, _) -> unexpected "duplicate flag: --timeout"+            (_, _, _, _:_:_, _) -> unexpected "duplicate flag: --start-period"+            (_, _, _, _, _:_:_) -> unexpected "duplicate flag: --retries"+            _ -> do+                Cmd checkCommand <- cmd+                let interval = listToMaybe intervals+                let timeout = listToMaybe timeouts+                let startPeriod = listToMaybe startPeriods+                let retries = listToMaybe retriesD+                return $ Check CheckArgs {..} +checkFlag :: Parser CheckFlag+checkFlag =+    (FlagInterval <$> durationFlag "--interval=" <?> "--interval") <|>+    (FlagTimeout <$> durationFlag "--timeout=" <?> "--timeout") <|>+    (FlagStartPeriod <$> durationFlag "--start-period=" <?> "--start-period") <|>+    (FlagRetries <$> retriesFlag <?> "--retries") <|>+    (CFlagInvalid <$> anyFlag <?> "no flags")++durationFlag :: String -> Parser Duration+durationFlag flagName = do+    void $ try (string flagName)+    scale <- natural+    unit <- char 's' <|> char 'm' <|> char 'h' <?> "either 's', 'm' or 'h' as the unit"+    case unit of+        's' -> return $ Duration (secondsToDiffTime scale)+        'm' -> return $ Duration (secondsToDiffTime (scale * 60))+        _ -> return $ Duration (secondsToDiffTime (scale * 60 * 60))++retriesFlag :: Parser Retries+retriesFlag = do+    void $ try (string "--retries=")+    n <- try natural <?> "the number of retries"+    return $ Retries (fromIntegral n)+ parseInstruction :: Parser Instruction parseInstruction =     try onbuild <|> -- parse all main instructions@@ -230,12 +434,11 @@     try maintainer <|>     try add <|>     try comment <|>-    try healthcheck <|>-    try eolInstruction+    try healthcheck  contents :: Parser a -> Parser a contents p = do-    Token.whiteSpace lexer+    void $ many (space <|> void (char '\n'))     r <- p     eof     return r@@ -245,15 +448,10 @@  dockerfile :: Parser Dockerfile dockerfile =-    many $-    -- deal with empty lines that only contain spaces or tabs-    -- skipMany space-    -- skipMany $ char '\t'-     do+    many $ do         pos <- getPosition         i <- parseInstruction-        optional eol-        -- skipMany eol+        void (many1 eol) <|> eof <?> "a new line followed by the next instruction"         return $ InstructionPos i (sourceName pos) (sourceLine pos)  parseString :: String -> Either ParseError Dockerfile
src/Language/Docker/Predef.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}  module Language.Docker.Predef where @@ -9,6 +10,7 @@ import Control.Monad.IO.Class import Data.Aeson (Value(..)) import qualified Data.HashMap.Strict as HashMap+import Data.List.NonEmpty (fromList) import Data.Maybe import Data.Monoid import Data.Text (Text)@@ -32,7 +34,7 @@ dockerIgnore :: Text -> IO () dockerIgnore = appendLnIfMissing ".dockerignore" -addGlob :: (MonadIO m, MonadFree EInstruction m) => String -> Destination -> m ()+addGlob :: (MonadIO m, MonadFree EInstruction m) => String -> TargetPath -> m () addGlob pattern dest = do     fs <-         liftIO $ do@@ -43,11 +45,13 @@                 isdir <- doesDirectoryExist f                 return $                     if isdir-                        then (f <> "/", dest <> takeBaseName f)-                        else (f, dest <> takeBaseName f)-    forM_ fs (uncurry add)+                        then (SourcePath $ f <> "/")+                        else (SourcePath f)+    case fs of+      [] -> return ()+      _ -> add (fromList fs) dest -copyGlob :: (MonadIO m, MonadFree EInstruction m) => String -> Destination -> m ()+copyGlob :: (MonadIO m, MonadFree EInstruction m) => String -> TargetPath -> m () copyGlob = addGlob  stackBuild :: (Monad m, MonadIO m, MonadFree EInstruction m) => m ()@@ -76,12 +80,12 @@     liftIO $ dockerIgnore ".cabal-sandbox"     from ("fpco" `tagged` tag)     extra-    add "./package.yaml" "/app/package.yaml"+    add ["./package.yaml"] "/app/package.yaml"     addGlob "./*.cabal" "/app/"-    add "./stack.yaml" "/app/stack.yaml"+    add ["./stack.yaml"] "/app/stack.yaml"     workdir "/app/"     run "stack build --only-dependencies"-    add "." "/app/stack.yaml"+    add ["."] "/app/stack.yaml"     run "stack build"  nodejs :: (Monad m, MonadIO m, MonadFree EInstruction m) => m ()@@ -93,8 +97,8 @@     liftIO $ dockerIgnore "bower_components"     from ("node" `tagged` tag)     extra-    add "./package.json" "/app/package.json"+    add ["./package.json"] "/app/package.json"     workdir "/app/"     run "npm install"-    add "." "/app/"+    add ["."] "/app/"     cmd "npm start"
src/Language/Docker/PrettyPrint.hs view
@@ -1,10 +1,14 @@ {-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE RecordWildCards #-}  module Language.Docker.PrettyPrint where  import qualified Data.ByteString.Char8 as ByteString (unpack)-import Data.List (intersperse)+import Data.List (foldl', intersperse)+import Data.List.NonEmpty (NonEmpty, toList) import Data.String import Language.Docker.Syntax import Prelude hiding ((>>), (>>=), return)@@ -15,7 +19,7 @@ prettyPrint =     unlines .     reverse .-    snd . foldl removeDoubleBlank (False, []) . lines . unlines . map prettyPrintInstructionPos+    snd . foldl' removeDoubleBlank (False, []) . lines . unlines . map prettyPrintInstructionPos   where     removeDoubleBlank (True, m) "" = (True, m)     removeDoubleBlank (False, m) "" = (True, "" : m)@@ -28,18 +32,26 @@ prettyPrintBaseImage :: BaseImage -> Doc prettyPrintBaseImage b =     case b of-        DigestedImage name digest -> do+        DigestedImage name digest alias -> do             text name             char '@'             text (ByteString.unpack digest)-        UntaggedImage name -> text name-        TaggedImage name tag -> do+            prettyAlias alias+        UntaggedImage name alias -> do             text name+            prettyAlias alias+        TaggedImage name tag alias -> do+            text name             char ':'             text tag+            prettyAlias alias   where     (>>) = (<>)     return = (mempty <>)+    prettyAlias maybeAlias =+        case maybeAlias of+            Nothing -> mempty+            Just (ImageAlias alias) -> text " AS " <> text alias  prettyPrintPairs :: Pairs -> Doc prettyPrintPairs ps = hsep $ map prettyPrintPair ps@@ -56,15 +68,50 @@ prettyPrintJSON :: Arguments -> Doc prettyPrintJSON as = brackets $ hsep $ intersperse comma $ map (doubleQuotes . text) as +prettyPrintPort :: Port -> Doc+prettyPrintPort (PortStr str) = text str+prettyPrintPort (PortRange start stop) = integer start <> text "-" <> integer stop+prettyPrintPort (Port num TCP) = integer num <> char '/' <> text "tcp"+prettyPrintPort (Port num UDP) = integer num <> char '/' <> text "udp"++prettyPrintFileList :: NonEmpty SourcePath -> TargetPath -> Doc+prettyPrintFileList sources (TargetPath dest) =+    hsep $ [text s | SourcePath s <- toList sources] ++ [text dest]++prettyPrintChown :: Chown -> Doc+prettyPrintChown chown =+    case chown of+        Chown c -> text "--chown=" <> text c+        NoChown -> mempty++prettyPrintCopySource :: CopySource -> Doc+prettyPrintCopySource source =+    case source of+        CopySource c -> text "--from=" <> text c+        NoSource -> mempty++prettyPrintDuration :: String -> Maybe Duration -> Doc+prettyPrintDuration flagName = maybe mempty pp+  where+    pp (Duration d) = text flagName <> text (show d)++prettyPrintRetries :: Maybe Retries -> Doc+prettyPrintRetries = maybe mempty pp+  where+    pp (Retries r) = text "--retries=" <> int r+ prettyPrintInstruction :: Instruction -> Doc prettyPrintInstruction i =     case i of         Maintainer m -> do             text "MAINTAINER"             text m-        Arg a -> do+        Arg a Nothing -> do             text "ARG"             text a+        Arg k (Just v) -> do+            text "ARG"+            text k <> text "=" <> text v         Entrypoint e -> do             text "ENTRYPOINT"             prettyPrintArguments e@@ -76,17 +123,18 @@             text w         Expose (Ports ps) -> do             text "EXPOSE"-            hsep (map (text . show) ps)-        Expose (PortStr p) -> do-            text "EXPOSE"-            text p+            hsep (map prettyPrintPort ps)         Volume dir -> do             text "VOLUME"             text dir         Run c -> do             text "RUN"             prettyPrintArguments c-        Copy s d -> hsep [text "COPY", text s, text d]+        Copy CopyArgs {sourcePaths, targetPath, chownFlag, sourceFlag} -> do+            text "COPY"+            prettyPrintChown chownFlag+            prettyPrintCopySource sourceFlag+            prettyPrintFileList sourcePaths targetPath         Cmd c -> do             text "CMD"             prettyPrintArguments c@@ -108,17 +156,22 @@         From b -> do             text "FROM"             prettyPrintBaseImage b-        Add s d -> do+        Add AddArgs {sourcePaths, targetPath, chownFlag} -> do             text "ADD"-            text s-            text d+            prettyPrintChown chownFlag+            prettyPrintFileList sourcePaths targetPath         Shell args -> do             text "SHELL"             prettyPrintJSON args-        Healthcheck c -> do+        Healthcheck NoCheck -> text "HEALTHCHECK NONE"+        Healthcheck (Check CheckArgs {..}) -> do             text "HEALTHCHECK"-            text c-        EOL -> mempty+            prettyPrintDuration "--interval=" interval+            prettyPrintDuration "--timeout=" timeout+            prettyPrintDuration "--start-period=" startPeriod+            prettyPrintRetries retries+            text "CMD"+            prettyPrintArguments checkCommand   where     (>>) = (<+>)     return = (mempty <>)
src/Language/Docker/Syntax.hs view
@@ -1,46 +1,112 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, TypeFamilies,+  DuplicateRecordFields #-}+ module Language.Docker.Syntax where  import Data.ByteString.Char8 (ByteString)-import Data.Maybe-import Data.String-import Text.Read+import Data.List.NonEmpty (NonEmpty)+import Data.String (IsString)+import Data.Time.Clock (DiffTime)+import GHC.Exts (IsList(..))  type Image = String  type Tag = String -data Ports-    = Ports [Integer]+data Protocol+    = TCP+    | UDP+    deriving (Show, Eq, Ord)++data Port+    = Port Integer+           Protocol     | PortStr String+    | PortRange Integer+                Integer     deriving (Show, Eq, Ord) -instance IsString Ports where-    fromString p =-        case readMaybe p of-            Just i -> Ports [i]-            Nothing ->-                let rs = map readMaybe (words p)-                in if all isJust rs-                       then Ports (catMaybes rs)-                       else PortStr p+newtype Ports = Ports+    { unPorts :: [Port]+    } deriving (Show, Eq, Ord) +instance IsList Ports where+    type Item Ports = Port+    fromList = Ports+    toList (Ports ps) = ps+ type Directory = String +newtype ImageAlias = ImageAlias+    { unImageAlias :: String+    } deriving (Show, Eq, Ord, IsString)+ data BaseImage     = UntaggedImage Image+                    (Maybe ImageAlias)     | TaggedImage Image                   Tag+                  (Maybe ImageAlias)     | DigestedImage Image                     ByteString+                    (Maybe ImageAlias)     deriving (Eq, Ord, Show)  -- | Type of the Dockerfile AST type Dockerfile = [InstructionPos] -type Source = String+newtype SourcePath = SourcePath+    { unSourcePath :: String+    } deriving (Show, Eq, Ord, IsString) -type Destination = String+newtype TargetPath = TargetPath+    { unTargetPath :: String+    } deriving (Show, Eq, Ord, IsString) +data Chown+    = Chown String+    | NoChown+    deriving (Show, Eq, Ord)++data CopySource+    = CopySource String+    | NoSource+    deriving (Show, Eq, Ord)++newtype Duration = Duration+    { durationTime :: DiffTime+    } deriving (Show, Eq, Ord, Num)++newtype Retries = Retries+    { times :: Int+    } deriving (Show, Eq, Ord, Num)++data CopyArgs = CopyArgs+    { sourcePaths :: NonEmpty SourcePath+    , targetPath :: TargetPath+    , chownFlag :: Chown+    , sourceFlag :: CopySource+    } deriving (Show, Eq, Ord)++data AddArgs = AddArgs+    { sourcePaths :: NonEmpty SourcePath+    , targetPath :: TargetPath+    , chownFlag :: Chown+    } deriving (Show, Eq, Ord)++data Check+    = Check CheckArgs+    | NoCheck+    deriving (Show, Eq, Ord)++data CheckArgs = CheckArgs+    { checkCommand :: Arguments+    , interval :: Maybe Duration+    , timeout :: Maybe Duration+    , startPeriod :: Maybe Duration+    , retries :: Maybe Retries+    } deriving (Show, Eq, Ord)+ type Arguments = [String]  type Pairs = [(String, String)]@@ -48,13 +114,11 @@ -- | All commands available in Dockerfiles data Instruction     = From BaseImage-    | Add Source-          Destination+    | Add AddArgs     | User String     | Label Pairs     | Stopsignal String-    | Copy Source-           Destination+    | Copy CopyArgs     | Run Arguments     | Cmd Arguments     | Shell Arguments@@ -65,10 +129,10 @@     | Maintainer String     | Env Pairs     | Arg String-    | Healthcheck String+          (Maybe String)+    | Healthcheck Check     | Comment String     | OnBuild Instruction-    | EOL     deriving (Eq, Ord, Show)  type Filename = String@@ -77,14 +141,8 @@  -- | 'Instruction' with additional location information required for creating -- good check messages-data InstructionPos =-    InstructionPos Instruction-                   Filename-                   Linenumber-    deriving (Eq, Ord, Show)--instruction :: InstructionPos -> Instruction-instruction (InstructionPos i _ _) = i--sourcename :: InstructionPos -> Filename-sourcename (InstructionPos _ fn _) = fn+data InstructionPos = InstructionPos+    { instruction :: Instruction+    , sourcename :: Filename+    , lineNumber :: Linenumber+    } deriving (Eq, Ord, Show)
src/Language/Docker/Syntax/Lift.hs view
@@ -2,16 +2,51 @@  module Language.Docker.Syntax.Lift where +import Data.Fixed (Fixed)+import Data.List.NonEmpty (NonEmpty)+import Data.Time.Clock (DiffTime) import Instances.TH.Lift () import Language.Haskell.TH.Lift import Language.Haskell.TH.Syntax ()  import Language.Docker.Syntax +deriveLift ''NonEmpty++deriveLift ''Fixed++deriveLift ''DiffTime++deriveLift ''Protocol++deriveLift ''Port+ deriveLift ''Ports +deriveLift ''ImageAlias+ deriveLift ''BaseImage  deriveLift ''Instruction  deriveLift ''InstructionPos++deriveLift ''SourcePath++deriveLift ''TargetPath++deriveLift ''Chown++deriveLift ''CopySource++deriveLift ''CopyArgs++deriveLift ''AddArgs++deriveLift ''Duration++deriveLift ''Retries++deriveLift ''CheckArgs++deriveLift ''Check
test/Language/Docker/EDSL/PluginsSpec.hs view
@@ -7,7 +7,7 @@  spec =     describe "listPlugins" $-        it "lists docker images matching language-dockerfile-*" pending+        it "lists docker images matching language-docker-*" pending             -- str <- toDockerFileStrIO $ do             --     ds <- liftIO (glob "./test/*.hs")             --     from "ubuntu"
test/Language/Docker/EDSL/QuasiSpec.hs view
@@ -17,7 +17,7 @@                                                 RUN apt-get update                                                 CMD node something.js                                                 |]-            df `shouldBe` [ From (UntaggedImage "node")+            df `shouldBe` [ From (UntaggedImage "node" Nothing)                           , Run ["apt-get", "update"]                           , Cmd ["node", "something.js"]                           ]@@ -25,15 +25,15 @@     describe "edockerfile" $         it "lets us use parsed dockerfiles seamlessly in our DSL" $ do             let d = do-                    from "node"-                    expose "8080"+                    from ("node" `aliased` "node-build")+                    expose (ports [tcpPort 8080, variablePort "PORT"])                     [edockerfile|                                 RUN apt-get update                                 CMD node something.js                                 |]                 df = map instruction (toDockerfile d)-            df `shouldBe` [ From (UntaggedImage "node")-                          , Expose (Ports [8080])+            df `shouldBe` [ From (UntaggedImage "node" (Just $ ImageAlias "node-build"))+                          , Expose (Ports [Port 8080 TCP, PortStr "$PORT"])                           , Run ["apt-get", "update"]                           , Cmd ["node", "something.js"]                           ]
test/Language/Docker/EDSLSpec.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-} module Language.Docker.EDSLSpec where  import           Control.Monad.IO.Class@@ -18,7 +19,7 @@             let r = map Syntax.instruction $ toDockerfile (do                         from "node"                         cmdArgs ["node", "-e", "'console.log(\'hey\')'"])-            r `shouldBe` [ Syntax.From (Syntax.UntaggedImage "node")+            r `shouldBe` [ Syntax.From $ (Syntax.UntaggedImage "node") Nothing                          , Syntax.Cmd ["node", "-e", "'console.log(\'hey\')'"]                          ] @@ -28,12 +29,21 @@                         from "node"                         shell ["cmd", "/S"]                         cmdArgs ["node", "-e", "'console.log(\'hey\')'"]-                        healthcheck "--interval=5m CMD curl -f http://localhost/ || exit 1")+                        healthcheck $ check "curl -f http://localhost/ || exit 1" `interval` 300)             r `shouldBe` unlines [ "FROM node"                                  , "SHELL [\"cmd\" , \"/S\"]"                                  , "CMD node -e 'console.log(\'hey\')'"-                                 , "HEALTHCHECK --interval=5m CMD curl -f http://localhost/ || exit 1"+                                 , "HEALTHCHECK --interval=300s CMD curl -f http://localhost/ || exit 1"                                  ]+        it "print expose instructions correctly" $ do+            let r = prettyPrint $ toDockerfile (do+                        from "scratch"+                        expose $ ports [variablePort "PORT", tcpPort 80, udpPort 51]+                        expose $ ports [portRange 90 100])+            r `shouldBe` unlines [ "FROM scratch"+                                 , "EXPOSE $PORT 80/tcp 51/udp"+                                 , "EXPOSE 90-100"+                                 ]          it "onBuild let's us nest statements" $ do             let r = prettyPrint $ toDockerfile $ do@@ -48,7 +58,13 @@                                  , "ONBUILD RUN echo \"hello world2\""                                  ] -        it "onBuild disallows unallowed instructions" pending+        it "parses and prints from aliases correctly" $ do+            let r = prettyPrint $ toDockerfile $ do+                        from ("node" `tagged` "10.1" `aliased` "node-build")+                        run "echo foo"+            r `shouldBe` unlines [ "FROM node:10.1 AS node-build"+                                 , "RUN echo foo"+                                 ]      describe "toDockerfileStrIO" $         it "let's us run in the IO monad" $ do@@ -59,7 +75,7 @@                     fs <- glob "./test/Language/Docker/*.hs"                     return (map (makeRelative cwd) (sort fs))                 from "ubuntu"-                mapM_ (\f -> add f ("/app/" ++ takeFileName f)) fs+                mapM_ (\f -> add [Syntax.SourcePath f] (Syntax.TargetPath $ "/app/" ++ takeFileName f)) fs             str `shouldBe` unlines [ "FROM ubuntu"                                    , "ADD ./test/Language/Docker/EDSLSpec.hs /app/EDSLSpec.hs"                                    , "ADD ./test/Language/Docker/ExamplesSpec.hs /app/ExamplesSpec.hs"
test/Language/Docker/ExamplesSpec.hs view
@@ -8,7 +8,7 @@ import           System.Process import           Test.Hspec -stackRunGhc e = callProcess "stack" ["runghc", "--package", "language-dockerfile", e]+stackRunGhc e = callProcess "stack" ["runghc", "--package", "language-docker", e]  spec :: Spec spec = do
test/Language/Docker/ParserSpec.hs view
@@ -1,26 +1,51 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-} module Language.Docker.ParserSpec where -import Data.List (find)-import Data.Maybe (fromMaybe, isJust)- import Language.Docker.Normalize import Language.Docker.Parser import Language.Docker.Syntax + import Test.HUnit hiding (Label) import Test.Hspec import Text.Parsec  spec :: Spec spec = do-        describe "parse FROM" $-            it "parse untagged image" $ assertAst "FROM busybox" [From (UntaggedImage "busybox")]+        describe "parse ARG" $ do+            it "no default" $+                assertAst "ARG FOO" [Arg "FOO" Nothing]+            it "with default" $+                assertAst "ARG FOO=bar" [Arg "FOO" (Just "bar")] +        describe "parse FROM" $ do+            it "parse untagged image" $+                assertAst "FROM busybox" [From (UntaggedImage "busybox" Nothing)]+            it "parse tagged image" $+                assertAst+                    "FROM busybox:5.12-dev"+                    [From (TaggedImage "busybox" "5.12-dev" Nothing)]+            it "parse digested image" $+                assertAst+                    "FROM ubuntu@sha256:0ef2e08ed3fab"+                    [From (DigestedImage "ubuntu" "sha256:0ef2e08ed3fab" Nothing)]++        describe "parse aliased FROM" $ do+            it "parse untagged image" $+                assertAst "FROM busybox as foo" [From (UntaggedImage "busybox" (Just $ ImageAlias "foo"))]+            it "parse tagged image" $+                assertAst "FROM busybox:5.12-dev AS foo-bar" [From (TaggedImage "busybox" "5.12-dev" (Just $ ImageAlias "foo-bar"))]+            it "parse diggested image" $+                assertAst "FROM ubuntu@sha256:0ef2e08ed3fab AS foo" [From (DigestedImage "ubuntu" "sha256:0ef2e08ed3fab" (Just $ ImageAlias "foo"))]+         describe "parse LABEL" $ do             it "parse label" $ assertAst "LABEL foo=bar" [Label[("foo", "bar")]]+            it "parse space separated label" $ assertAst "LABEL foo bar baz" [Label[("foo", "bar baz")]]+            it "parse quoted labels" $ assertAst "LABEL \"foo bar\"=baz" [Label[("foo bar", "baz")]]             it "parses multiline labels" $                 let dockerfile = unlines [ "LABEL foo=bar \\", "hobo=mobo"]-                    ast = [ Label [("foo", "bar"), ("hobo", "mobo")], EOL ]+                    ast = [ Label [("foo", "bar"), ("hobo", "mobo")] ]                 in assertAst dockerfile ast          describe "parse ENV" $ do@@ -47,9 +72,8 @@                                  , "ENV NODE_VERSION=v5.7.1 \\"                                  , "DEBIAN_FRONTEND=noninteractive"                                  ]-                    ast = [ From (UntaggedImage "busybox")+                    ast = [ From (UntaggedImage "busybox" Nothing)                           , Env[("NODE_VERSION", "v5.7.1"), ("DEBIAN_FRONTEND", "noninteractive")]-                          , EOL                           ]                 in assertAst dockerfile ast             it "parses long env over multiple lines" $@@ -59,50 +83,132 @@                                ,("APACHE_RUN_USER", "www-data")                                ,("APACHE_RUN_GROUP", "www-data")                                ]-                          , EOL                           ]                 in assertAst dockerfile ast-+            it "parse single var list" $+                assertAst "ENV foo val1 val2 val3 val4" [Env [("foo", "val1 val2 val3 val4")]]+            it "parses many env lines with an equal sign in the value" $+                let dockerfile = unlines [ "ENV TOMCAT_VERSION 9.0.2"+                                         , "ENV TOMCAT_URL foo.com?q=1"+                                         ]+                    ast = [ Env [("TOMCAT_VERSION", "9.0.2")]+                          , Env [("TOMCAT_URL", "foo.com?q=1")]+                          ]+                in assertAst dockerfile ast+            it "parses many env lines in mixed style" $+                let dockerfile = unlines [ "ENV myName=\"John Doe\" myDog=Rex\\ The\\ Dog \\"+                                         , "    myCat=fluffy"+                                         ]+                    ast = [ Env [("myName", "John Doe")+                                ,("myDog", "Rex The Dog")+                                ,("myCat", "fluffy")+                                ]+                          ]+                in assertAst dockerfile ast+            it "parses many env with backslashes" $+                let dockerfile = unlines [ "ENV JAVA_HOME=C:\\\\jdk1.8.0_112"+                                         ]+                    ast = [ Env [("JAVA_HOME", "C:\\\\jdk1.8.0_112")]+                          ]+                in assertAst dockerfile ast -        describe "parse RUN" $+        describe "parse RUN" $ do             it "escaped with space before" $-            let dockerfile = unlines ["RUN yum install -y \\ ", "imagemagick \\ ", "mysql"]-            in assertAst dockerfile [Run ["yum", "install", "-y", "imagemagick", "mysql"], EOL, EOL]+                let dockerfile = unlines ["RUN yum install -y \\ ", "imagemagick \\ ", "mysql"]+                in assertAst dockerfile [Run ["yum", "install", "-y", "imagemagick", "mysql"]] +            it "does not choke on unmatched brackets" $+                let dockerfile = unlines ["RUN [foo"]+                in assertAst dockerfile [Run ["[foo"]]+         describe "parse CMD" $ do             it "one line cmd" $ assertAst "CMD true" [Cmd ["true"]]             it "cmd over several lines" $-                assertAst "CMD true \\\n && true" [Cmd ["true", "&&", "true"], EOL]+                assertAst "CMD true \\\n && true" [Cmd ["true", "&&", "true"]]             it "quoted command params" $ assertAst "CMD [\"echo\",  \"1\"]" [Cmd ["echo", "1"]]          describe "parse SHELL" $ do             it "quoted shell params" $                 assertAst "SHELL [\"/bin/bash\",  \"-c\"]" [Shell ["/bin/bash", "-c"]] -        describe "parse HEALTHCHECK" $-            it "parse healthcheck without args" $-              assertAst "HEALTHCHECK --interval=5m \\nCMD curl -f http://localhost/" [Healthcheck "--interval=5m \\nCMD curl -f http://localhost/"]+        describe "parse HEALTHCHECK" $ do+            it "parse healthcheck with interval" $+              assertAst+                "HEALTHCHECK --interval=5m \\\nCMD curl -f http://localhost/"+                [Healthcheck $+                    Check $+                      CheckArgs (words "curl -f http://localhost/") (Just $ fromInteger 300) Nothing Nothing Nothing+                ] +            it "parse healthcheck with retries" $+              assertAst+                "HEALTHCHECK --retries=10 CMD curl -f http://localhost/"+                [Healthcheck $+                    Check $+                      CheckArgs (words "curl -f http://localhost/") Nothing Nothing Nothing (Just $ Retries 10)+                ]++            it "parse healthcheck with timeout" $+              assertAst+                "HEALTHCHECK --timeout=10s CMD curl -f http://localhost/"+                [Healthcheck $+                    Check $+                      CheckArgs (words "curl -f http://localhost/") Nothing (Just $ fromInteger 10) Nothing Nothing+                ]++            it "parse healthcheck with start-period" $+              assertAst+                "HEALTHCHECK --start-period=2m CMD curl -f http://localhost/"+                [Healthcheck $+                    Check $+                      CheckArgs (words "curl -f http://localhost/") Nothing Nothing (Just $ fromInteger 120) Nothing+                ]++            it "parse healthcheck with all flags" $+              assertAst+                "HEALTHCHECK --start-period=2s --timeout=1m --retries=3 --interval=5s    CMD curl -f http://localhost/"+                [Healthcheck $+                    Check $+                      CheckArgs+                        (words "curl -f http://localhost/")+                        (Just $ fromInteger 5)+                        (Just $ fromInteger 60)+                        (Just $ fromInteger 2)+                        (Just $ Retries 3)+                ]++            it "parse healthcheck with no flags" $+              assertAst+                "HEALTHCHECK CMD curl -f http://localhost/"+                [Healthcheck $+                    Check $+                      CheckArgs (words "curl -f http://localhost/") Nothing Nothing Nothing Nothing+                ]+         describe "parse MAINTAINER" $ do             it "maintainer of untagged scratch image" $                 assertAst                     "FROM scratch\nMAINTAINER hudu@mail.com"-                    [From (UntaggedImage "scratch"), Maintainer "hudu@mail.com"]+                    [From (UntaggedImage "scratch" Nothing), Maintainer "hudu@mail.com"]             it "maintainer with mail" $                 assertAst "MAINTAINER hudu@mail.com" [Maintainer "hudu@mail.com"]             it "maintainer only mail after from" $                 let maintainerFromProg = "FROM busybox\nMAINTAINER hudu@mail.com"-                    maintainerFromAst = [From (UntaggedImage "busybox"), Maintainer "hudu@mail.com"]+                    maintainerFromAst = [From (UntaggedImage "busybox" Nothing), Maintainer "hudu@mail.com"]                 in assertAst maintainerFromProg maintainerFromAst         describe "parse # comment " $ do             it "multiple comments before run" $                 let dockerfile = unlines ["# line 1", "# line 2", "RUN apt-get update"]-                in assertAst dockerfile [Comment " line 1", Comment " line 2", Run ["apt-get", "update"], EOL]+                in assertAst dockerfile [Comment " line 1", Comment " line 2", Run ["apt-get", "update"]]             it "multiple comments after run" $                 let dockerfile = unlines ["RUN apt-get update", "# line 1", "# line 2"]                 in assertAst                        dockerfile-                       [Run ["apt-get", "update"], Comment " line 1", Comment " line 2", EOL]+                       [Run ["apt-get", "update"], Comment " line 1", Comment " line 2"]++            it "empty comment" $+                let dockerfile = unlines ["#", "# Hello"]+                in assertAst dockerfile [Comment "", Comment " Hello"]         describe "normalize lines" $ do             it "join multiple ENV" $                 let dockerfile = unlines [ "FROM busybox"@@ -128,7 +234,7 @@                             ]                     longEscapedCmdExpected =                         concat-                            [ "RUN wget https://download.com/${version}.tar.gz -O /tmp/logstash.tar.gz &&  "+                            ([ "RUN wget https://download.com/${version}.tar.gz -O /tmp/logstash.tar.gz &&  "                             , "(cd /tmp && tar zxf logstash.tar.gz && mv logstash-${version} /opt/logstash &&  "                             , "rm logstash.tar.gz) &&  "                             , "(cd /opt/logstash &&  "@@ -137,17 +243,88 @@                             , "\n"                             , "\n"                             , "\n"-                            ]+                            ] :: [String])                 in normalizeEscapedLines longEscapedCmd `shouldBe` longEscapedCmdExpected         describe "expose" $ do             it "should handle number ports" $ do                 let content = "EXPOSE 8080"-                parse expose "" content `shouldBe` Right (Expose (Ports [8080]))+                parse expose "" content `shouldBe` Right (Expose (Ports [Port 8080 TCP]))+            it "should handle many number ports" $ do+                let content = "EXPOSE 8080 8081"+                parse expose "" content `shouldBe` Right (Expose (Ports [Port 8080 TCP, Port 8081 TCP]))+            it "should handle ports with protocol" $ do+                let content = "EXPOSE 8080/TCP 8081/UDP"+                parse expose "" content `shouldBe` Right (Expose (Ports [Port 8080 TCP, Port 8081 UDP]))+            it "should handle ports with protocol and variables" $ do+                let content = "EXPOSE $PORT 8080 8081/UDP"+                parse expose "" content `shouldBe` Right (Expose (Ports [PortStr "$PORT", Port 8080 TCP, Port 8081 UDP]))+            it "should handle port ranges" $ do+                let content = "EXPOSE 80 81 8080-8085"+                parse expose "" content `shouldBe` Right (Expose (Ports [Port 80 TCP, Port 81 TCP, PortRange 8080 8085]))+         describe "syntax" $ do             it "should handle lowercase instructions (#7 - https://github.com/beijaflor-io/haskell-language-dockerfile/issues/7)" $ do                 let content = "from ubuntu"-                parse dockerfile "" content `shouldBe` Right [InstructionPos (From (UntaggedImage "ubuntu")) "" 1]+                parse dockerfile "" content `shouldBe` Right [InstructionPos (From (UntaggedImage "ubuntu" Nothing)) "" 1] +        describe "ADD" $ do+            it "simple ADD" $+                let file = unlines ["ADD . /app", "ADD http://foo.bar/baz ."]+                in assertAst file [ Add $ AddArgs [SourcePath "."] (TargetPath "/app") NoChown+                                  , Add $ AddArgs [SourcePath "http://foo.bar/baz"] (TargetPath ".") NoChown+                                  ]+            it "multifiles ADD" $+                let file = unlines ["ADD foo bar baz /app"]+                in assertAst file [ Add $ AddArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown+                                  ]++            it "list of quoted files" $+                let file = unlines ["ADD [\"foo\", \"bar\", \"baz\", \"/app\"]"]+                in assertAst file [ Add $ AddArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown+                                  ]++            it "with chown flag" $+                let file = unlines ["ADD --chown=root:root foo bar"]+                in assertAst file [ Add $ AddArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "root:root")+                                  ]++            it "list of quoted files and chown" $+                let file = unlines ["ADD --chown=user:group [\"foo\", \"bar\", \"baz\", \"/app\"]"]+                in assertAst file [ Add $ AddArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") (Chown "user:group")+                                  ]+        describe "COPY" $ do+            it "simple COPY" $+                let file = unlines ["COPY . /app", "COPY baz /some/long/path"]+                in assertAst file [ Copy $ CopyArgs [SourcePath "."] (TargetPath "/app") NoChown NoSource+                                  , Copy $ CopyArgs [SourcePath "baz"] (TargetPath "/some/long/path") NoChown NoSource+                                  ]+            it "multifiles COPY" $+                let file = unlines ["COPY foo bar baz /app"]+                in assertAst file [ Copy $ CopyArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown NoSource+                                  ]++            it "list of quoted files" $+                let file = unlines ["COPY [\"foo\", \"bar\", \"baz\", \"/app\"]"]+                in assertAst file [ Copy $ CopyArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown NoSource+                                  ]++            it "with chown flag" $+                let file = unlines ["COPY --chown=user:group foo bar"]+                in assertAst file [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") NoSource+                                  ]++            it "with from flag" $+                let file = unlines ["COPY --from=node foo bar"]+                in assertAst file [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") NoChown (CopySource "node")+                                  ]+            it "with both flags" $+                let file = unlines ["COPY --from=node --chown=user:group foo bar"]+                in assertAst file [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") (CopySource "node")+                                  ]+            it "with both flags in different order" $+                let file = unlines ["COPY --chown=user:group --from=node foo bar"]+                in assertAst file [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") (CopySource "node")+                                  ] assertAst s ast =     case parseString (s ++ "\n") of         Left err -> assertFailure $ show err