texbuilder-0.1.4.0: TexBuilder/Engine.hs
{-# LANGUAGE LambdaCase, OverloadedStrings #-}
module TexBuilder.Engine
( Engine(..)
, compile
, luaLaTex
, pdfLaTex
, recompile
, luaLaTexMk
, pdfLaTexMk )
where
import TexBuilder.Utils.Hashing
import TexBuilder.Utils.File
-- import Data.Semigroup
import Data.Monoid
import Numeric.Natural
import Text.PrettyPrint.ANSI.Leijen ((<+>))
import qualified Text.PrettyPrint.ANSI.Leijen as PP
import Control.Monad.State
import System.Directory
import System.FilePath
import System.Process
import System.Exit
import System.Environment (setEnv)
import System.Posix.Time (epochTime)
data Engine =
LuaLaTexMk
| LuaLaTex
| PdfLaTexMk
| PdfLaTex
| XeLaTexMk
| XeLaTex
runEngine :: Natural -> Engine -> EngineImpl
runEngine nrecomp = \case
LuaLaTexMk -> luaLaTexMk
LuaLaTex -> recompile nrecomp luaLaTex
PdfLaTexMk -> pdfLaTexMk
PdfLaTex -> recompile nrecomp pdfLaTex
XeLaTexMk -> xeLaTexMk
XeLaTex -> recompile nrecomp xeLaTex
type EngineImpl =
FilePath -> FilePath -> [String] -> IO (Either String FilePath)
data RecompileState =
StInitial Natural
| StSucc Natural FilePath (Digest MD5)
-- | Recompile the code until the output stabilizes
-- or maxNum compile runs is reached.
-- Care must be taken to make the latex output reproductible.
-- Otherwise the output will never stablilize.
recompile :: Natural -> EngineImpl -> EngineImpl
recompile maxNum engine outDir texfile extraArgs = do
time <- show <$> epochTime
evalStateT (recompileSt (run time)) (StInitial maxNum)
where
run time = do
setEnv "SOURCE_DATE_EPOCH" time
-- ^ For reproductible output
engine outDir texfile extraArgs
recompileSt :: IO (Either String FilePath)
-> StateT RecompileState IO (Either String FilePath)
recompileSt run = get >>= \case
StInitial maxNum -> step $ succ maxNum
StSucc i path oldHash ->
if i <= 0 then done path
else step $ \path hash ->
if hash == oldHash then done path
else succ i path hash
where
step k = lift run >>= \case
Left err -> failed err
Right path -> computeHash path >>= k path
succ i path hash = do
put $ StSucc (i-1) path hash
recompileSt run
done = pure . Right
failed = pure . Left
luaLaTex :: EngineImpl
luaLaTex outDir texfile extraArgs = do
(exCode,out,err) <- readProcessWithExitCode
"lualatex" args ""
pure $ case exCode of
ExitSuccess -> Right $ outDir </> jobname <.> "pdf"
ExitFailure _ -> Left out
where
args =
[ "--interaction=scrollmode"
, "--output-directory=" <> outDir
, "--jobname=" <> jobname
, "--output-format=pdf"
, "--file-line-error" ]
++ extraArgs ++ [ texfile ]
jobname = "texbuilder-job"
pdfLaTex :: EngineImpl
pdfLaTex outDir texfile extraArgs = do
(exCode,out,err) <- readProcessWithExitCode
"pdflatex" args ""
pure $ case exCode of
ExitSuccess -> Right $ outDir </> jobname <.> "pdf"
ExitFailure _ -> Left out
where
args =
[ "--interaction=scrollmode"
, "--output-directory=" <> outDir
, "--jobname=" <> jobname
, "--output-format=pdf"
, "--file-line-error" ]
++ extraArgs ++ [ texfile ]
jobname = "texbuilder-job"
xeLaTex :: EngineImpl
xeLaTex outDir texfile extraArgs = do
(exCode,out,err) <- readProcessWithExitCode
"xelatex" args ""
pure $ case exCode of
ExitSuccess -> Right $ outDir </> jobname <.> "pdf"
ExitFailure _ -> Left out
where
args =
[ "--interaction=scrollmode"
, "--output-directory=" <> outDir
, "--jobname=" <> jobname
, "--output-format=pdf"
, "--file-line-error" ]
++ extraArgs ++ [ texfile ]
jobname = "texbuilder-job"
xeLaTexMk :: EngineImpl
xeLaTexMk outDir texfile extraArgs = do
(exCode,out,err) <- readProcessWithExitCode
"latexmk" args ""
pure $ case exCode of
ExitSuccess -> Right $ outDir </> jobname <.> "pdf"
ExitFailure _ -> Left out
where
args =
[ "-xelatex"
, "-output-directory=" <> outDir
, "-jobname=" <> jobname ]
++ extraArgs ++ [ texfile ]
jobname = "texbuilder-job"
luaLaTexMk :: EngineImpl
luaLaTexMk outDir texfile extraArgs = do
(exCode,out,err) <- readProcessWithExitCode
"latexmk" args ""
pure $ case exCode of
ExitSuccess -> Right $ outDir </> jobname <.> "pdf"
ExitFailure _ -> Left out
where
args =
[ "-lualatex"
, "-output-directory=" <> outDir
, "-jobname=" <> jobname ]
++ extraArgs ++ [ texfile ]
jobname = "texbuilder-job"
pdfLaTexMk :: EngineImpl
pdfLaTexMk outDir texfile extraArgs = do
(exCode,out,err) <- readProcessWithExitCode
"latexmk" args ""
pure $ case exCode of
ExitSuccess -> Right $ outDir </> jobname <.> "pdf"
ExitFailure _ -> Left out
where
args =
[ "-pdf"
, "-output-directory=" <> outDir
, "-jobname=" <> jobname ]
++ extraArgs ++ [ texfile ]
jobname = "texbuilder-job"
-- | Compile the tex code and (if successful)
-- write the output to given path. Returns human readable
-- information about the build success / failure.
compile :: Engine
-> Natural
-> FilePath
-> FilePath
-> [String]
-> FilePath
-> IO PP.Doc
compile engine nrecomp texfile pdffile extraArgs dir = do
initT <- epochTime
runEngine nrecomp engine dir texfile extraArgs >>= \case
Left err -> pure $ PP.red $ PP.text err
Right outFile -> do
finalT <- epochTime
copyFile outFile pdffile
pure $ PP.green $
"Successful build from"
<+> PP.text dir <> ","
<+> "build time was"
<+> (PP.text . show $ finalT - initT)
<+> "s" <> PP.hardline