{-# LANGUAGE ScopedTypeVariables #-}
module Reanimate.Driver.Check
( checkEnvironment
, hasRSvg
, hasInkscape
, hasMagick
, hasFFmpegRSvg
) where
import Control.Exception (SomeException, handle)
import Control.Monad
import Data.Maybe
import Data.Version
import Reanimate.Misc (runCmd_)
import Reanimate.Driver.Magick (magickCmd)
import System.Console.ANSI.Codes
import System.Directory (findExecutable)
import System.IO
import System.IO.Temp
import Text.ParserCombinators.ReadP
import Text.Printf
checkEnvironment :: IO ()
checkEnvironment = do
putStrLn "reanimate checks:"
runCheck "Has ffmpeg" hasFFmpeg
runCheck "Has ffmpeg(rsvg)" hasFFmpegRSvg
runCheck "Has dvisvgm" hasDvisvgm
runCheck "Has povray" hasPovray
runCheck "Has blender" hasBlender
runCheck "Has rsvg-convert" hasRSvg
runCheck "Has inkscape" hasInkscape
runCheck "Has imagemagick" hasMagick
runCheck "Has LaTeX" hasLaTeX
runCheck ("Has LaTeX package '"++ "babel" ++ "'") $ hasTeXPackage "latex"
"[english]{babel}"
forM_ latexPackages $ \pkg ->
runCheck ("Has LaTeX package '"++ pkg ++ "'") $ hasTeXPackage "latex" $
"{"++pkg++"}"
runCheck "Has XeLaTeX" hasXeLaTeX
forM_ xelatexPackages $ \pkg ->
runCheck ("Has XeLaTeX package '"++ pkg ++ "'") $ hasTeXPackage "xelatex" $
"{"++pkg++"}"
where
latexPackages =
["preview"
,"amsmath"
]
xelatexPackages =
["ctex"]
runCheck msg fn = do
printf " %-35s" (msg ++ ":")
val <- fn
case val of
Left err -> putStrLnColor Red err
Right ok -> putStrLnColor Green ok
putStrLnColor :: Color -> String -> IO ()
putStrLnColor color msg =
putStrLn $ setSGRCode [SetColor Foreground Vivid color] ++ msg ++ setSGRCode [Reset]
hasLaTeX :: IO (Either String String)
hasLaTeX = hasProgram "latex"
hasXeLaTeX :: IO (Either String String)
hasXeLaTeX = hasProgram "xelatex"
hasDvisvgm :: IO (Either String String)
hasDvisvgm = hasProgram "dvisvgm"
hasPovray :: IO (Either String String)
hasPovray = hasProgram "povray"
hasFFmpeg :: IO (Either String String)
hasFFmpeg = checkMinVersion minVersion <$> ffmpegVersion
where
minVersion = Version [4,1,3] []
hasFFmpegRSvg :: IO (Either String String)
hasFFmpegRSvg = do
mbPath <- findExecutable "ffmpeg"
case mbPath of
Nothing -> return $ Left "n/a"
Just path -> do
ret <- runCmd_ path ["-version"]
pure $ case ret of
Right out | "--enable-librsvg" `elem` words out
-> Right "yes"
_ -> Left "no"
hasBlender :: IO (Either String String)
hasBlender = checkMinVersion minVersion <$> blenderVersion
where
minVersion = Version [2,80] []
hasRSvg :: IO (Either String String)
hasRSvg = checkMinVersion minVersion <$> rsvgVersion
where
minVersion = Version [2,44,0] []
hasInkscape :: IO (Either String String)
hasInkscape = checkMinVersion minVersion <$> inkscapeVersion
where
minVersion = Version [0,92] []
hasMagick :: IO (Either String String)
hasMagick = checkMinVersion minVersion <$> magickVersion
where
minVersion = Version [6,0,0] []
ffmpegVersion :: IO (Maybe Version)
ffmpegVersion = extractVersion "ffmpeg" ["-version"] $ \line ->
case take 3 $ words line of
["ffmpeg", "version", vs] -> vs
_ -> ""
blenderVersion :: IO (Maybe Version)
blenderVersion = extractVersion "blender" ["--version"] $ \line ->
case take 2 (words line) of
["Blender", vs] -> vs
_ -> ""
rsvgVersion :: IO (Maybe Version)
rsvgVersion = extractVersion "rsvg-convert" ["--version"] $ \line ->
case words line of
["rsvg-convert", "version", vs] -> vs
_ -> ""
inkscapeVersion :: IO (Maybe Version)
inkscapeVersion = extractVersion "inkscape" ["--version"] $ \line ->
case take 2 $ words line of
["Inkscape", vs] -> vs
_ -> ""
magickVersion :: IO (Maybe Version)
magickVersion = extractVersion magickCmd ["-version"] $ \line ->
case take 3 $ words line of
["Version:", "ImageMagick", vs] -> vs
_ -> ""
checkMinVersion :: Version -> Maybe Version -> Either String String
checkMinVersion _minVersion Nothing = Left "no"
checkMinVersion minVersion (Just vs)
| vs < minVersion = Left $ "too old: " ++ showVersion vs ++ " < " ++ showVersion minVersion
| otherwise = Right (showVersion vs)
extractVersion :: FilePath -> [String] -> (String -> String) -> IO (Maybe Version)
extractVersion execPath args outputFilter = do
mbPath <- findExecutable execPath
case mbPath of
Nothing -> return Nothing
Just path -> do
ret <- runCmd_ path args
case ret of
Left{} -> return $ Just noVersion
Right out ->
pure $ Just $ fromMaybe noVersion $ parseVS $ outputFilter out
where
noVersion = Version [] []
parseVS vs = listToMaybe $ reverse
[ v | (v, _) <- readP_to_S parseVersion vs ]
hasTeXPackage :: FilePath -> String -> IO (Either String String)
hasTeXPackage exec pkg = handle (\(_::SomeException) -> return $ Left "n/a") $
withSystemTempDirectory "reanimate" $ \tmp_dir -> withTempFile tmp_dir "test.tex" $ \tex_file tex_handle -> do
hPutStr tex_handle tex_document
hPutStr tex_handle $ "\\usepackage" ++ pkg ++ "\n"
hPutStr tex_handle "\\begin{document}\n"
hPutStr tex_handle "blah\n"
hPutStr tex_handle tex_epilogue
hClose tex_handle
ret <- runCmd_ exec ["-interaction=batchmode", "-halt-on-error", "-output-directory="++tmp_dir, tex_file]
return $ case ret of
Right{} -> Right "OK"
Left{} -> Left "missing"
where
tex_document = "\\documentclass[preview]{standalone}\n"
tex_epilogue =
"\n\
\\\end{document}"
hasProgram :: String -> IO (Either String String)
hasProgram exec = do
mbPath <- findExecutable exec
return $ case mbPath of
Nothing -> Left $ "'" ++ exec ++ "' not found"
Just path -> Right path