Compile .tex and remove .div file from Haskell

Posted on

Problem

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 <- E.getArgs
    case args of
        [] -> putStrLn "Specify a file!"
        (filename:_)  -> handler filename

isTeXFile :: S.FilePath -> Bool
isTeXFile = ( == ".tex") . map toLower . S.takeExtension

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

compile :: S.FilePath -> 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.

Solution

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 -> IO ()
handler f = do
  (Right isfile) <- 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 >>= 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 -> c) -> (b -> c) -> Either a b -> 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 -> IO Bool
isTeXFile fp = do
  (Right ans) <- S.shell $ S.run "file" [fp] ""
  return $ " LaTeX " `isInfixOf` ans

handler :: S.FilePath -> IO ()
handler f = do
  (Right isfile) <- S.shell $ S.isFile f
  if not isfile        then putStrLn "File not found."
  else do
  testTexFile    <- isTeXFile f
  if not $ testTexFile then putStrLn "Not a .tex file!"
  else compile f >>= 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).

Leave a Reply

Your email address will not be published. Required fields are marked *