packages feed

texrunner (empty) → 0.0.1.0

raw patch · 10 files changed

+981/−0 lines, 10 filesdep +HUnitdep +attoparsecdep +basesetup-changed

Dependencies added: HUnit, attoparsec, base, bytestring, directory, filepath, io-streams, lens, mtl, process, temporary, test-framework, test-framework-hunit, texrunner

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Chris Chalmers++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Chris Chalmers nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,14 @@+texrunner+=========++texrunner provides a (hopefully) convenient interface to Tex including+functions for running Tex, parsing log files.++The online module allows retrieving results of commands, such as+dimensions of an `hbox`, using Tex's interactive features.++![hbox dimensions recovered from texrunner](https://rawgit.com/cchalmers/texrunner/master/diagrams/hbox.svg)++This package is a work in progress and likely contains lots of bugs.+Eventually I hope it will be good enough for general use but for now+it's only suitable for simple things.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/System/Texrunner.hs view
@@ -0,0 +1,109 @@+----------------------------------------------------------------------------+-- |+-- Module      :  System.Texrunner+-- Copyright   :  (c) 2014 Christopher Chalmers+-- License     :  BSD-style (see LICENSE)+-- Maintainer  :  c.chalmers@me.com+--+-- Functions for running Tex.+--+-----------------------------------------------------------------------------++module System.Texrunner+  ( runTex+  , runTex'+  , prettyPrintLog+  ) where++import           Control.Applicative+import qualified Data.ByteString.Char8      as C8 hiding (concatMap)+import           Data.ByteString.Lazy.Char8 as LC8 hiding (concatMap)+import           Data.Maybe++import           System.Directory+import           System.Environment+import           System.Exit+import           System.FilePath+import           System.IO+import           System.IO.Temp+import           System.Process++import           System.Texrunner.Parse++-- | Same as 'runTex'' but runs Tex in a temporary system directory.+runTex :: String     -- ^ Tex command+       -> [String]   -- ^ Additional arguments+       -> [FilePath] -- ^ Additional Tex input paths+       -> ByteString -- ^ Source Tex file+       -> IO (ExitCode, TexLog, Maybe ByteString)+runTex command args extras source =+  withSystemTempDirectory "texrunner." $ \path ->+    runTex' path command args extras source++-- | Run Tex program in the given directory. Additional Tex inputs are+--   for filepaths to things like images that Tex can refer to.+runTex' :: FilePath   -- ^ Directory to run Tex in+        -> String     -- ^ Tex command+        -> [String]   -- ^ Additional arguments+        -> [FilePath] -- ^ Additional Tex inputs+        -> ByteString -- ^ Source Tex file+        -> IO (ExitCode, TexLog, Maybe ByteString)+runTex' path command args extras source = do++  LC8.writeFile (path </> "texrunner.tex") source++  environment <- extraTexInputs (path:extras) <$> getEnvironment++  let p = (proc command ("texrunner.tex" : args))+            { cwd     = Just path+            , std_in  = CreatePipe+            , std_out = CreatePipe+            , env     = Just environment+            }++  (Just inH, Just outH, _, pHandle) <- createProcess p++  hClose inH+  a <- C8.hGetContents outH -- backup log++  hClose outH+  exitC <- waitForProcess pHandle++  pdfExists <- doesFileExist (path </> "texrunner.pdf")+  pdfFile   <- if pdfExists+                  then Just <$> LC8.readFile (path </> "texrunner.pdf")+                  else return Nothing++  logExists <- doesFileExist (path </> "texrunner.log")+  logFile   <- if logExists+                  then Just <$> C8.readFile (path </> "texrunner.log")+                  else return Nothing++  -- pdfFile <- optional $ LC8.readFile (path </> "texrunner.pdf")+  -- logFile <- optional $ C8.readFile (path </> "texrunner.log")++  return (exitC, parseLog $ fromMaybe a logFile, pdfFile)++-- | Add a list of paths to the tex+extraTexInputs :: [FilePath] -> [(String,String)] -> [(String,String)]+extraTexInputs []      = id+extraTexInputs inputss = alter f "TEXINPUTS"+  where+    f Nothing  = Just inputs+    f (Just x) = Just (inputs ++ [searchPathSeparator] ++ x)+    --+    inputs = concatMap (++ [searchPathSeparator]) inputss+    -- inputs = intercalate [searchPathSeparator] inputss++-- Alter can be used to insert, delete or update an element. Similar to alter+-- in Data.Map.+alter :: Eq k => (Maybe a -> Maybe a) -> k -> [(k,a)] -> [(k,a)]+alter f k = go+  where+    go []         = maybeToList ((,) k <$> f Nothing)+    go ((k',x):xs)+      | k' == k   = case f (Just x) of+                      Just x' -> (k',x') : xs+                      Nothing -> xs+      | otherwise = (k',x) : go xs+
+ src/System/Texrunner/Online.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings          #-}++----------------------------------------------------------------------------+-- |+-- Module      :  System.Texrunner.Online+-- Copyright   :  (c) 2015 Christopher Chalmers+-- License     :  BSD-style (see LICENSE)+-- Maintainer  :  c.chalmers@me.com+--+-- Functions for running and parsing using Tex's online interface. This is+-- mostly used for getting measurements like hbox dimensions and textwidth.+--+-- Tex's online interface is basically running the command line. You can+-- see it by running @pdflatex@ without any arguments. The contents can+-- be writen line by and tex can give feedback though stdout, which gets+-- parsed in by this module. This is the only way I know to get info+-- like hbox sizes. Please let me know if you know a better way.+--+-----------------------------------------------------------------------------++module System.Texrunner.Online+  ( OnlineTex+  -- * Running Tex online+  , runOnlineTex++  , runOnlineTex'+  -- * Interaction+  , hbox+  , hsize+  , showthe+  , onlineTexParser+  , texPutStrLn++  -- * Low level+  -- | These functions allow give you direct access to the iostreams+  --   with tex. The implementation is likely to change in the future+  --   and using them directly is not recommended.+  , TexStreams+  , getInStream+  , getOutStream+  , clearUnblocking+  ) where++import           Control.Applicative+import           Control.Monad.Reader+import qualified Data.Attoparsec.ByteString   as A+import           Data.ByteString.Char8        (ByteString)+import qualified Data.ByteString.Char8        as C8+import qualified Data.ByteString.Lazy.Char8   as LC8+import           Data.List                    (find)+import           Data.Maybe+import           Data.Monoid+import qualified Data.Traversable             as T++import           System.Directory+import           System.FilePath+import           System.IO+import           System.IO.Streams            as Streams+import           System.IO.Streams.Attoparsec+import           System.IO.Temp+import           System.Process               as P (runInteractiveProcess)++import           System.Texrunner.Parse++-- | Type for dealing with Tex's pipping interface, the current streams+--   are availble though the `MonadReader` instance.+newtype OnlineTex a = OnlineTex {runOnlineTexT :: ReaderT TexStreams IO a}+  deriving (Functor, Applicative, Monad, MonadIO, MonadReader TexStreams)++-- Run a tex process, disguarding the resulting PDF.+runOnlineTex :: String      -- ^ tex command+             -> [String]    -- ^ tex command arguments+             -> ByteString  -- ^ preamble+             -> OnlineTex a -- ^ Online Tex to be Run+             -> IO a+runOnlineTex command args preamble process =+  (\(a,_,_) -> a) <$> runOnlineTex' command args preamble process++-- Run a tex process, keeping the resulting PDF. The OnlineTex must receive+-- the terminating control sequence (\bye, \end{document}, \stoptext).+runOnlineTex' :: String+              -> [String]+              -> ByteString+              -> OnlineTex a+              -> IO (a, TexLog, Maybe LC8.ByteString)+runOnlineTex' command args preamble process =+  withSystemTempDirectory "onlinetex." $ \path -> do+    (outS, inS, h) <- mkTexHandles path Nothing command args preamble+    a              <- flip runReaderT (outS, inS) . runOnlineTexT $ process++    write Nothing outS+    _ <- waitForProcess h++    -- it's normally texput.pdf but some (Context) choose random names+    pdfPath  <- find ((==".pdf") . takeExtension) <$> getDirectoryContents path+    pdfFile  <- T.mapM (LC8.readFile . (path </>)) pdfPath++    logPath  <- find ((==".log") . takeExtension) <$> getDirectoryContents path+    logFile  <- T.mapM (C8.readFile . (path </>)) logPath++    return (a, parseLog $ fromMaybe "" logFile, pdfFile)++-- | Get the dimensions of a hbox.+hbox :: Fractional n => ByteString -> OnlineTex (Box n)+hbox str = do+  clearUnblocking+  texPutStrLn $ "\\setbox0=\\hbox{" <> str <> "}\n\\showbox0\n"+  onlineTexParser parseBox++-- | Parse result from @\showthe@.+showthe :: Fractional n => ByteString -> OnlineTex n+showthe str = do+  clearUnblocking+  texPutStrLn $ "\\showthe" <> str+  onlineTexParser parseUnit++-- | Dimensions from filling the current line.+hsize :: Fractional n => OnlineTex n+hsize = boxWidth <$> hbox "\\line{\\hfill}"++-- | Run an Attoparsec parser on Tex's output.+onlineTexParser :: A.Parser a -> OnlineTex a+onlineTexParser p = getInStream >>= liftIO . parseFromStream p+  -- TODO: have a timeout++texPutStrLn :: ByteString -> OnlineTex ()+texPutStrLn a = getOutStream >>= liftIO . write (Just $ C8.append a "\n")++-- * Internal+-- These functions should be used with caution.++type TexStreams = (OutputStream ByteString, InputStream ByteString)++-- | Get the output stream to read tex's output.+getOutStream :: OnlineTex (OutputStream ByteString)+getOutStream = reader fst++-- | Get the input stream to give text to tex.+getInStream :: OnlineTex (InputStream ByteString)+getInStream = reader snd++-- | Clear any output tex has already given.+clearUnblocking :: OnlineTex ()+clearUnblocking = getInStream >>= void . liftIO . Streams.read++-- | Uses a surface to open an interface with Tex,+mkTexHandles :: FilePath+             -> Maybe [(String, String)]+             -> String+             -> [String]+             -> ByteString+             -> IO (OutputStream ByteString,+                    InputStream ByteString,+                    ProcessHandle)+mkTexHandles dir env command args preamble = do++  -- Tex doesn't send anything to stderr+  (outStream, inStream, _, h) <- runInteractiveProcess'+                                   command+                                   args+                                   (Just dir)+                                   env++  -- inStream <- debugStream inStream'++  -- commands to get Tex to play nice+  write (Just $ "\\tracingonline=1"  -- \showbox is echoed to stdout+             <> "\\showboxdepth=1"   -- show boxes one deep+             <> "\\showboxbreadth=1"+             <> "\\scrollmode\n"     -- don't pause after showing something+        ) outStream+  write (Just preamble) outStream++  return (outStream, inStream, h)++-- Adapted from io-streams. Sets input handle to line buffering.+runInteractiveProcess'+    :: FilePath                 -- ^ Filename of the executable (see 'proc' for details)+    -> [String]                 -- ^ Arguments to pass to the executable+    -> Maybe FilePath           -- ^ Optional path to the working directory+    -> Maybe [(String,String)]  -- ^ Optional environment (otherwise inherit)+    -> IO (OutputStream ByteString,+           InputStream ByteString,+           InputStream ByteString,+           ProcessHandle)+runInteractiveProcess' cmd args wd env = do+    (hin, hout, herr, ph) <- P.runInteractiveProcess cmd args wd env++    -- it is possible to flush using write (Just "") but this seems nicer+    -- is there a better way?+    hSetBuffering hin LineBuffering++    sIn  <- Streams.handleToOutputStream hin >>=+            Streams.atEndOfOutput (hClose hin) >>=+            Streams.lockingOutputStream+    sOut <- Streams.handleToInputStream hout >>=+            Streams.atEndOfInput (hClose hout) >>=+            Streams.lockingInputStream+    sErr <- Streams.handleToInputStream herr >>=+            Streams.atEndOfInput (hClose herr) >>=+            Streams.lockingInputStream++    return (sIn, sOut, sErr, ph)++-- debugStream :: InputStream ByteString -> IO (InputStream ByteString)+-- debugStream = debugInput id "tex" Streams.stdout
+ src/System/Texrunner/Parse.hs view
@@ -0,0 +1,324 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}++----------------------------------------------------------------------------+-- |+-- Module      :  System.Texrunner.Parse+-- Copyright   :  (c) 2015 Christopher Chalmers+-- License     :  BSD-style (see LICENSE)+-- Maintainer  :  c.chalmers@me.com+--+-- Functions for parsing Tex output and logs. This log is parser is+-- experimental and largely untested. Please make an issue for any logs+-- that aren't parsed properly.+--+-----------------------------------------------------------------------------++module System.Texrunner.Parse+  ( -- * Box+    Box (..)+  , parseBox+    -- * Errors+  , TexLog (..)+  , TexInfo (..)+  , TexError (..)+  , TexError' (..)+  , someError+  , badBox+  , parseUnit+  , parseLog+  , prettyPrintLog+  ) where++import           Control.Applicative+import           Data.Attoparsec.ByteString.Char8 as A+import           Data.ByteString.Char8            (ByteString, cons, pack)+import qualified Data.ByteString.Char8            as B+import           Data.Maybe+import           Data.Monoid++------------------------------------------------------------------------+-- Boxes+------------------------------------------------------------------------++-- | Data type for holding dimensions of a hbox. It is likely the+--   internal representation will change to allow nested boxes in the+--   future.+data Box n = Box+  { boxHeight :: n+  , boxDepth  :: n+  , boxWidth  :: n+  } deriving Show++int :: Parser Int+int = decimal++parseBox :: Fractional n => Parser (Box n)+parseBox = do+  A.skipWhile (/='\\') <* char '\\'+  parseSingle <|> parseBox+  where+    parseSingle = do+      _ <- "box" *> int <* "=\n\\hbox("+      h <- rational <* char '+'+      d <- rational <* ")x"+      w <- rational+      --+      return $ Box (pt2bp h) (pt2bp d) (pt2bp w)++parseUnit :: Fractional n => Parser n+parseUnit = do+  A.skipWhile (/='>') <* char '>'+  skipSpace+  fmap pt2bp rational <|> parseUnit++pt2bp :: Fractional n => n -> n+pt2bp = (/1.00374)++------------------------------------------------------------------------+-- Logs+------------------------------------------------------------------------++-- Everything's done using ByteString because io-streams' attoparsec module+-- only has a ByteString function. It's very likely this will all change to+-- Text in the future.++data TexLog = TexLog+  { texInfo   :: TexInfo+  , numPages  :: Maybe Int+  , texErrors :: [TexError]+  -- , rawLog    :: ByteString+  } deriving Show++data TexInfo = TexInfo+  { texCommand      :: Maybe ByteString+  , texVersion      :: Maybe ByteString+  , texDistribution :: Maybe ByteString+  -- , texDate    :: Maybe Date+  }+  deriving Show++-- Make shift way to parse a log by combining it in this way.+instance Monoid TexLog where+  mempty = TexLog (TexInfo Nothing Nothing Nothing) Nothing []+  TexLog prog pages1 errors1 `mappend` TexLog _ pages2 errors2 =+    case (pages1,pages2) of+      (Just a,_) -> TexLog prog (Just a) (errors1 ++ errors2)+      (_,b)      -> TexLog prog b (errors1 ++ errors2)++infoParser :: Parser TexInfo+infoParser+  = TexInfo+  <$> optional ("This is"   *> takeTill (== ',') <* anyChar)+  <*> optional (" Version " *> takeTill (== ' ') <* anyChar)+  <*> optional (char '('    *> takeTill (== ')') <* anyChar)+  -- <*> Nothing++logFile :: Parser TexLog+logFile = mconcat <$> many logLine+  where+    logLine = do+      info   <- infoParser+      pages  <- optional nPages+      errors <- maybeToList <$> optional someError+      _      <- restOfLine+      return $ TexLog info pages errors++-- thisIs :: Parser TexVersion++parseLog :: ByteString -> TexLog+parseLog = (\(Right a) -> a) . parseOnly logFile+-- the parse should never fail (I think)++prettyPrintLog :: TexLog -> ByteString+prettyPrintLog (TexLog {..}) =+  fromMaybe "unknown program" (texCommand texInfo)+  <> maybe "" (" version " <>) (texVersion texInfo)+  <> maybe "" (" " <>) (texDistribution texInfo)+  <> "\n"+  <> maybe "" ((<> "pages\n") . pack . show) numPages+  <> B.unlines (map (pack . show) texErrors)++------------------------------------------------------------------------+-- Errors+------------------------------------------------------------------------++-- | An error from tex with possible line number.+data TexError = TexError+  { errorLine :: Maybe Int+  , error'    :: TexError'+  }+  deriving Show++instance Eq TexError where+  TexError _ a == TexError _ b = a == b++-- | A subset of possible error Tex can throw.+data TexError'+  = UndefinedControlSequence ByteString+  | MissingNumber+  | Missing Char+  | IllegalUnit -- (Maybe Char) (Maybe Char)+  | PackageError String String+  | LatexError ByteString+  | BadBox ByteString+  | EmergencyStop+  | ParagraphEnded+  | TooMany ByteString+  | DimensionTooLarge+  | TooManyErrors+  | NumberTooBig+  | ExtraBrace+  | FatalError ByteString+  | UnknownError ByteString+  deriving (Show, Read, Eq)++-- Parse any line begining with "! ". Any unknown errors are returned as 'UnknownError'.+someError :: Parser TexError+someError =  mark *> errors+  where+    -- in context exclamation mark isn't always at the begining+    mark = "! " <|> (notChar '\n' *> mark)+    errors =  undefinedControlSequence+          <|> illegalUnit+          <|> missingNumber+          <|> missing+          <|> latexError+          <|> emergencyStop+          <|> extraBrace+          <|> paragraphEnded+          <|> numberTooBig+          <|> tooMany+          <|> dimentionTooLarge+          <|> tooManyErrors+          <|> fatalError+          <|> TexError Nothing <$> UnknownError <$> restOfLine++noteStar :: Parser ()+noteStar = skipSpace *> "<*>" *> skipSpace++toBeReadAgain :: Parser Char+toBeReadAgain = do+  skipSpace+  _ <- "<to be read again>"+  skipSpace+  anyChar++-- insertedText :: Parser ByteString+-- insertedText = do+--   skipSpace+--   _ <- "<inserted text>"+--   skipSpace+--   restOfLine++------------------------------------------------------------------------+-- Error parsers+------------------------------------------------------------------------++undefinedControlSequence :: Parser TexError+undefinedControlSequence = do+  _ <- "Undefined control sequence"++  _ <- optional $ do -- for context log+    skipSpace+    _ <- "system"+    let skipLines = line <|> restOfLine *> skipLines+    skipLines++  _ <- optional noteStar+  skipSpace+  l <- optional line+  skipSpace+  cs <- finalControlSequence+  return $ TexError l (UndefinedControlSequence cs)++finalControlSequence :: Parser ByteString+finalControlSequence = last <$> many1 controlSequence+  where+    controlSequence = cons '\\' <$>+      (char '\\' *> takeTill (\x -> isSpace x || x=='\\'))++illegalUnit :: Parser TexError+illegalUnit = do+  _ <- "Illegal unit of measure (pt inserted)"+  _ <- optional toBeReadAgain+  _ <- optional toBeReadAgain++  return $ TexError Nothing IllegalUnit++missingNumber :: Parser TexError+missingNumber = do+  _ <- "Missing number, treated as zero"+  _ <- optional toBeReadAgain+  _ <- optional noteStar+  return $ TexError Nothing MissingNumber++badBox :: Parser TexError+badBox = do+  s <- choice ["Underfull", "Overfull", "Tight", "Loose"]+  _ <- " \\hbox " *> char '(' *> takeTill (==')') <* char ')'+  l <- optional line+  return $ TexError l (BadBox s)++missing :: Parser TexError+missing = do+  c <- "Missing " *> anyChar <* " inserted"+  l <- optional line+  return $ TexError l (Missing c)++line :: Parser Int+line =  " detected at line " *> decimal+    <|> "l."                 *> decimal++emergencyStop :: Parser TexError+emergencyStop = "Emergency stop"+             *> return (TexError Nothing EmergencyStop)++fatalError :: Parser TexError+fatalError = TexError Nothing <$> FatalError <$> (" ==> Fatal error occurred, " *> restOfLine)++-- line 8058 tex.web+extraBrace :: Parser TexError+extraBrace = "Argument of" *> return (TexError Nothing ExtraBrace)++tooMany :: Parser TexError+tooMany = TexError Nothing <$> TooMany <$> ("Too Many " *> takeTill (=='\''))++tooManyErrors :: Parser TexError+tooManyErrors = "That makes 100 errors; please try again"+             *> return (TexError Nothing TooManyErrors)++dimentionTooLarge :: Parser TexError+dimentionTooLarge = "Dimension too large"+                 *> return (TexError Nothing DimensionTooLarge)++-- line 8075 tex.web+paragraphEnded :: Parser TexError+paragraphEnded = do+  _ <- "Paragraph ended before "+  _ <- takeTill isSpace+  _ <- toBeReadAgain+  l <- optional line+  return $ TexError l ParagraphEnded++numberTooBig :: Parser TexError+numberTooBig = "Number too big"+            *> return (TexError Nothing NumberTooBig)++-- Latex errors++latexError :: Parser TexError+latexError = TexError Nothing <$> LatexError <$> ("Latex Error: " *> restOfLine)++-- Pages++nPages :: Parser Int+nPages = "Output written on "+      *> skipWhile (/= '(') *> char '('+      *> decimal++-- Utilities++restOfLine :: Parser ByteString+restOfLine = takeTill (=='\n') <* char '\n'+
+ tests/Tests.hs view
@@ -0,0 +1,9 @@+module Main (main) where++import Test.Framework (defaultMain)+import qualified Tex.PDF+import qualified Tex.LogParse++main :: IO ()+main = defaultMain (Tex.PDF.tests ++ Tex.LogParse.tests)+
+ tests/Tex/LogParse.hs view
@@ -0,0 +1,160 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+module Tex.LogParse where++import Data.ByteString.Lazy.Char8 as B (unlines, ByteString, writeFile)+import Data.Monoid++import Test.HUnit+import Test.Framework.Providers.HUnit+import Test.Framework as F++import System.Texrunner+import System.Texrunner.Parse+import Control.Lens+import Data.Foldable++tests = texTests ++ latexTests ++ contextTests++texTests = [checkErrors "tex error parse" tex]+latexTests = [checkErrors "latex error parse" latex]+contextTests = [checkErrors "context error parse" context]++withHead :: Monad m => [a] -> (a -> m ()) -> m ()+withHead (a:_) f = f a+withHead _     _ = return ()++tex e code = testCase ("tex" ++ show e) $ do+  (exitCode, texLog, mPDF) <- runTex "pdftex" [] [] code+  take 1 (map error' (texErrors texLog)) @?= [e]++latexHeader, latexBye :: ByteString+latexHeader = B.unlines+  [ "\\documentclass{article}"+  , "\\begin{document}"+  ]+latexBye = "\\end{document}"++latex e code = testCase ("latex" ++ show e) $ do+  (exitCode, texLog, mPDF) <- runTex "pdflatex" [] [] (latexHeader <> code)+  head (map error' $ texErrors texLog) @?= e++contextHeader, contextBye :: ByteString+contextHeader = "\\starttext"+contextBye = "\\stoptext"++context e code = testCase ("context" ++ show e) $ do+  (exitCode, texLog, mPDF) <- runTex "context" [] [] (contextHeader <> code)+  take 1 (map error' (texErrors texLog)) @?= [e]+  -- head (map error' $ texErrors texLog) @?= e+  -- assertBool ("context" ++ show e) $ texLog `containsError` e++-- Generating tex sample tex files -------------------------------------++-- plain tex++genTexFiles :: IO ()+genTexFiles = for_ labeledErrors mkFile+  where+    mkFile (nm, (_err, xs)) = ifor_ xs $ \i x -> do+      let doc = latexHeader <> x <> latexBye+          name | length xs == 1 = nm+               | otherwise      = nm <> "-" <> show (i+1)+      B.writeFile ("tests/samples/tex/" <> name <> ".tex") doc++-- latex++-- pdflatex -draftmode --interaction=nonstopmode $i++genLatexFiles :: IO ()+genLatexFiles = for_ labeledErrors mkFile+  where+    mkFile (nm, (_err, xs)) = ifor_ xs $ \i x -> do+      let doc = latexHeader <> x <> latexBye+          name | length xs == 1 = nm+               | otherwise      = nm <> "-" <> show (i+1)+      B.writeFile ("tests/samples/latex/" <> name <> ".tex") doc++-- context tex++genContextFiles :: IO ()+genContextFiles = for_ labeledErrors mkFile+  where+    mkFile (nm, (_err, xs)) = ifor_ xs $ \i x -> do+      let doc = contextHeader <> x <> contextBye+          name | length xs == 1 = nm+               | otherwise      = nm <> "-" <> show (i+1)+      B.writeFile ("tests/samples/context/" <> name <> ".tex") doc++labeledErrors =+  [ ("missing-dollar", missingDollar)+  , ("dimention-too-large", dimensionTooLarge)+  , ("illegal-unit", illegalUnit)+  , ("missing-number", missingNumber)+  , ("undefined-control-sequence", undefinedControlSequence)+  ]++-- Checking error parsing ----------------------------------------------++containsError :: TexLog -> TexError -> Bool+containsError log (TexError _ err) = err `elem` map error' (texErrors log)++checkError :: (TexError' -> ByteString -> F.Test) -> (TexError', [ByteString]) -> F.Test+checkError f (e, codes) = testGroup (show e) $ map (f e) codes++checkErrors :: TestName -> (TexError' -> ByteString -> F.Test) ->  F.Test+checkErrors name f = testGroup name $ map (checkError f) texErrs++-- Sample errors -------------------------------------------------------++texErrs =+  [ missingDollar+  , dimensionTooLarge+  , illegalUnit+  , missingNumber+  , undefinedControlSequence+  ]++missingDollar = (,) (Missing '$')+  [ "$x+1=2\n\n"+  , "$$x+1=2\n\n"+  ]++dimensionTooLarge = (,) DimensionTooLarge+  [ "\\hskip100000em"+  ]++illegalUnit = (,) IllegalUnit+  [ "\\hskip1cn"+  ]++missingNumber = (,) MissingNumber+  [ "\\hskip hi"+  ]++undefinedControlSequence = (,) (UndefinedControlSequence "\\hobx")+  [ "\\hobx"+  ]++++--+-- missingDollarExample2= "x_1"+--+-- missingDollarExample3= "+--+-- numberTooBig = "10000000000"+--+-- overfull = "\\hbox to 1em{overfill box}"+--+-- underfill = "\\hbox to 20em{underfill box}"+--+-- illegalUnit = "\\hskip{1cn}"+--+-- undefinedControlSequence = "\\hobx"+--+-- missingNumber = "\\hskip"+--+--+-- missingDollarTest = (texPutStrLn missingDollarExample, MissingDollar)+--
+ tests/Tex/PDF.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+module Tex.PDF where++import Data.ByteString.Lazy.Char8 as B+import Data.Maybe+import System.Exit++import Test.HUnit+import Test.Framework.Providers.HUnit++import System.Texrunner+import System.Texrunner.Online++tests = [tex, latex, context, texOnline, latexOnline, contextOnline]+texTests = [tex, texOnline]+latexTests = [latex, latexOnline]+contextTests = [context, contextOnline]++texDocument :: ByteString+texDocument = "hi\\bye"++latexDocument :: ByteString+latexDocument = B.unlines+  [ "\\documentclass{article}"+  , "\\begin{document}"+  , "hi"+  , "\\end{document}"+  ]++contextDocument :: ByteString+contextDocument = B.unlines+  [ "\\starttext"+  , "hi"+  , "\\stoptext"+  ]++tex     = testRunTeX "pdftex" [] texDocument+latex   = testRunTeX "pdflatex" [] latexDocument+context = testRunTeX "context" ["--once"] contextDocument++testRunTeX command args document = testCase command $ do+  (exitCode, _, mPDF) <- runTex command args [] document+  exitCode @?= ExitSuccess+  assertBool "pdf found" $ isJust mPDF+++-- online++testOnlineTeX command args document = testCase (command ++ "Online") $ do+  ((), _, mPDF) <- runOnlineTex' command args "" (texPutStrLn $ toStrict document)+  assertBool "pdf found" $ isJust mPDF++texOnline     = testOnlineTeX "pdftex" [] texDocument+latexOnline   = testOnlineTeX "pdflatex" [] latexDocument+contextOnline = testOnlineTeX "context" ["--pipe"] contextDocument+++++-- tests to make:+-- * texinputs for files in cwd+-- * pdf made online+
+ texrunner.cabal view
@@ -0,0 +1,62 @@+name:                texrunner+version:             0.0.1.0+synopsis:            Functions for running Tex from Haskell.+description:+  texrunner is an interface to tex that attempts to parse errors and+  can parse tex in online mode to retrive hbox sizes.+  .+  This package should be considered very experimental. Eventually I hope+  it will be good enough for general use but for now it's only suitable+  for simple things.+license:             BSD3+license-file:        LICENSE+bug-reports:         http://github.com/cchalmers/texrunner/issues+author:              Christopher Chalmers+maintainer:          c.chalmers@me.com+copyright:           2015 Christopher Chalmers+category:            System+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10+source-repository head+  type:     git+  location: http://github.com/cchalmers/texrunner++library+  exposed-modules:+    System.Texrunner+    System.Texrunner.Online+    System.Texrunner.Parse+  build-depends:+    base       >=4.6  && <4.9,+    bytestring >=0.10 && <1.0,+    filepath,+    directory  >=1.2  && <2.0,+    temporary  >=1.2  && <2.0,+    process    >=1.2  && <2.0,+    mtl        >=2.1  && <3.0,+    attoparsec >=0.10 && <1.0,+    io-streams >=1.1  && <2.0+  hs-source-dirs: src+  ghc-options:        -Wall+  default-language:    Haskell2010++test-suite tests+  type:           exitcode-stdio-1.0+  hs-source-dirs: tests+  main-is:        Tests.hs+  ghc-options:    -Wall+  default-language: Haskell2010+  other-modules:+    Tex.PDF+    Tex.LogParse++  build-depends:+    base,+    lens,+    test-framework,+    test-framework-hunit,+    HUnit,+    bytestring,+    texrunner+