hakyll-process-0.0.2.0: src/Hakyll/Process.hs
{-|
Module : Hakyll.Process
Description : Common compilers and helpers for external executables.
Stability : experimental
-}
module Hakyll.Process
(
newExtension
, newExtOutFilePath
, execName
, execCompiler
, execCompilerWith
, unsafeExecCompiler
, CompilerOut(..)
, ExecutableArg(..)
, ExecutableArgs
, ExecutableName
, OutFilePath(..)
) where
import qualified Data.ByteString.Lazy.Char8 as B
import GHC.Conc (atomically)
import Hakyll.Core.Item
import Hakyll.Core.Compiler
import System.Process.Typed
-- | Expected output from the external compiler.
data CompilerOut =
-- | Compiler uses stdout as the output type.
CStdOut
-- | Compiler outputs to a specific target file on the filesystem.
| COutFile OutFilePath
-- | Arguments to provide to the process
data ExecutableArg =
-- | Abstract representation of the path to the Hakyll item.
HakFilePath
-- | Literal argument to provide to the other process.
| ProcArg String deriving (Read, Show)
-- | Specifies the output file path of a process.
data OutFilePath =
-- | A specific, known filepath.
SpecificPath FilePath
-- | Indicates that the output path is related to the input path.
| RelativePath (FilePath -> FilePath)
-- | Name of the executable if in the PATH or path to it if not
newtype ExecutableName = ExecutableName String deriving (Read, Show)
-- | Arguments to pass to the executable. Empty if none.
type ExecutableArgs = [ExecutableArg]
-- | Helper function to indicate that the output file name is the same as the input file name with a new extension
-- Note: like hakyll, assumes that no "." is present in the extension
newExtension ::
String -- ^ New file extension, excluding the leading "."
-> FilePath -- ^ Original FilePath
-> FilePath
newExtension ext f = (reverse . dropWhile (/= '.') . reverse $ f) <> ext
-- | Helper function to indicate that the output file name is the same as the input file name with a new extension
-- Note: like hakyll, assumes that no "." is present in the extension
newExtOutFilePath :: String -> CompilerOut
newExtOutFilePath ext = COutFile $ RelativePath (newExtension ext)
execName :: String -> ExecutableName
execName = ExecutableName
-- | Calls the external compiler with no arguments. Returns the output contents as a 'B.ByteString'.
-- If an error occurs this raises an exception.
-- May be useful if you already have build scripts for artifacts in your repository.
execCompiler :: ExecutableName -> CompilerOut -> Compiler (Item B.ByteString)
execCompiler name out = execCompilerWith name [] out
-- | Calls the external compiler with the provided arguments. Returns the output contents as a 'B.ByteString'.
-- If an error occurs this raises an exception.
execCompilerWith :: ExecutableName -> ExecutableArgs -> CompilerOut -> Compiler (Item B.ByteString)
execCompilerWith name exArgs out = do
input <- getResourceFilePath
let args = fmap (hargToArg input) exArgs
let outputReader = cOutToFileContents input out
unsafeExecCompiler name args outputReader
-- | Primarily for internal use, occasionally useful when already building a compiler imperatively.
-- Allows the caller to opt out of the declarative components of 'execCompiler' and 'execCompilerWith'.
unsafeExecCompiler ::
ExecutableName -- ^ Name or filepath of the executable
-> [String] -- ^ Arguments to pass to the executable
-> (B.ByteString -> IO B.ByteString) -- ^ Action to read the output of the compiler. Input is the stdout of the process.
-> Compiler (Item B.ByteString)
unsafeExecCompiler (ExecutableName exName) args outputReader =
do
results <- unsafeCompiler $ procResults
-- just using this to get at the item
oldBody <- getResourceString
pure $ itemSetBody results oldBody
where
procResults = withProcessWait procConf waitOutput
procConf = setStdout byteStringOutput . proc exName $ args
waitOutput process = do
let stmProc = getStdout process
out <- atomically stmProc
checkExitCode process
outputReader out
-- input fpath stdout contents
cOutToFileContents :: FilePath -> CompilerOut -> B.ByteString -> IO B.ByteString
cOutToFileContents _ CStdOut out = pure out
cOutToFileContents _ (COutFile (SpecificPath f)) _ = B.readFile f
cOutToFileContents input (COutFile (RelativePath f)) _ = B.readFile (f input)
hargToArg :: FilePath -> ExecutableArg -> String
hargToArg _ (ProcArg s) = s
hargToArg f HakFilePath = f