module Language.Docker.Parser
  ( parseText,
    parseFile,
    parseStdin,
    Parser,
    Error,
    DockerfileError (..),
  )
where

import qualified Data.ByteString as B
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import qualified Data.Text.Encoding.Error as E
import Language.Docker.Parser.Instruction (parseInstruction, parseComment)
import Language.Docker.Parser.Prelude
import Language.Docker.Syntax

contents :: Parser a -> Parser a
contents :: forall a. Parser a -> Parser a
contents Parser a
p = do
  forall (f :: * -> *) a. Functor f => f a -> f ()
void Parser Text
onlyWhitespaces
  a
r <- Parser a
p
  forall e s (m :: * -> *). MonadParsec e s m => m ()
eof
  forall (m :: * -> *) a. Monad m => a -> m a
return a
r

dockerfile :: (?esc :: Char) => Parser Dockerfile
dockerfile :: (?esc::Char) => Parser Dockerfile
dockerfile =
  forall (m :: * -> *) a. MonadPlus m => m a -> m [a]
many forall a b. (a -> b) -> a -> b
$ do
    SourcePos
pos <- forall s e (m :: * -> *).
(TraversableStream s, MonadParsec e s m) =>
m SourcePos
getSourcePos
    Instruction Text
i <- (?esc::Char) => Parser (Instruction Text)
parseInstruction
    (?esc::Char) => ParsecT DockerfileError Text Identity ()
eol forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> forall e s (m :: * -> *). MonadParsec e s m => m ()
eof forall e s (m :: * -> *) a.
MonadParsec e s m =>
m a -> String -> m a
<?> String
"a new line followed by the next instruction"
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall args.
Instruction args -> Text -> Linenumber -> InstructionPos args
InstructionPos Instruction Text
i (String -> Text
T.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. SourcePos -> String
sourceName forall a b. (a -> b) -> a -> b
$ SourcePos
pos) (Pos -> Linenumber
unPos forall b c a. (b -> c) -> (a -> b) -> a -> c
. SourcePos -> Pos
sourceLine forall a b. (a -> b) -> a -> b
$ SourcePos
pos)

parseText :: Text -> Either Error Dockerfile
parseText :: Text -> Either Error Dockerfile
parseText Text
txt = do
  let ?esc = [Text] -> Char
findEscapePragma (Text -> [Text]
T.lines (Text -> Text
dos2unix Text
txt))
   in forall e s a.
Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
parse (forall a. Parser a -> Parser a
contents (?esc::Char) => Parser Dockerfile
dockerfile) String
"<string>" (Text -> Text
dos2unix Text
txt)

parseFile :: FilePath -> IO (Either Error Dockerfile)
parseFile :: String -> IO (Either Error Dockerfile)
parseFile String
file = String -> ByteString -> Either Error Dockerfile
doParse String
file forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO ByteString
B.readFile String
file

-- | Reads the standard input until the end and parses the contents as a Dockerfile
parseStdin :: IO (Either Error Dockerfile)
parseStdin :: IO (Either Error Dockerfile)
parseStdin = String -> ByteString -> Either Error Dockerfile
doParse String
"/dev/stdin" forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO ByteString
B.getContents

-- | Parses a list of lines from a dockerfile one by one until either the escape
-- | pragma has been found, or pragmas are no longer expected.
-- | Pragmas can occur only until a comment, an empty line or another
-- | instruction occurs (i.e. they have to be the first lines of a Dockerfile).
findEscapePragma :: [Text] -> Char
findEscapePragma :: [Text] -> Char
findEscapePragma [] = Char
defaultEsc
findEscapePragma (Text
l:[Text]
ls) =
  case forall e s a.
Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
parse (forall a. Parser a -> Parser a
contents (?esc::Char) => Parser (Instruction Text)
parseComment) String
"<line>" Text
l of
    Left Error
_ -> Char
defaultEsc
    Right (Pragma (Escape (EscapeChar Char
c))) -> Char
c
    Right (Pragma PragmaDirective
_) -> [Text] -> Char
findEscapePragma [Text]
ls
    Right Instruction Text
_ -> Char
defaultEsc
  where
    ?esc = Char
defaultEsc

doParse :: FilePath -> B.ByteString -> Either Error Dockerfile
doParse :: String -> ByteString -> Either Error Dockerfile
doParse String
path ByteString
txt = do
  let ?esc = [Text] -> Char
findEscapePragma (Text -> [Text]
T.lines Text
src)
   in forall e s a.
Parsec e s a -> String -> s -> Either (ParseErrorBundle s e) a
parse (forall a. Parser a -> Parser a
contents (?esc::Char) => Parser Dockerfile
dockerfile) String
path Text
src
  where
    src :: Text
src =
      case Linenumber -> ByteString -> ByteString
B.take Linenumber
4 ByteString
txt of
        ByteString
"\255\254\NUL\NUL" ->
          Text -> Text
dos2unix (OnDecodeError -> ByteString -> Text
E.decodeUtf32LEWith OnDecodeError
E.lenientDecode forall a b. (a -> b) -> a -> b
$ Linenumber -> ByteString -> ByteString
B.drop Linenumber
4 ByteString
txt)
        ByteString
"\NUL\NUL\254\255" ->
          Text -> Text
dos2unix (OnDecodeError -> ByteString -> Text
E.decodeUtf32BEWith OnDecodeError
E.lenientDecode forall a b. (a -> b) -> a -> b
$ Linenumber -> ByteString -> ByteString
B.drop Linenumber
4 ByteString
txt)
        ByteString
_ ->
          case Linenumber -> ByteString -> ByteString
B.take Linenumber
2 ByteString
txt of
            ByteString
"\255\254" ->
              Text -> Text
dos2unix (OnDecodeError -> ByteString -> Text
E.decodeUtf16LEWith OnDecodeError
E.lenientDecode forall a b. (a -> b) -> a -> b
$ Linenumber -> ByteString -> ByteString
B.drop Linenumber
2 ByteString
txt)
            ByteString
"\254\255" ->
              Text -> Text
dos2unix (OnDecodeError -> ByteString -> Text
E.decodeUtf16BEWith OnDecodeError
E.lenientDecode forall a b. (a -> b) -> a -> b
$ Linenumber -> ByteString -> ByteString
B.drop Linenumber
2 ByteString
txt)
            ByteString
_ ->
              case Linenumber -> ByteString -> ByteString
B.take Linenumber
3 ByteString
txt of
                ByteString
"\239\187\191" ->
                  Text -> Text
dos2unix (OnDecodeError -> ByteString -> Text
E.decodeUtf8With OnDecodeError
E.lenientDecode forall a b. (a -> b) -> a -> b
$ Linenumber -> ByteString -> ByteString
B.drop Linenumber
3 ByteString
txt)
                ByteString
_ -> Text -> Text
dos2unix (OnDecodeError -> ByteString -> Text
E.decodeUtf8With OnDecodeError
E.lenientDecode ByteString
txt)

-- | Changes crlf line endings to simple line endings
dos2unix :: T.Text -> T.Text
dos2unix :: Text -> Text
dos2unix = Text -> Text -> Text -> Text
T.replace Text
"\r\n" Text
"\n"