never executed always true always false
    1 module Reanimate.Misc
    2   ( requireExecutable
    3   , runCmd
    4   , runCmd_
    5   , runCmdLazy
    6   , withTempDir
    7   , withTempFile
    8   , renameOrCopyFile
    9   ) where
   10 
   11 import           Control.Concurrent
   12 import           Control.Exception  (catch, evaluate, finally, throw)
   13 import qualified Data.Text          as T
   14 import qualified Data.Text.IO       as T
   15 import           Foreign.C.Error
   16 import           GHC.IO.Exception
   17 import           System.Directory   (copyFile, findExecutable, removeFile,
   18                                      renameFile)
   19 import           System.FilePath    ((<.>))
   20 import           System.IO          (hClose, hGetContents, hIsEOF, hPutStr,
   21                                      stderr)
   22 import           System.IO.Temp     (withSystemTempDirectory,
   23                                      withSystemTempFile)
   24 import           System.Process     (readProcessWithExitCode,
   25                                      runInteractiveProcess, showCommandForUser,
   26                                      terminateProcess, waitForProcess)
   27 
   28 
   29 requireExecutable :: String -> IO FilePath
   30 requireExecutable exec = do
   31   mbPath <- findExecutable exec
   32   case mbPath of
   33     Nothing   -> error $ "Couldn't find executable: " ++ exec
   34     Just path -> return path
   35 
   36 runCmd :: FilePath -> [String] -> IO ()
   37 runCmd exec args = do
   38   ret <- runCmd_ exec args
   39   case ret of
   40     Left err -> error $ showCommandForUser exec args ++ ":\n" ++ err
   41     Right{}  -> return ()
   42 
   43 runCmd_ :: FilePath -> [String] -> IO (Either String String)
   44 runCmd_ exec args = do
   45   (ret, stdout, errMsg) <- readProcessWithExitCode exec args ""
   46   _                     <- evaluate (length stdout + length errMsg)
   47   case ret of
   48     ExitSuccess -> return (Right stdout)
   49     ExitFailure err | False ->
   50       return
   51         $  Left
   52         $  "Failed to run: "
   53         ++ showCommandForUser exec args
   54         ++ "\n"
   55         ++ "Error code: "
   56         ++ show err
   57         ++ "\n"
   58         ++ "stderr: "
   59         ++ errMsg
   60     ExitFailure{} | null errMsg -> -- LaTeX prints errors to stdout. :(
   61       return $ Left stdout
   62     ExitFailure{} -> return $ Left errMsg
   63 
   64 runCmdLazy
   65   :: FilePath -> [String] -> (IO (Either String T.Text) -> IO a) -> IO a
   66 runCmdLazy exec args handler = do
   67   (inp, out, err, pid) <- runInteractiveProcess exec args Nothing Nothing
   68   hClose inp
   69   errOutput <- hGetContents err
   70   _ <- forkIO $ hPutStr stderr errOutput
   71   let fetch = do
   72         eof <- hIsEOF out
   73         if eof
   74           then do
   75             _      <- evaluate (length errOutput)
   76             ret    <- waitForProcess pid
   77             case ret of
   78               ExitSuccess   -> return (Left "")
   79               ExitFailure{} -> return (Left errOutput)
   80               {-ExitFailure errMsg -> do
   81                 return $ Left $
   82                   "Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
   83                   "Error code: " ++ show errMsg ++ "\n" ++
   84                   "stderr: " ++ stderr-}
   85           else do
   86             line <- T.hGetLine out
   87             return (Right line)
   88   handler fetch `finally` do
   89     terminateProcess pid
   90     _ <- waitForProcess pid
   91     return ()
   92 
   93 -- renameFile fails if we're crossing filesystem boundaries. If this happens,
   94 -- revert back to copyFile + removeFile.
   95 renameOrCopyFile :: FilePath -> FilePath -> IO ()
   96 renameOrCopyFile src dst = renameFile src dst `catch` exdev
   97  where
   98   exdev e = if fmap Errno (ioe_errno e) == Just eXDEV
   99     then copyFile src dst >> removeFile src
  100     else throw e
  101 
  102 withTempDir :: (FilePath -> IO a) -> IO a
  103 withTempDir = withSystemTempDirectory "reanimate"
  104 
  105 withTempFile :: String -> (FilePath -> IO a) -> IO a
  106 withTempFile ext action =
  107   withSystemTempFile ("reanimate" <.> ext) $ \path hd ->
  108     hClose hd >> action path