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.

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Dec 26, 2014 at 11:28
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Welcome to Code Review! That's an interesting question, I hope you get some good reviews! \$\endgroup\$ 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).

answered Dec 28, 2014 at 23:09
\$\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\$ Commented Dec 29, 2014 at 5:35

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.