{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TemplateHaskell #-} {-| Module : $header$ Copyright : (c) Laurent P René de Cotret, 2020 License : GNU GPL, version 2 or above Maintainer : laurent.decotret@outlook.com Stability : internal Portability : portable This module defines types and functions that help with keeping track of figure specifications -} module Text.Pandoc.Filter.Plot.Parse ( plotToolkit , parseFigureSpec , captionReader ) where import Control.Monad (join, when) import Control.Monad.Reader (asks, liftIO) import Data.Default.Class (def) import Data.List (intersperse) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, listToMaybe) import Data.Monoid ((<>)) import Data.String (fromString) import Data.Text (Text, pack, unpack) import qualified Data.Text.IO as TIO import Data.Version (showVersion) import Paths_pandoc_plot (version) import System.FilePath (makeValid) import Text.Pandoc.Definition (Block (..), Inline, Pandoc (..), Format(..)) import Text.Pandoc.Class (runPure) import Text.Pandoc.Options (ReaderOptions (..)) import Text.Pandoc.Readers (getReader, Reader(..)) import Text.Pandoc.Filter.Plot.Renderers import Text.Pandoc.Filter.Plot.Types tshow :: Show a => a -> Text tshow = pack . show -- | Determine inclusion specifications from @Block@ attributes. -- If an environment is detected, but the save format is incompatible, -- an error will be thrown. parseFigureSpec :: Block -> PlotM (Maybe FigureSpec) parseFigureSpec (CodeBlock (id', classes, attrs) content) = do toolkit <- asks toolkit if not (cls toolkit `elem` classes) then return Nothing else Just <$> figureSpec where attrs' = Map.fromList attrs preamblePath = unpack <$> Map.lookup (tshow PreambleK) attrs' figureSpec :: PlotM FigureSpec figureSpec = do conf <- asks config toolkit <- asks toolkit let extraAttrs' = parseExtraAttrs toolkit attrs' header = comment toolkit $ "Generated by pandoc-plot " <> ((pack . showVersion) version) defaultPreamble = preambleSelector toolkit conf includeScript <- fromMaybe (return defaultPreamble) ((liftIO . TIO.readFile) <$> preamblePath) let -- Filtered attributes that are not relevant to pandoc-plot -- This presumes that inclusionKeys includes ALL possible keys, for all toolkits filteredAttrs = filter (\(k, _) -> k `notElem` (tshow <$> inclusionKeys)) attrs defWithSource = defaultWithSource conf defSaveFmt = defaultSaveFormat conf defDPI = defaultDPI conf let caption = Map.findWithDefault mempty (tshow CaptionK) attrs' withSource = fromMaybe defWithSource $ readBool <$> Map.lookup (tshow WithSourceK) attrs' script = mconcat $ intersperse "\n" [header, includeScript, content] saveFormat = fromMaybe defSaveFmt $ (fromString . unpack) <$> Map.lookup (tshow SaveFormatK) attrs' directory = makeValid $ unpack $ Map.findWithDefault (pack $ defaultDirectory conf) (tshow DirectoryK) attrs' dpi = fromMaybe defDPI $ (read . unpack) <$> Map.lookup (tshow DpiK) attrs' extraAttrs = Map.toList extraAttrs' blockAttrs = (id', classes, filteredAttrs) -- This is the first opportunity to check save format compatibility let saveFormatSupported = saveFormat `elem` (supportedSaveFormats toolkit) when (not saveFormatSupported) $ do (error $ mconcat ["Save format ", show saveFormat, " not supported by ", show toolkit ]) return FigureSpec{..} parseFigureSpec _ = return Nothing -- | Determine which toolkit should be used to render the plot -- from a code block, if any. plotToolkit :: Block -> Maybe Toolkit plotToolkit (CodeBlock (_, classes, _) _) = listToMaybe $ filter (\tk->cls tk `elem` classes) toolkits plotToolkit _ = Nothing -- | Reader a caption, based on input document format captionReader :: Format -> Text -> Maybe [Inline] captionReader (Format f) t = either (const Nothing) (Just . extractFromBlocks) $ runPure $ do (reader, exts) <- getReader f let readerOpts = def {readerExtensions = exts} -- Assuming no ByteString readers... case reader of TextReader fct -> fct readerOpts t _ -> return mempty where extractFromBlocks (Pandoc _ blocks) = mconcat $ extractInlines <$> blocks extractInlines (Plain inlines) = inlines extractInlines (Para inlines) = inlines extractInlines (LineBlock multiinlines) = join multiinlines extractInlines _ = [] -- | Flexible boolean parsing readBool :: Text -> Bool readBool s | s `elem` ["True", "true", "'True'", "'true'", "1"] = True | s `elem` ["False", "false", "'False'", "'false'", "0"] = False | otherwise = error $ unpack $ mconcat ["Could not parse '", s, "' into a boolean. Please use 'True' or 'False'"]