{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module System.Elm.Compile (
Mode (..),
ElmOutputFormat (..),
compileElm,
)
where
import Control.Exception.Safe (tryAny)
import Control.Monad (Monad((>>=)), void)
import Data.String (IsString)
import Language.Haskell.TH (Q, runIO)
import Language.Haskell.TH.Syntax (addDependentFile)
import Prelude
( Bool(True), Functor(fmap), Maybe(Just, Nothing), MonadFail(fail)
, Semigroup((<>)), Show(show), ($), (++), (.), Eq, FilePath, String, putStrLn
)
import System.Directory (createDirectory, removeDirectoryRecursive)
import System.Exit (ExitCode(ExitSuccess))
import System.Posix
( ProcessStatus(Exited), executeFile, forkProcess, getProcessStatus
)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
data Mode
= Optimize
| Dev
| Debug
deriving stock (Mode -> Mode -> Bool
(Mode -> Mode -> Bool) -> (Mode -> Mode -> Bool) -> Eq Mode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Mode -> Mode -> Bool
== :: Mode -> Mode -> Bool
$c/= :: Mode -> Mode -> Bool
/= :: Mode -> Mode -> Bool
Eq, Int -> Mode -> ShowS
[Mode] -> ShowS
Mode -> FilePath
(Int -> Mode -> ShowS)
-> (Mode -> FilePath) -> ([Mode] -> ShowS) -> Show Mode
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Mode -> ShowS
showsPrec :: Int -> Mode -> ShowS
$cshow :: Mode -> FilePath
show :: Mode -> FilePath
$cshowList :: [Mode] -> ShowS
showList :: [Mode] -> ShowS
Show)
data ElmOutputFormat
= JavaScript
| Html
deriving stock (ElmOutputFormat -> ElmOutputFormat -> Bool
(ElmOutputFormat -> ElmOutputFormat -> Bool)
-> (ElmOutputFormat -> ElmOutputFormat -> Bool)
-> Eq ElmOutputFormat
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ElmOutputFormat -> ElmOutputFormat -> Bool
== :: ElmOutputFormat -> ElmOutputFormat -> Bool
$c/= :: ElmOutputFormat -> ElmOutputFormat -> Bool
/= :: ElmOutputFormat -> ElmOutputFormat -> Bool
Eq, Int -> ElmOutputFormat -> ShowS
[ElmOutputFormat] -> ShowS
ElmOutputFormat -> FilePath
(Int -> ElmOutputFormat -> ShowS)
-> (ElmOutputFormat -> FilePath)
-> ([ElmOutputFormat] -> ShowS)
-> Show ElmOutputFormat
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ElmOutputFormat -> ShowS
showsPrec :: Int -> ElmOutputFormat -> ShowS
$cshow :: ElmOutputFormat -> FilePath
show :: ElmOutputFormat -> FilePath
$cshowList :: [ElmOutputFormat] -> ShowS
showList :: [ElmOutputFormat] -> ShowS
Show)
buildDir :: (IsString a) => a
buildDir :: forall a. IsString a => a
buildDir = a
".om-elm-build-dir"
compileElm
:: Mode
-> ElmOutputFormat
-> FilePath
-> Q String
compileElm :: Mode -> ElmOutputFormat -> FilePath -> Q FilePath
compileElm Mode
mode ElmOutputFormat
format FilePath
elmFile = do
FilePath -> Q ()
addDependentFile FilePath
elmFile
IO FilePath -> Q FilePath
forall a. IO a -> Q a
runIO (IO FilePath -> Q FilePath) -> IO FilePath -> Q FilePath
forall a b. (a -> b) -> a -> b
$ do
IO (Either SomeException ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Either SomeException ()) -> IO ())
-> (IO () -> IO (Either SomeException ())) -> IO () -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO () -> IO (Either SomeException ())
forall (m :: * -> *) a.
(HasCallStack, MonadCatch m) =>
m a -> m (Either SomeException a)
tryAny (IO () -> IO ()) -> IO () -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath -> IO ()
removeDirectoryRecursive FilePath
forall a. IsString a => a
buildDir
FilePath -> IO ()
createDirectory FilePath
forall a. IsString a => a
buildDir
FilePath -> IO ()
putStrLn (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
"Compiling elm file: " FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
elmFile
let
flags :: [String]
flags :: [FilePath]
flags =
case Mode
mode of
Mode
Debug -> [FilePath
"--debug"]
Mode
Dev -> []
Mode
Optimize -> [FilePath
"--optimize"]
IO () -> IO ProcessID
forkProcess
(
FilePath
-> Bool -> [FilePath] -> Maybe [(FilePath, FilePath)] -> IO ()
forall a.
FilePath
-> Bool -> [FilePath] -> Maybe [(FilePath, FilePath)] -> IO a
executeFile FilePath
"elm" Bool
True ([
FilePath
"make",
FilePath
elmFile,
FilePath
"--output=" FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> ElmOutputFormat -> FilePath
buildFile ElmOutputFormat
format
] [FilePath] -> [FilePath] -> [FilePath]
forall a. [a] -> [a] -> [a]
++ [FilePath]
flags) Maybe [(FilePath, FilePath)]
forall a. Maybe a
Nothing
) IO ProcessID
-> (ProcessID -> IO (Maybe ProcessStatus))
-> IO (Maybe ProcessStatus)
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Bool -> Bool -> ProcessID -> IO (Maybe ProcessStatus)
getProcessStatus Bool
True Bool
True IO (Maybe ProcessStatus)
-> (Maybe ProcessStatus -> IO FilePath) -> IO FilePath
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Maybe ProcessStatus
Nothing -> FilePath -> IO FilePath
forall a. FilePath -> IO a
forall (m :: * -> *) a. MonadFail m => FilePath -> m a
fail FilePath
"elm should have ended."
Just (Exited ExitCode
ExitSuccess) ->
(ByteString -> FilePath) -> IO ByteString -> IO FilePath
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ByteString -> FilePath
BS8.unpack (FilePath -> IO ByteString
BS.readFile (ElmOutputFormat -> FilePath
buildFile ElmOutputFormat
format))
Maybe ProcessStatus
e -> FilePath -> IO FilePath
forall a. FilePath -> IO a
forall (m :: * -> *) a. MonadFail m => FilePath -> m a
fail (FilePath -> IO FilePath) -> FilePath -> IO FilePath
forall a b. (a -> b) -> a -> b
$ FilePath
"elm failed with: " FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ Maybe ProcessStatus -> FilePath
forall a. Show a => a -> FilePath
show Maybe ProcessStatus
e
buildFile :: ElmOutputFormat -> FilePath
buildFile :: ElmOutputFormat -> FilePath
buildFile = \case
ElmOutputFormat
JavaScript -> FilePath
forall a. IsString a => a
buildDir FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> FilePath
"/elm.js"
ElmOutputFormat
Html -> FilePath
forall a. IsString a => a
buildDir FilePath -> ShowS
forall a. Semigroup a => a -> a -> a
<> FilePath
"/elm.html"