Compile .tex and remove .div file from Haskell As a practice for interacting with system commands, I wrote a Haskell program that compiles a .tex file specified by the argument.

When one execute this program:


If no argument is specified, print "Specify a file!" then exit
If the specified file does not exist, print "File not found." then exit
If the specified file does not have the extension .tex, then print "Not a .tex file!" then exit
If a .tex file is specified (report.tex), execute the following commands

$ uplatex report.tex -o report.dvi
$ dvipdfmx report.dvi
$ rm report.dvi

The program does not have to handle errors in the execution of tex-related commands.


This is what I wrote:

import qualified Control.Shell as S
import Data.Char (toLower)
import Data.List (dropWhileEnd)
import qualified System.Environment as E

main = do
    args &lt;- E.getArgs
    case args of
        [] -&gt; putStrLn "Specify a file!"
        (filename:_)  -&gt; handler filename

isTeXFile :: S.FilePath -&gt; Bool
isTeXFile = ( == ".tex") . map toLower . S.takeExtension

handler :: S.FilePath -&gt; IO ()
handler f = do
    (Right isfile) &lt;- S.shell $ S.isFile f
    if not isfile
        then putStrLn "File not found."
        else
            if isTeXFile f
                then do
                    result &lt;- compile f
                    case result of
                        (Left l) -&gt; putStrLn l
                        (Right _) -&gt; return ()
                else
                    putStrLn "Not a .tex file!"

compile :: S.FilePath -&gt; IO (Either String ())
compile f = S.shell $ do
    S.run_ "uplatex" [f, "-o", dvifile] ""
    S.run_ "dvipdfmx" [dvifile] ""
    S.rm dvifile
    where
        basename = dropWhileEnd (/= '.') f
        dvifile = basename ++ "dvi"


I appreciate any kind of feedback. I'm especially concerned about:


The body of handler seems unnecessarily complicated. How can I clean it up?
Am I using Control.Shell in the right way? Is there other function I should use instead? Maybe I should consider using System.Process?


Side note: I don't use pdflatex because it is not as good as uplatex at Japanese typesetting and I haven't been catching up with the latest LaTeX families.
When you are using packages, please make sure to say which ones. I guessed shellmate btw. As for the code, it's quite readable except for the handler definition which I've rewritten as follows:

handler :: S.FilePath -&gt; IO ()
handler f = do
  (Right isfile) &lt;- S.shell $ S.isFile f
  if      not isfile        then putStrLn "File not found."
  else if not $ isTeXFile f then putStrLn "Not a .tex file!"
  else compile f &gt;&gt;= either putStrLn return


Given that you are quickly dismissing the cases you are not interested in, I inspected not $ isTeXFile f and systematically written the if (...) then clauses on the same line. This avoids the crazy right-leaning nesting.

Using the either combinator of type (a -&gt; c) -&gt; (b -&gt; c) -&gt; Either a b -&gt; c lets you rewrite the final case expression in a more concise fashion.

Finally, I don't think it's good practice to test a file's type based on its extension. Given that you are already running external tools, you may want to use ̀file for that job. It would look something like this:

isTeXFile :: S.FilePath -&gt; IO Bool
isTeXFile fp = do
  (Right ans) &lt;- S.shell $ S.run "file" [fp] ""
  return $ " LaTeX " `isInfixOf` ans

handler :: S.FilePath -&gt; IO ()
handler f = do
  (Right isfile) &lt;- S.shell $ S.isFile f
  if not isfile        then putStrLn "File not found."
  else do
  testTexFile    &lt;- isTeXFile f
  if not $ testTexFile then putStrLn "Not a .tex file!"
  else compile f &gt;&gt;= either putStrLn return


The rest is pretty much perfect as far as I'm concerned (modulo some cosmetic preferences which I won't bore you with).