patat-0.10.0.0: lib/Patat/Presentation/Comments.hs
--------------------------------------------------------------------------------
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Patat.Presentation.Comments
( Comment (..)
, parse
, remove
, split
, partition
, SpeakerNotes
, speakerNotesToText
, SpeakerNotesHandle
, withSpeakerNotesHandle
, writeSpeakerNotes
, parseSlideSettings
) where
--------------------------------------------------------------------------------
import Control.Applicative ((<|>))
import Control.Exception (bracket)
import Control.Monad (unless, when)
import Data.Function (on)
import qualified Data.IORef as IORef
import Data.List (intercalate, intersperse)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import qualified Data.Text.IO as T
import qualified Data.Yaml as Yaml
import Patat.EncodingFallback (EncodingFallback)
import qualified Patat.EncodingFallback as EncodingFallback
import Patat.Presentation.Settings
import System.Directory (removeFile)
import qualified System.IO as IO
import qualified Text.Pandoc as Pandoc
--------------------------------------------------------------------------------
data Comment = Comment
{ cSpeakerNotes :: SpeakerNotes
, cConfig :: Either String PresentationSettings
} deriving (Show)
--------------------------------------------------------------------------------
instance Semigroup Comment where
l <> r = Comment
{ cSpeakerNotes = on (<>) cSpeakerNotes l r
, cConfig = case (cConfig l, cConfig r) of
(Left err, _ ) -> Left err
(Right _, Left err) -> Left err
(Right x, Right y ) -> Right (x <> y)
}
--------------------------------------------------------------------------------
instance Monoid Comment where
mappend = (<>)
mempty = Comment mempty (Right mempty)
--------------------------------------------------------------------------------
parse :: Pandoc.Block -> Maybe Comment
parse (Pandoc.RawBlock "html" t0) =
(do
t1 <- T.stripPrefix "<!--config:" t0
t2 <- T.stripSuffix "-->" t1
pure . Comment mempty $ case Yaml.decodeEither' (T.encodeUtf8 t2) of
Left err -> Left (show err)
Right obj -> Right obj) <|>
(do
t1 <- T.stripPrefix "<!--" t0
t2 <- T.stripSuffix "-->" t1
pure $ Comment (SpeakerNotes [T.strip t2]) (Right mempty))
parse _ = Nothing
--------------------------------------------------------------------------------
remove :: [Pandoc.Block] -> [Pandoc.Block]
remove = snd . partition
--------------------------------------------------------------------------------
-- | Take all comments from the front of the list. Return those and the
-- remaining blocks.
split :: [Pandoc.Block] -> (Comment, [Pandoc.Block])
split = go []
where
go sn [] = (mconcat (reverse sn), [])
go sn (x : xs) | Just s <- parse x = go (s : sn) xs
go sn xs = (mconcat (reverse sn), xs)
--------------------------------------------------------------------------------
-- | Partition the list into speaker notes and other blocks.
partition :: [Pandoc.Block] -> (Comment, [Pandoc.Block])
partition = go [] []
where
go sn bs [] = (mconcat (reverse sn), reverse bs)
go sn bs (x : xs) | Just s <- parse x = go (s : sn) bs xs
go sn bs (x : xs) = go sn (x : bs) xs
--------------------------------------------------------------------------------
newtype SpeakerNotes = SpeakerNotes [T.Text]
deriving (Eq, Monoid, Semigroup, Show)
--------------------------------------------------------------------------------
speakerNotesToText :: SpeakerNotes -> T.Text
speakerNotesToText (SpeakerNotes sn) = T.unlines $ intersperse mempty sn
--------------------------------------------------------------------------------
data SpeakerNotesHandle = SpeakerNotesHandle
{ snhSettings :: !SpeakerNotesSettings
, snhActive :: !(IORef.IORef SpeakerNotes)
}
--------------------------------------------------------------------------------
withSpeakerNotesHandle
:: SpeakerNotesSettings -> (SpeakerNotesHandle -> IO a) -> IO a
withSpeakerNotesHandle settings = bracket
(SpeakerNotesHandle settings <$> IORef.newIORef mempty)
(\_ -> removeFile (snsFile settings))
--------------------------------------------------------------------------------
writeSpeakerNotes
:: SpeakerNotesHandle -> EncodingFallback -> SpeakerNotes -> IO ()
writeSpeakerNotes h encodingFallback sn = do
change <- IORef.atomicModifyIORef' (snhActive h) $ \old -> (sn, old /= sn)
when change $ IO.withFile (snsFile $ snhSettings h) IO.WriteMode $ \ioh ->
EncodingFallback.withHandle ioh encodingFallback $
T.hPutStr ioh $ speakerNotesToText sn
--------------------------------------------------------------------------------
data Setting where
Setting :: String -> (PresentationSettings -> Maybe a) -> Setting
--------------------------------------------------------------------------------
unsupportedSlideSettings :: [Setting]
unsupportedSlideSettings =
[ Setting "incrementalLists" psIncrementalLists
, Setting "autoAdvanceDelay" psAutoAdvanceDelay
, Setting "slideLevel" psSlideLevel
, Setting "pandocExtensions" psPandocExtensions
, Setting "images" psImages
, Setting "eval" psEval
, Setting "speakerNotes" psSpeakerNotes
]
--------------------------------------------------------------------------------
parseSlideSettings :: Comment -> Either String PresentationSettings
parseSlideSettings c = do
settings <- cConfig c
let unsupported = do
setting <- unsupportedSlideSettings
case setting of
Setting name f | Just _ <- f settings -> [name]
Setting _ _ -> []
unless (null unsupported) $ Left $
"the following settings are not supported in slide config blocks: " ++
intercalate ", " unsupported
pure settings