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 +30/−0
- README.md +14/−0
- Setup.hs +2/−0
- src/System/Texrunner.hs +109/−0
- src/System/Texrunner/Online.hs +207/−0
- src/System/Texrunner/Parse.hs +324/−0
- tests/Tests.hs +9/−0
- tests/Tex/LogParse.hs +160/−0
- tests/Tex/PDF.hs +64/−0
- texrunner.cabal +62/−0
+ 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.++++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+