-- | The writer monad applied to 'LaTeX' values. Useful to compose 'LaTeX' values -- using the @do@ notation: -- -- > anExample :: Monad m => LaTeXT m () -- > anExample = do -- > documentclass [] article -- > author "Daniel Monad" -- > title "LaTeX and do notation" -- > document $ do -- > maketitle -- > section "Some words" -- > "Using " ; texttt "do" ; " notation " -- > "you avoid many ocurrences of the " -- > texttt "(<>)" ; " operator and a lot of " -- > "parentheses. With the cost of a monad." -- -- Since 'LaTeXT' is a monad transformer, you can do also: -- -- > anotherExample :: LaTeXT IO () -- > anotherExample = lift (readFileTex "foo") >>= verbatim -- -- -- This way, it is easy (without carrying arguments) to include IO outputs -- in the LaTeX document, like files, times or random objects. -- -- Another approach could be to have custom counters, label management -- or any other user-defined feature. -- -- Of course, you can always use the simpler interface provided by the plain 'LaTeX' type. -- -- Another thing you should know about the LaTeX Writer Monad. Don't try to get values -- from computations with no results (like @raw "foo"@). module Text.LaTeX.Base.Writer ( -- * @LaTeXT@ writer LaTeXT , LaTeXT_ , runLaTeXT , execLaTeXT , execLaTeXTWarn , extractLaTeX , extractLaTeX_ , textell , rendertexM , liftFun , liftOp -- * Errors , throwError , merror -- * Re-export , lift , liftIO ) where import Control.Monad.Trans.Writer import Control.Monad.IO.Class import Control.Monad.Trans.Class import Control.Applicative import Control.Arrow import Data.String import Data.Monoid -- import Text.LaTeX.Base.Syntax import Text.LaTeX.Base.Class import Text.LaTeX.Base.Render import Text.LaTeX.Base.Warnings (Warning,checkAll,check) -- import Control.Monad (liftM) -- | 'WriterT' monad transformer applied to 'LaTeX' values. newtype LaTeXT m a = LaTeXT { unwrapLaTeXT :: WriterT LaTeX m (a,Maybe String) } instance Functor f => Functor (LaTeXT f) where fmap f = LaTeXT . fmap (first f) . unwrapLaTeXT -- | Pair a value with 'Nothing'. pairNoth :: a -> (a,Maybe b) pairNoth x = (x,Nothing) instance Applicative f => Applicative (LaTeXT f) where pure = LaTeXT . pure . pairNoth (LaTeXT f) <*> (LaTeXT x) = LaTeXT $ fmap (first . fst) f <*> x -- | Type synonym for empty 'LaTeXT' computations. type LaTeXT_ m = LaTeXT m () instance MonadTrans LaTeXT where lift = LaTeXT . liftM pairNoth . lift instance Monad m => Monad (LaTeXT m) where return = LaTeXT . return . pairNoth (LaTeXT c) >>= f = LaTeXT $ do (a,_) <- c let LaTeXT c' = f a c' fail = throwError instance MonadIO m => MonadIO (LaTeXT m) where liftIO = lift . liftIO instance Monad m => LaTeXC (LaTeXT m a) where liftListL f xs = mapM extractLaTeX_ xs >>= merror "liftListL" . textell . f -- | Running a 'LaTeXT' computation returns the final 'LaTeX' value -- and either a 'String' if the computation didn't contain any value -- or the value itself otherwise. runLaTeXT :: Monad m => LaTeXT m a -> m (Either String a,LaTeX) runLaTeXT (LaTeXT c) = runWriterT c >>= ( \((a,m),l) -> case m of Nothing -> return (Right a ,l) Just err -> return (Left err,l) ) -- | This is the usual way to run the 'LaTeXT' monad -- and obtain a 'LaTeX' value. -- -- > execLaTeXT = liftM snd . runLaTeXT -- -- If @anExample@ is defined as above (at the top of this module -- documentation), use the following to get the LaTeX value -- generated out. -- -- > myLaTeX :: Monad m => m LaTeX -- > myLaTeX = execLaTeXT anExample -- execLaTeXT :: Monad m => LaTeXT m a -> m LaTeX execLaTeXT = liftM snd . runLaTeXT -- | Version of 'execLaTeXT' with possible warning messages. -- This function applies 'checkAll' to the 'LaTeX' output. execLaTeXTWarn :: Monad m => LaTeXT m a -> m (LaTeX,[Warning]) execLaTeXTWarn = liftM (id &&& check checkAll) . execLaTeXT -- | This function run a 'LaTeXT' computation, -- lifting the result again in the monad. extractLaTeX :: Monad m => LaTeXT m a -> LaTeXT m (a,LaTeX) extractLaTeX (LaTeXT c) = LaTeXT $ do ((a,m),l) <- lift $ runWriterT c return ((a,l),m) -- | Executes a 'LaTeXT' computation, embedding it again in -- the 'LaTeXT' monad. -- -- > extractLaTeX_ = liftM snd . extractLaTeX -- -- This function was heavily used in the past by HaTeX-meta -- to generate those @.Monad@ modules. The current purpose -- is to implement the 'LaTeXC' instance of 'LaTeXT', which -- is closely related. extractLaTeX_ :: Monad m => LaTeXT m a -> LaTeXT m LaTeX extractLaTeX_ = liftM snd . extractLaTeX -- | With 'textell' you can append 'LaTeX' values to the -- state of the 'LaTeXT' monad. textell :: Monad m => LaTeX -> LaTeXT m () textell = LaTeXT . liftM pairNoth . tell -- | Lift a function over 'LaTeX' values to a function -- acting over the state of a 'LaTeXT' computation. liftFun :: Monad m => (LaTeX -> LaTeX) -> (LaTeXT m a -> LaTeXT m a) liftFun f (LaTeXT c) = LaTeXT $ do (p,l) <- lift $ runWriterT c tell $ f l return p -- | Lift an operator over 'LaTeX' values to an operator -- acting over the state of two 'LaTeXT' computations. -- -- /Note: The returned value is the one returned by the/ -- /second argument of the lifted operator./ liftOp :: Monad m => (LaTeX -> LaTeX -> LaTeX) -> (LaTeXT m a -> LaTeXT m b -> LaTeXT m b) liftOp op (LaTeXT c) (LaTeXT c') = LaTeXT $ do (_,l) <- lift $ runWriterT c (p,l') <- lift $ runWriterT c' tell $ l `op` l' return p -- | Just like 'rendertex', but with 'LaTeXT' output. -- -- > rendertexM = textell . rendertex rendertexM :: (Render a, Monad m) => a -> LaTeXT m () rendertexM = textell . rendertex -- Error throwing -- | The 'fail' method of the 'LaTeXT' monad. throwError :: Monad m => String -> LaTeXT m a throwError = LaTeXT . return . (error &&& Just) -- | Function 'merror' casts a value contained in a monad @m@ to the -- bottom value of another type. If you try to evaluate this value, you will -- get an error message with the 'String' passed as argument to 'merror'. merror :: Monad m => String -> LaTeXT m a -> LaTeXT m b merror = flip (>>) . throwError -- Overloaded Strings -- | Be careful when using 'fromString' over a 'LaTeXT' value, -- the returned value of the computation is bottom (i.e. 'undefined'). instance Monad m => IsString (LaTeXT m a) where fromString = merror "LaTeXT: fromString!" . textell . fromString -- | 'mappend' @=@ '>>'. instance Monad m => Monoid (LaTeXT m a) where mempty = throwError "LaTeXT: mempty!" mappend = (>>)