7
\$\begingroup\$

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.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to Code Review! That's an interesting question, I hope you get some good reviews! \$\endgroup\$
    – Phrancis
    Commented Dec 26, 2014 at 15:05

1 Answer 1

3
\$\begingroup\$

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).

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much! Your guess is right, the package I used was shellmate (I had forgotten that I'd installed it with cabal!). \$\endgroup\$
    – Yosh
    Commented Dec 29, 2014 at 5:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.