packages feed

scripths-0.1.0.0: src/ScriptHs/Run.hs

{-# LANGUAGE NamedFieldPuns #-}

module ScriptHs.Run where

import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import ScriptHs.Parser (
    CabalMeta (..),
    ScriptFile (scriptLines, scriptMeta),
 )
import ScriptHs.Render (toGhciScript)
import System.Exit (ExitCode (..), exitWith)
import System.FilePath ((</>))
import System.IO (stderr)
import System.IO.Temp (withSystemTempDirectory)
import System.Process (
    CreateProcess (delegate_ctlc),
    createProcess,
    proc,
    readProcessWithExitCode,
    waitForProcess,
 )

runScript :: ScriptFile -> IO ()
runScript = runWithCont runGhc

runScriptCapture :: ScriptFile -> IO T.Text
runScriptCapture = runWithCont captureGhc

runWithCont :: (FilePath -> CabalMeta -> FilePath -> IO a) -> ScriptFile -> IO a
runWithCont cont sf = withSystemTempDirectory "scripths" $ \tmpDir -> do
    (ghciPath, envPath) <- createScriptAndEnvironment sf tmpDir
    cont envPath (scriptMeta sf) ghciPath

createScriptAndEnvironment :: ScriptFile -> FilePath -> IO (FilePath, FilePath)
createScriptAndEnvironment sf tmpDir = do
    let ghciPath = tmpDir </> "script.ghci"
        envPath = tmpDir </> ".ghc.environment"
        sm = scriptMeta sf
    TIO.writeFile ghciPath (toGhciScript (scriptLines sf))
    resolveDeps envPath (metaDeps sm <> ["base"])
    pure (ghciPath, envPath)

captureGhc :: FilePath -> CabalMeta -> FilePath -> IO T.Text
captureGhc env meta ghciPath = do
    let args = ghcArgs env meta ghciPath
    (code, out, err) <- readProcessWithExitCode "ghc" args ""
    case code of
        ExitSuccess -> pure (T.pack $ out <> err)
        ExitFailure _ -> do
            TIO.hPutStrLn stderr (T.pack err)
            exitWith code

runGhc :: FilePath -> CabalMeta -> FilePath -> IO ()
runGhc env meta ghciPath = do
    let args = ghcArgs env meta ghciPath
        cp = (proc "ghc" args){delegate_ctlc = True}
    (_, _, _, ph) <- createProcess cp
    code <- waitForProcess ph
    case code of
        ExitSuccess -> pure ()
        ExitFailure _ -> exitWith code

ghcArgs :: FilePath -> CabalMeta -> FilePath -> [String]
ghcArgs env CabalMeta{metaExts, metaGhcOptions} ghciPath =
    let envFlags = ["-package-env=" ++ env]
        extFlags = map (\e -> "-X" ++ T.unpack e) metaExts
        optFlags = map T.unpack metaGhcOptions
        scriptArg = ":script " ++ ghciPath
     in envFlags ++ extFlags ++ optFlags ++ ["-e", scriptArg]

resolveDeps :: FilePath -> [T.Text] -> IO ()
resolveDeps _ [] = pure ()
resolveDeps envPath deps = do
    let args =
            ["-v0", "install", "--lib", "--package-env=" ++ envPath, "--force-reinstalls"]
                ++ map T.unpack deps
        cp = (proc "cabal" args){delegate_ctlc = True}
    (_, _, _, ph) <- createProcess cp
    code <- waitForProcess ph
    case code of
        ExitSuccess -> pure ()
        ExitFailure n -> do
            TIO.hPutStrLn
                stderr
                (T.pack ("scripths: cabal install --lib failed (exit " ++ show n ++ ")"))
            exitWith code