packages feed

language-docker-14.0.0: src/Language/Docker/Parser/Copy.hs

module Language.Docker.Parser.Copy
  ( parseCopy,
    parseAdd,
  )
where

import Data.List.NonEmpty (NonEmpty, fromList)
import qualified Data.Text as T
import Language.Docker.Parser.Prelude
import Language.Docker.Syntax

data Flag
  = FlagChecksum Checksum
  | FlagChown Chown
  | FlagChmod Chmod
  | FlagLink Link
  | FlagSource CopySource
  | FlagExclude Exclude
  | FlagInvalid (Text, Text)

parseCopy :: (?esc :: Char) => Parser (Instruction Text)
parseCopy = do
  reserved "COPY"
  flags <- copyFlag `sepEndBy` requiredWhitespace
  let chownFlags = [c | FlagChown c <- flags]
  let chmodFlags = [c | FlagChmod c <- flags]
  let linkFlags = [l | FlagLink l <- flags]
  let sourceFlags = [f | FlagSource f <- flags]
  let excludeFlags = [e | FlagExclude e <- flags]
  let invalid = [i | FlagInvalid i <- flags]
  -- Let's do some validation on the flags
  case (invalid, chownFlags, chmodFlags, linkFlags, sourceFlags, excludeFlags) of
    ((k, v) : _, _, _, _, _, _) -> unexpectedFlag k v
    (_, _ : _ : _, _, _, _, _) -> customError $ DuplicateFlagError "--chown"
    (_, _, _ : _ : _, _, _, _) -> customError $ DuplicateFlagError "--chmod"
    (_, _, _, _ : _ : _, _, _) -> customError $ DuplicateFlagError "--link"
    (_, _, _, _, _ : _ : _, _) -> customError $ DuplicateFlagError "--from"
    _ -> do
      let cho =
            case chownFlags of
              [] -> NoChown
              c : _ -> c
      let chm =
            case chmodFlags of
              [] -> NoChmod
              c : _ -> c
      let lnk =
            case linkFlags of
              [] -> NoLink
              l : _ -> l
      let fr =
            case sourceFlags of
              [] -> NoSource
              f : _ -> f
      try (heredocList (\src dest -> Copy (CopyArgs src dest) (CopyFlags cho chm lnk fr excludeFlags)))
        <|> fileList "COPY" (\src dest -> Copy (CopyArgs src dest) (CopyFlags cho chm lnk fr excludeFlags))

parseAdd :: (?esc :: Char) => Parser (Instruction Text)
parseAdd = do
  reserved "ADD"
  flags <- addFlag `sepEndBy` requiredWhitespace
  let checksumFlags = [c | FlagChecksum c <- flags]
  let chownFlags = [c | FlagChown c <- flags]
  let chmodFlags = [c | FlagChmod c <- flags]
  let linkFlags = [l | FlagLink l <- flags]
  let excludeFlags = [e | FlagExclude e <- flags]
  let invalidFlags = [i | FlagInvalid i <- flags]
  notFollowedBy (string "--") <?>
    "only the --checksum, --chown, --chmod, --link, --exclude flags or the src and dest paths"
  case (invalidFlags, checksumFlags, chownFlags, linkFlags, chmodFlags, excludeFlags) of
    ((k, v) : _, _, _, _, _, _) -> unexpectedFlag k v
    (_, _ : _ : _, _, _, _, _) -> customError $ DuplicateFlagError "--checksum"
    (_, _, _ : _ : _, _, _, _) -> customError $ DuplicateFlagError "--chown"
    (_, _, _, _ : _ : _, _, _) -> customError $ DuplicateFlagError "--chmod"
    (_, _, _, _, _ : _ : _, _) -> customError $ DuplicateFlagError "--link"
    _ -> do
      let chk = case checksumFlags of
                  [] -> NoChecksum
                  c : _ -> c
      let cho = case chownFlags of
                  [] -> NoChown
                  c : _ -> c
      let chm = case chmodFlags of
                  [] -> NoChmod
                  c : _ -> c
      let lnk = case linkFlags of
                  [] -> NoLink
                  l : _ -> l
      fileList "ADD" (\src dest -> Add (AddArgs src dest) (AddFlags chk cho chm lnk excludeFlags))

heredocList :: (?esc :: Char) =>
               (NonEmpty SourcePath -> TargetPath -> Instruction Text) ->
               Parser (Instruction Text)
heredocList constr = do
  markers <- try (spaceSep1 heredocMarker) <?> "a list of heredoc markers"
  target <- untilEol "target path"
  case reverse markers of
    [] -> customError $ FileListError "empty list of heredoc markers"
    m:_ -> void $ heredocContent m
  return $ constr (SourcePath <$> fromList markers) (TargetPath target)

fileList :: (?esc :: Char) => Text ->
            (NonEmpty SourcePath -> TargetPath -> Instruction Text) ->
            Parser (Instruction Text)
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
    [_] -> customError $ FileListError (T.unpack name)
    _ -> return $ constr (SourcePath <$> fromList (init paths)) (TargetPath $ last paths)
  where
    spaceSeparated =
      someUnless "a file" (== ' ') `sepEndBy1` (try requiredWhitespace <?> "at least another file path")
    stringList = brackets $ commaSep doubleQuotedString

unexpectedFlag :: Text -> Text -> Parser a
unexpectedFlag name "" = customFailure $ NoValueFlagError (T.unpack name)
unexpectedFlag name _ = customFailure $ InvalidFlagError (T.unpack name)

copyFlag :: (?esc :: Char) => Parser Flag
copyFlag = (FlagSource <$> try copySource <?> "only one --from") <|> addFlag

addFlag :: (?esc :: Char) => Parser Flag
addFlag = (FlagChecksum <$> try checksum <?> "--checksum")
  <|> (FlagChown <$> try chown <?> "--chown")
  <|> (FlagChmod <$> try chmod <?> "--chmod")
  <|> (FlagLink <$> try link <?> "--link")
  <|> (FlagExclude <$> try exclude <?> "--exclude")
  <|> (FlagInvalid <$> try anyFlag <?> "other flag")

checksum :: (?esc :: Char) => Parser Checksum
checksum = do
  void $ string "--checksum="
  chk <- someUnless "the remote file checksum" (== ' ')
  return $ Checksum chk

chown :: (?esc :: Char) => Parser Chown
chown = do
  void $ string "--chown="
  cho <- someUnless "the user and group for chown" (== ' ')
  return $ Chown cho

chmod :: (?esc :: Char) => Parser Chmod
chmod = do
  void $ string "--chmod="
  chm <- someUnless "the mode for chmod" (== ' ')
  return $ Chmod chm

link :: Parser Link
link = do
  void $ string "--link"
  return Link

copySource :: (?esc :: Char) => Parser CopySource
copySource = do
  void $ string "--from="
  src <- someUnless "the copy source path" isNl
  return $ CopySource src

exclude :: (?esc :: Char) => Parser Exclude
exclude = do
  void $ string "--exclude="
  exc <- someUnless "the exclude pattern" (== ' ')
  return $ Exclude exc

anyFlag :: (?esc :: Char) => Parser (Text, Text)
anyFlag = do
  void $ string "--"
  name <- someUnless "the flag value" (== '=')
  void $ char '='
  val <- anyUnless (== ' ')
  return (T.append "--" name, val)