pandoc-plot 1.1.1 → 1.2.0
raw patch · 20 files changed
+321/−152 lines, 20 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Text.Pandoc.Filter.Plot.Internal: [envFormat] :: RuntimeEnv -> Maybe Format
- Text.Pandoc.Filter.Plot.Internal: RuntimeEnv :: Configuration -> Logger -> FilePath -> RuntimeEnv
+ Text.Pandoc.Filter.Plot.Internal: RuntimeEnv :: Maybe Format -> Configuration -> Logger -> FilePath -> RuntimeEnv
- Text.Pandoc.Filter.Plot.Internal: debug :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: debug :: (MonadLogger m, MonadIO m) => Text -> m ()
- Text.Pandoc.Filter.Plot.Internal: err :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: err :: (MonadLogger m, MonadIO m) => Text -> m ()
- Text.Pandoc.Filter.Plot.Internal: info :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: info :: (MonadLogger m, MonadIO m) => Text -> m ()
- Text.Pandoc.Filter.Plot.Internal: runPlotM :: Configuration -> PlotM a -> IO a
+ Text.Pandoc.Filter.Plot.Internal: runPlotM :: Maybe Format -> Configuration -> PlotM a -> IO a
- Text.Pandoc.Filter.Plot.Internal: type PlotM a = StateT PlotState (ReaderT RuntimeEnv IO) a
+ Text.Pandoc.Filter.Plot.Internal: type PlotM = StateT PlotState (ReaderT RuntimeEnv IO)
- Text.Pandoc.Filter.Plot.Internal: warning :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: warning :: (MonadLogger m, MonadIO m) => Text -> m ()
Files
- CHANGELOG.md +15/−0
- README.md +10/−0
- executable/ExampleConfig.hs +9/−2
- executable/Main.hs +38/−16
- executable/ManPage.hs +6/−0
- pandoc-plot.cabal +1/−1
- src/Text/Pandoc/Filter/Plot.hs +10/−16
- src/Text/Pandoc/Filter/Plot/Clean.hs +2/−2
- src/Text/Pandoc/Filter/Plot/Configuration.hs +38/−38
- src/Text/Pandoc/Filter/Plot/Embed.hs +34/−2
- src/Text/Pandoc/Filter/Plot/Monad.hs +44/−35
- src/Text/Pandoc/Filter/Plot/Monad/Logging.hs +57/−14
- src/Text/Pandoc/Filter/Plot/Monad/Types.hs +2/−2
- src/Text/Pandoc/Filter/Plot/Renderers.hs +6/−4
- src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs +2/−1
- src/Text/Pandoc/Filter/Plot/Scripting.hs +2/−3
- src/Text/Pandoc/Filter/Plot/Scripting/Template.hs +7/−1
- stack.yaml +3/−3
- tests/Common.hs +34/−12
- tests/Main.hs +1/−0
CHANGELOG.md view
@@ -2,6 +2,21 @@ pandoc-plot uses [Semantic Versioning](http://semver.org/spec/v2.0.0.html) +Release 1.2.0 +------------- + +* Fixed an issue where code blocks nested in other structures were detected properly. For example, in the following LaTeX snippet, plots would not be detected properly: +```latex +\begin{column} + \begin{minted}[]{matplotlib} + ... + \end{minted} +\end{column} +``` +Nested figures are not correctly identified. + +* The executables are now built with Pandoc 2.13. Pandoc 2.11 and Pandoc 2.12 are still supported. + Release 1.1.1 -------------
README.md view
@@ -97,6 +97,16 @@ conda install -c conda-forge pandoc-plot ``` +### Homebrew + +`pandoc-plot` is available as a package via [`Homebrew`](https://brew.sh/). [Click here to see the package page](https://formulae.brew.sh/formula/pandoc-plot#default). + +To install: + +```sh +brew install pandoc-plot +``` + ### winget You can install `pandoc-plot` from the [Windows Package
executable/ExampleConfig.hs view
@@ -2,16 +2,23 @@ module ExampleConfig (embedExampleConfig) where -import Data.String +import Data.Functor ((<&>)) +import Data.String (IsString (fromString)) import Data.Text (unpack) import qualified Data.Text.IO as TIO import Language.Haskell.TH.Syntax + ( Exp (AppE, LitE, VarE), + Lit (StringL), + Q, + Quasi (qAddDependentFile), + runIO, + ) docFile :: FilePath docFile = "example-config.yml" readDocFile :: IO String -readDocFile = TIO.readFile docFile >>= return . unpack +readDocFile = TIO.readFile docFile <&> unpack embedExampleConfig :: Q Exp embedExampleConfig = do
executable/Main.hs view
@@ -7,7 +7,7 @@ module Main where -import Control.Monad (join, msum, when) +import Control.Monad (join, msum, void, when) import Data.List (intersperse, (\\)) import Data.Maybe (fromJust) import Data.Text (unpack) @@ -16,10 +16,32 @@ import qualified Data.Version as V import ExampleConfig (embedExampleConfig) import GHC.IO.Encoding (setLocaleEncoding, utf8) -import GitHash as Git +import GitHash as Git (giHash, tGitInfoCwdTry) import ManPage (embedManualHtml) import OpenFile (openFile) import Options.Applicative + ( Alternative ((<|>)), + Parser, + command, + execParser, + flag, + footerDoc, + fullDesc, + header, + help, + helper, + info, + long, + metavar, + optional, + progDesc, + short, + strArgument, + strOption, + subparser, + value, + (<**>), + ) import qualified Options.Applicative.Help.Pretty as P import System.Directory (doesFileExist, getTemporaryDirectory) import System.Environment (lookupEnv) @@ -52,6 +74,10 @@ -- The difference between commands and flags is that commands require knowledge of -- the configuration, while flags only display static information. +-- Please note that for some reason, makeVersion [2, 11, 0, 0] > makeVersion [2, 11] +minimumPandocVersion :: V.Version +minimumPandocVersion = V.makeVersion [2, 11] + data Command = Clean (Maybe FilePath) FilePath | WriteConfig FilePath @@ -198,17 +224,15 @@ -- indicates whether the Pandoc version is new enough or not. checkRuntimePandocVersion :: IO Bool checkRuntimePandocVersion = do - -- Please note that for some reason, makeVersion [2, 11, 0, 0] > makeVersion [2, 11] - let minimumPandocVersion = V.makeVersion [2, 11] - -- Pandoc runs filters in an environment with two variables: -- PANDOV_VERSION and PANDOC_READER_OPTS - -- We can use the former to ensure that people are not using pandoc < 2.11 + -- We can use the former to ensure that people are not + -- using an old version of pandoc pandocV <- lookupEnv "PANDOC_VERSION" case pandocV >>= readVersion of Nothing -> return True Just v -> - if (v < minimumPandocVersion) + if v < minimumPandocVersion then do hPutStrLn stderr $ mconcat @@ -234,12 +258,12 @@ showFullVersion :: IO () showFullVersion = do - putStrLn $ "pandoc-plot " <> (V.showVersion pandocPlotVersion) + putStrLn $ "pandoc-plot " <> V.showVersion pandocPlotVersion putStrLn $ "Git revision " <> gitrev putStrLn $ mconcat [ "Compiled with pandoc ", - (unpack pandocVersion), + unpack pandocVersion, " and pandoc-types ", V.showVersion pandocTypesVersion, " using GHC ", @@ -252,23 +276,21 @@ showAvailableToolkits :: Maybe FilePath -> IO () showAvailableToolkits mfp = do - c <- case mfp of - Nothing -> localConfig - Just fp -> configuration fp + c <- maybe localConfig configuration mfp putStrLn "\nAVAILABLE TOOLKITS\n" available <- availableToolkits c - return available >>= mapM_ (availToolkitInfo c) + mapM_ (availToolkitInfo c) available putStrLn "\nUNAVAILABLE TOOLKITS\n" -- We don't use unavailableToolkits because this would force -- more IO actions let unavailable = toolkits \\ available - return unavailable >>= mapM_ (unavailToolkitInfo c) + mapM_ (unavailToolkitInfo c) unavailable where toolkitInfo avail conf tk = do putStrLn $ "Toolkit: " <> show tk when avail $ do - Executable dir exe <- fmap fromJust $ runPlotM conf $ executable tk + Executable dir exe <- fmap fromJust $ runPlotM Nothing conf $ executable tk putStrLn $ " Executable: " <> (dir </> unpack exe) putStrLn $ " Code block trigger: " <> (unpack . cls $ tk) putStrLn $ " Supported save formats: " <> (mconcat . intersperse ", " . fmap show $ supportedSaveFormats tk) @@ -291,7 +313,7 @@ -- (3) local .pandoc-plot.yml -- (4) default config conf <- maybe localConfig configuration $ firstJusts [configurationPathMeta doc, mfp] - cleanOutputDirs conf doc >> return () + void (cleanOutputDirs conf doc) where firstJusts :: [Maybe a] -> Maybe a firstJusts = msum
executable/ManPage.hs view
@@ -11,6 +11,12 @@ import Data.Text (unpack) import qualified Data.Text.IO as TIO import Language.Haskell.TH.Syntax + ( Exp (AppE, LitE, VarE), + Lit (StringL), + Q, + Quasi (qAddDependentFile), + runIO, + ) import System.Directory (doesFileExist) import System.FilePath ((</>))
pandoc-plot.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: pandoc-plot -version: 1.1.1 +version: 1.2.0 synopsis: A Pandoc filter to include figures generated from code blocks using your plotting toolkit of choice. description: A Pandoc filter to include figures generated from code blocks. Keep the document and code in the same location. Output is
src/Text/Pandoc/Filter/Plot.hs view
@@ -16,21 +16,9 @@ -- code blocks, using a multitude of plotting toolkits. -- -- The syntax for code blocks is simple. Code blocks with the appropriate class --- attribute will trigger the filter: --- --- * @matplotlib@ for matplotlib-based Python plots; --- * @plotly_python@ for Plotly-based Python plots; --- * @plotly_r@ for Plotly-based R plots; --- * @matlabplot@ for MATLAB plots; --- * @mathplot@ for Mathematica plots; --- * @octaveplot@ for GNU Octave plots; --- * @ggplot2@ for ggplot2-based R plots; --- * @gnuplot@ for gnuplot plots; --- * @graphviz@ for Graphviz graphs; --- * @bokeh@ for Bokeh-based Python plots; --- * @plotsjl@ for Plots.jl-based Julia plots; +-- attribute will trigger the filter, e.g. @matplotlib@ for matplotlib-based Python plots. -- --- For example, in Markdown: +-- Here is an example, in Markdown, for a plot in MATLAB: -- -- @ -- This is a paragraph. @@ -145,6 +133,7 @@ unavailableToolkits, whenStrict, ) +import Text.Pandoc.Walk (walkM) -- | Walk over an entire Pandoc document, transforming appropriate code blocks -- into figures. This function will operate on blocks in parallel if possible. @@ -160,7 +149,8 @@ IO Pandoc plotTransform conf (Pandoc meta blocks) = do maxproc <- getNumCapabilities - runPlotM conf $ do + -- TODO: make filter aware of target format + runPlotM Nothing conf $ do debug $ mconcat ["Starting a new run, utilizing at most ", pack . show $ maxproc, " processes."] mapConcurrentlyN maxproc make blocks <&> Pandoc meta @@ -172,8 +162,10 @@ -- | Try to process the block with `pandoc-plot`. If a failure happens (or the block) -- was not meant to become a figure, return the block as-is unless running in strict mode. +-- +-- New in version 1.2.0: this function will detect nested code blocks, for example in @Div@ blocks. make :: Block -> PlotM Block -make blk = either (onError blk) return =<< makeEither blk +make = walkM $ \blk -> either (onError blk) return =<< makeEither blk where onError :: Block -> PandocPlotError -> PlotM Block onError b e = do @@ -181,6 +173,8 @@ return b -- | Try to process the block with `pandoc-plot`, documenting the error. +-- This function does not transform code blocks nested in +-- other blocks (e.g. @Divs@) makeEither :: Block -> PlotM (Either PandocPlotError Block) makeEither block = parseFigureSpec block
src/Text/Pandoc/Filter/Plot/Clean.hs view
@@ -29,7 +29,7 @@ import System.Directory (removePathForcibly) import System.FilePath (takeExtension) import Text.Pandoc.Class (runIO) -import Text.Pandoc.Definition +import Text.Pandoc.Definition (Block, Pandoc) import Text.Pandoc.Error (handleError) import Text.Pandoc.Filter.Plot.Monad import Text.Pandoc.Filter.Plot.Parse @@ -48,7 +48,7 @@ b -> IO [FilePath] cleanOutputDirs conf doc = do - dirs <- runPlotM conf . cleanOutputDirsM $ doc + dirs <- runPlotM Nothing conf . cleanOutputDirsM $ doc -- Deletion of the log file must be done outside of PlotM -- to ensure the log file has been closed. case logSink conf of
src/Text/Pandoc/Filter/Plot/Configuration.hs view
@@ -213,89 +213,89 @@ instance FromJSON LoggingPrecursor where parseJSON (Object v) = - LoggingPrecursor <$> v .:? "verbosity" .!= (logVerbosity defaultConfiguration) + LoggingPrecursor <$> v .:? "verbosity" .!= logVerbosity defaultConfiguration <*> v .:? "filepath" parseJSON _ = fail $ mconcat ["Could not parse logging configuration. "] instance FromJSON MatplotlibPrecursor where parseJSON (Object v) = MatplotlibPrecursor - <$> v .:? (tshow PreambleK) - <*> v .:? (tshow MatplotlibTightBBoxK) .!= (matplotlibTightBBox defaultConfiguration) - <*> v .:? (tshow MatplotlibTransparentK) .!= (matplotlibTransparent defaultConfiguration) - <*> v .:? (tshow ExecutableK) .!= (matplotlibExe defaultConfiguration) - <*> v .:? (tshow CommandLineArgsK) .!= (matplotlibCmdArgs defaultConfiguration) + <$> v .:? tshow PreambleK + <*> v .:? tshow MatplotlibTightBBoxK .!= matplotlibTightBBox defaultConfiguration + <*> v .:? tshow MatplotlibTransparentK .!= matplotlibTransparent defaultConfiguration + <*> v .:? tshow ExecutableK .!= matplotlibExe defaultConfiguration + <*> v .:? tshow CommandLineArgsK .!= matplotlibCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Matplotlib, " configuration."] instance FromJSON MatlabPrecursor where - parseJSON (Object v) = MatlabPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (matlabExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (matlabCmdArgs defaultConfiguration) + parseJSON (Object v) = MatlabPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= matlabExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= matlabCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Matlab, " configuration."] instance FromJSON PlotlyPythonPrecursor where - parseJSON (Object v) = PlotlyPythonPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyPythonExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (plotlyPythonCmdArgs defaultConfiguration) + parseJSON (Object v) = PlotlyPythonPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= plotlyPythonExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= plotlyPythonCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show PlotlyPython, " configuration."] instance FromJSON PlotlyRPrecursor where - parseJSON (Object v) = PlotlyRPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyRExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (plotlyRCmdArgs defaultConfiguration) + parseJSON (Object v) = PlotlyRPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= plotlyRExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= plotlyRCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show PlotlyR, " configuration."] instance FromJSON MathematicaPrecursor where - parseJSON (Object v) = MathematicaPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (mathematicaExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (mathematicaCmdArgs defaultConfiguration) + parseJSON (Object v) = MathematicaPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= mathematicaExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= mathematicaCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Mathematica, " configuration."] instance FromJSON OctavePrecursor where - parseJSON (Object v) = OctavePrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (octaveExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (octaveCmdArgs defaultConfiguration) + parseJSON (Object v) = OctavePrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= octaveExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= octaveCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Octave, " configuration."] instance FromJSON GGPlot2Precursor where - parseJSON (Object v) = GGPlot2Precursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (ggplot2Exe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (ggplot2CmdArgs defaultConfiguration) + parseJSON (Object v) = GGPlot2Precursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= ggplot2Exe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= ggplot2CmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show GGPlot2, " configuration."] instance FromJSON GNUPlotPrecursor where - parseJSON (Object v) = GNUPlotPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (gnuplotExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (gnuplotCmdArgs defaultConfiguration) + parseJSON (Object v) = GNUPlotPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= gnuplotExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= gnuplotCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show GNUPlot, " configuration."] instance FromJSON GraphvizPrecursor where - parseJSON (Object v) = GraphvizPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (graphvizExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (graphvizCmdArgs defaultConfiguration) + parseJSON (Object v) = GraphvizPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= graphvizExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= graphvizCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Graphviz, " configuration."] instance FromJSON BokehPrecursor where - parseJSON (Object v) = BokehPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (bokehExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (bokehCmdArgs defaultConfiguration) + parseJSON (Object v) = BokehPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= bokehExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= bokehCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Bokeh, " configuration."] instance FromJSON PlotsjlPrecursor where - parseJSON (Object v) = PlotsjlPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotsjlExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (plotsjlCmdArgs defaultConfiguration) + parseJSON (Object v) = PlotsjlPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= plotsjlExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= plotsjlCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Plotsjl, " configuration."] instance FromJSON PlantUMLPrecursor where - parseJSON (Object v) = PlantUMLPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plantumlExe defaultConfiguration) <*> v .:? (tshow CommandLineArgsK) .!= (plantumlCmdArgs defaultConfiguration) + parseJSON (Object v) = PlantUMLPrecursor <$> v .:? tshow PreambleK <*> v .:? tshow ExecutableK .!= plantumlExe defaultConfiguration <*> v .:? tshow CommandLineArgsK .!= plantumlCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show PlantUML, " configuration."] instance FromJSON ConfigPrecursor where - parseJSON (Null) = return defaultConfigPrecursor -- In case of empty file + parseJSON Null = return defaultConfigPrecursor -- In case of empty file parseJSON (Object v) = do - _defaultDirectory <- v .:? (tshow DirectoryK) .!= (_defaultDirectory defaultConfigPrecursor) - _defaultWithSource <- v .:? (tshow WithSourceK) .!= (_defaultWithSource defaultConfigPrecursor) - _defaultDPI <- v .:? (tshow DpiK) .!= (_defaultDPI defaultConfigPrecursor) - _defaultSaveFormat <- v .:? (tshow SaveFormatK) .!= (_defaultSaveFormat defaultConfigPrecursor) - _defaultDependencies <- v .:? (tshow DependenciesK) .!= (_defaultDependencies defaultConfigPrecursor) - _captionFormat <- v .:? (tshow CaptionFormatK) .!= (_captionFormat defaultConfigPrecursor) - _sourceCodeLabel <- v .:? (tshow SourceCodeLabelK) .!= (_sourceCodeLabel defaultConfigPrecursor) - _strictMode <- v .:? tshow StrictModeK .!= (_strictMode defaultConfigPrecursor) + _defaultDirectory <- v .:? tshow DirectoryK .!= _defaultDirectory defaultConfigPrecursor + _defaultWithSource <- v .:? tshow WithSourceK .!= _defaultWithSource defaultConfigPrecursor + _defaultDPI <- v .:? tshow DpiK .!= _defaultDPI defaultConfigPrecursor + _defaultSaveFormat <- v .:? tshow SaveFormatK .!= _defaultSaveFormat defaultConfigPrecursor + _defaultDependencies <- v .:? tshow DependenciesK .!= _defaultDependencies defaultConfigPrecursor + _captionFormat <- v .:? tshow CaptionFormatK .!= _captionFormat defaultConfigPrecursor + _sourceCodeLabel <- v .:? tshow SourceCodeLabelK .!= _sourceCodeLabel defaultConfigPrecursor + _strictMode <- v .:? tshow StrictModeK .!= _strictMode defaultConfigPrecursor _logPrec <- v .:? "logging" .!= _logPrec defaultConfigPrecursor - _matplotlibPrec <- v .:? (cls Matplotlib) .!= _matplotlibPrec defaultConfigPrecursor - _matlabPrec <- v .:? (cls Matlab) .!= _matlabPrec defaultConfigPrecursor - _plotlyPythonPrec <- v .:? (cls PlotlyPython) .!= _plotlyPythonPrec defaultConfigPrecursor - _plotlyRPrec <- v .:? (cls PlotlyR) .!= _plotlyRPrec defaultConfigPrecursor - _mathematicaPrec <- v .:? (cls Mathematica) .!= _mathematicaPrec defaultConfigPrecursor - _octavePrec <- v .:? (cls Octave) .!= _octavePrec defaultConfigPrecursor - _ggplot2Prec <- v .:? (cls GGPlot2) .!= _ggplot2Prec defaultConfigPrecursor - _gnuplotPrec <- v .:? (cls GNUPlot) .!= _gnuplotPrec defaultConfigPrecursor - _graphvizPrec <- v .:? (cls Graphviz) .!= _graphvizPrec defaultConfigPrecursor - _bokehPrec <- v .:? (cls Bokeh) .!= _bokehPrec defaultConfigPrecursor - _plotsjlPrec <- v .:? (cls Plotsjl) .!= _plotsjlPrec defaultConfigPrecursor - _plantumlPrec <- v .:? (cls PlantUML) .!= _plantumlPrec defaultConfigPrecursor + _matplotlibPrec <- v .:? cls Matplotlib .!= _matplotlibPrec defaultConfigPrecursor + _matlabPrec <- v .:? cls Matlab .!= _matlabPrec defaultConfigPrecursor + _plotlyPythonPrec <- v .:? cls PlotlyPython .!= _plotlyPythonPrec defaultConfigPrecursor + _plotlyRPrec <- v .:? cls PlotlyR .!= _plotlyRPrec defaultConfigPrecursor + _mathematicaPrec <- v .:? cls Mathematica .!= _mathematicaPrec defaultConfigPrecursor + _octavePrec <- v .:? cls Octave .!= _octavePrec defaultConfigPrecursor + _ggplot2Prec <- v .:? cls GGPlot2 .!= _ggplot2Prec defaultConfigPrecursor + _gnuplotPrec <- v .:? cls GNUPlot .!= _gnuplotPrec defaultConfigPrecursor + _graphvizPrec <- v .:? cls Graphviz .!= _graphvizPrec defaultConfigPrecursor + _bokehPrec <- v .:? cls Bokeh .!= _bokehPrec defaultConfigPrecursor + _plotsjlPrec <- v .:? cls Plotsjl .!= _plotsjlPrec defaultConfigPrecursor + _plantumlPrec <- v .:? cls PlantUML .!= _plantumlPrec defaultConfigPrecursor return $ ConfigPrecursor {..} parseJSON _ = fail "Could not parse configuration."
src/Text/Pandoc/Filter/Plot/Embed.hs view
@@ -22,6 +22,16 @@ import Data.Text (Text, pack) import qualified Data.Text.IO as T import Text.HTML.TagSoup + ( Attribute, + Tag (TagClose, TagOpen), + canonicalizeTags, + parseOptionsFast, + parseTagsOptions, + partitions, + renderTags, + (~/=), + (~==), + ) import Text.Pandoc.Builder ( Inlines, fromList, @@ -77,6 +87,28 @@ return . head . toList . para $ imageWith as (pack fp) "fig:" caption' +-- TODO: also add the case where SVG plots can be +-- embedded in HTML output +-- embeddedSVGBlock :: +-- Attr -> +-- FilePath -> +-- Inlines -> +-- PlotM Block +-- embeddedSVGBlock _ fp caption' = do +-- svgsource <- liftIO $ T.readFile fp +-- renderedCaption <- writeHtml caption' +-- return $ +-- RawBlock +-- "html5" +-- [st| +-- <figure> +-- <svg> +-- #{svgsource} +-- </svg> +-- <figcaption>#{renderedCaption}</figcaption> +-- </figure> +-- |] + interactiveBlock :: Attr -> FilePath -> @@ -112,7 +144,7 @@ extractPlot :: Text -> Text extractPlot t = let tags = canonicalizeTags $ parseTagsOptions parseOptionsFast t - extracted = headScripts tags <> [inside "body" $ tags] + extracted = headScripts tags <> [inside "body" tags] in mconcat $ renderTags <$> (deferScripts <$> extracted) where headScripts = partitions (~== ("<script>" :: String)) . inside "head" @@ -131,7 +163,7 @@ fromTag :: Tag Text -> Maybe ScriptTag fromTag (TagOpen "script" attrs) = Just $ - if "src" `elem` (fst . unzip $ attrs) + if "src" `elem` map fst attrs then ExternalScript attrs else InlineScript attrs fromTag _ = Nothing
src/Text/Pandoc/Filter/Plot/Monad.hs view
@@ -1,4 +1,6 @@+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeSynonymInstances #-} -- | -- Module : $header$ @@ -54,12 +56,26 @@ where import Control.Concurrent.Async.Lifted (mapConcurrently) -import Control.Concurrent.Chan (writeChan) -import Control.Concurrent.MVar +import Control.Concurrent.MVar (MVar, newMVar, putMVar, takeMVar) import Control.Concurrent.QSemN + ( QSemN, + newQSemN, + signalQSemN, + waitQSemN, + ) import Control.Exception.Lifted (bracket, bracket_) import Control.Monad.Reader + ( MonadIO (liftIO), + MonadReader (ask, local), + ReaderT (runReaderT), + asks, + when, + ) import Control.Monad.State.Strict + ( MonadState (get, put), + StateT, + evalStateT, + ) import Data.ByteString.Lazy (toStrict) import Data.Functor ((<&>)) import Data.Hashable (hash) @@ -89,30 +105,46 @@ shell, ) import Text.Pandoc.Definition (Format (..)) -import Text.Pandoc.Filter.Plot.Monad.Logging as Log +import Text.Pandoc.Filter.Plot.Monad.Logging + ( LogSink (..), + Logger (lVerbosity), + MonadLogger (..), + Verbosity (..), + debug, + err, + info, + strict, + terminateLogging, + warning, + withLogger, + ) import Text.Pandoc.Filter.Plot.Monad.Types import Prelude hiding (fst, log, snd) -- | pandoc-plot monad -type PlotM a = StateT PlotState (ReaderT RuntimeEnv IO) a +type PlotM = StateT PlotState (ReaderT RuntimeEnv IO) +instance MonadLogger PlotM where + askLogger = asks envLogger + data RuntimeEnv = RuntimeEnv - { envConfig :: Configuration, + { envFormat :: Maybe Format, -- pandoc output format + envConfig :: Configuration, envLogger :: Logger, envCWD :: FilePath } -- | Modify the runtime environment to be silent. silence :: PlotM a -> PlotM a -silence = local (\(RuntimeEnv c l d) -> RuntimeEnv c l {lVerbosity = Silent} d) +silence = local (\(RuntimeEnv f c l d) -> RuntimeEnv f c l {lVerbosity = Silent} d) -- | Get access to configuration within the @PlotM@ monad. asksConfig :: (Configuration -> a) -> PlotM a asksConfig f = asks (f . envConfig) -- | Evaluate a @PlotM@ action. -runPlotM :: Configuration -> PlotM a -> IO a -runPlotM conf v = do +runPlotM :: Maybe Format -> Configuration -> PlotM a -> IO a +runPlotM fmt conf v = do cwd <- getCurrentDirectory st <- PlotState <$> newMVar mempty @@ -120,7 +152,7 @@ let verbosity = logVerbosity conf sink = logSink conf withLogger verbosity sink $ - \logger -> runReaderT (evalStateT v st) (RuntimeEnv conf logger cwd) + \logger -> runReaderT (evalStateT v st) (RuntimeEnv fmt conf logger cwd) -- | maps a function, performing at most @N@ actions concurrently. mapConcurrentlyN :: Traversable t => Int -> (a -> PlotM b) -> t a -> PlotM (t b) @@ -132,27 +164,6 @@ with :: QSemN -> PlotM a -> PlotM a with s = bracket_ (liftIO $ waitQSemN s 1) (liftIO $ signalQSemN s 1) -debug, err, warning, info :: Text -> PlotM () -debug = log "[pandoc-plot] DEBUG | " Debug -err = log "[pandoc-plot] ERROR | " Error -warning = log "[pandoc-plot] WARN | " Warning -info = log "[pandoc-plot] INFO | " Info - --- | General purpose logging. -log :: - -- | Header. - Text -> - -- | Verbosity of the message. - Verbosity -> - -- | Message (can be multiple lines). - Text -> - PlotM () -log h v t = do - logger <- asks envLogger - when (v >= lVerbosity logger) $ - liftIO $ do - forM_ (T.lines t) $ \l -> writeChan (lChannel logger) (Just (h <> l <> "\n")) - -- | Run a command within the @PlotM@ monad. Stderr stream -- is read and decoded, while Stdout is ignored. -- Logging happens at the debug level if the command succeeds, or at @@ -216,11 +227,9 @@ -- | Throw an error that halts the execution of pandoc-plot due to a strict-mode. throwStrictError :: Text -> PlotM () throwStrictError msg = do - logger <- asks envLogger - log "[pandoc-plot] STRICT MODE | " Error msg - liftIO $ do - terminateLogging logger - exitFailure + strict msg + logger <- askLogger + liftIO $ terminateLogging logger >> exitFailure -- | Conditional execution of a PlotM action if pandoc-plot is -- run in strict mode.
src/Text/Pandoc/Filter/Plot/Monad/Logging.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} @@ -11,25 +12,36 @@ -- -- Logging primitives. module Text.Pandoc.Filter.Plot.Monad.Logging - ( Verbosity (..), + ( MonadLogger (..), + Verbosity (..), LogSink (..), Logger (..), withLogger, terminateLogging, + + -- * Logging messages + debug, + err, + warning, + info, + strict, ) where import Control.Concurrent (forkIO) import Control.Concurrent.Chan (Chan, newChan, readChan, writeChan) import Control.Concurrent.MVar (MVar, newEmptyMVar, putMVar, takeMVar) -import Control.Monad (forever, void) +import Control.Monad (forM_, forever, void, when) +import Control.Monad.IO.Class (MonadIO (..)) import Data.Char (toLower) import Data.List (intercalate) import Data.String (IsString (..)) import Data.Text (Text, unpack) -import Data.Text.IO as TIO +import qualified Data.Text as T +import Data.Text.IO as TIO (appendFile, hPutStr) import Data.Yaml (FromJSON (parseJSON), Value (String)) import System.IO (stderr) +import Prelude hiding (log) -- | Verbosity of the logger. data Verbosity @@ -55,12 +67,19 @@ -- | The logging implementation is very similar to Hakyll's. data Logger = Logger - { lVerbosity :: Verbosity, - lChannel :: Chan (Maybe Text), - lSink :: Text -> IO (), - lSync :: MVar () + { lVerbosity :: Verbosity, -- Verbosity level below which to ignore messages + lChannel :: Chan Command, -- Queue of logging commands + lSink :: Text -> IO (), -- Action to perform with log messages + lSync :: MVar () -- Synchronization variable } +data Command + = LogMessage Text + | EndLogging + +class Monad m => MonadLogger m where + askLogger :: m Logger + -- | Ensure that all log messages are flushed, and stop logging terminateLogging :: Logger -> IO () terminateLogging logger = do @@ -68,7 +87,7 @@ -- To signal to the logger that logging duties are over, -- we append Nothing to the channel, and wait for it to finish -- dealing with all items in the channel. - writeChan (lChannel logger) Nothing + writeChan (lChannel logger) EndLogging void $ takeMVar (lSync logger) -- | Perform an IO action with a logger. Using this function @@ -76,8 +95,8 @@ withLogger :: Verbosity -> LogSink -> (Logger -> IO a) -> IO a withLogger v s f = do logger <- - Logger <$> pure v - <*> newChan + Logger v + <$> newChan <*> pure (sink s) <*> newEmptyMVar @@ -87,7 +106,9 @@ forkIO $ forever $ readChan (lChannel logger) - >>= maybe (putMVar (lSync logger) ()) (lSink logger) + >>= \case + EndLogging -> putMVar (lSync logger) () + LogMessage t -> lSink logger t result <- f logger @@ -99,6 +120,26 @@ sink StdErr = TIO.hPutStr stderr sink (LogFile fp) = TIO.appendFile fp +-- | General purpose logging function. +log :: + (MonadLogger m, MonadIO m) => + Text -> -- Header + Verbosity -> + Text -> + m () +log h v t = do + logger <- askLogger + when (v >= lVerbosity logger) $ + liftIO $ do + forM_ (T.lines t) $ \l -> writeChan (lChannel logger) (LogMessage (h <> l <> "\n")) + +debug, err, strict, warning, info :: (MonadLogger m, MonadIO m) => Text -> m () +debug = log "[pandoc-plot] DEBUG | " Debug +err = log "[pandoc-plot] ERROR | " Error +strict = log "[pandoc-plot] STRICT MODE | " Error +warning = log "[pandoc-plot] WARN | " Warning +info = log "[pandoc-plot] INFO | " Info + instance IsString Verbosity where fromString s | ls == "silent" = Silent @@ -110,9 +151,11 @@ where ls = toLower <$> s choices = - intercalate ", " $ - fmap (fmap toLower . show) $ - enumFromTo minBound (maxBound :: Verbosity) + intercalate + ", " + ( fmap toLower . show + <$> enumFromTo minBound (maxBound :: Verbosity) + ) instance FromJSON Verbosity where parseJSON (String t) = pure $ fromString . unpack $ t
src/Text/Pandoc/Filter/Plot/Monad/Types.hs view
@@ -225,13 +225,13 @@ errorWithoutStackTrace $ mconcat [ s, - " is not one of valid save format : ", + " is not one of the valid save formats : ", mconcat $ intersperse ", " $ show <$> saveFormats ] where saveFormats = enumFromTo minBound maxBound :: [SaveFormat] -instance FromJSON SaveFormat -- TODO: test this parsing +instance FromJSON SaveFormat instance ToJSON SaveFormat where toJSON = toJSON . extension
src/Text/Pandoc/Filter/Plot/Renderers.hs view
@@ -27,9 +27,11 @@ where import Control.Concurrent.Async.Lifted (forConcurrently) -import Control.Concurrent.MVar +import Control.Concurrent.MVar (putMVar, takeMVar) import Control.Monad.Reader (local) import Control.Monad.State.Strict + ( MonadState (get, put), + ) import Data.List ((\\)) import Data.Map.Strict (Map) import qualified Data.Map.Strict as M @@ -122,12 +124,12 @@ -- | List of toolkits available on this machine. -- The executables to look for are taken from the configuration. availableToolkits :: Configuration -> IO [Toolkit] -availableToolkits conf = runPlotM conf availableToolkitsM +availableToolkits conf = runPlotM Nothing conf availableToolkitsM -- | List of toolkits not available on this machine. -- The executables to look for are taken from the configur unavailableToolkits :: Configuration -> IO [Toolkit] -unavailableToolkits conf = runPlotM conf unavailableToolkitsM +unavailableToolkits conf = runPlotM Nothing conf unavailableToolkitsM -- | Monadic version of @availableToolkits@. -- @@ -142,7 +144,7 @@ else return Nothing return $ catMaybes mtks where - asNonStrict = local (\(RuntimeEnv c l d) -> RuntimeEnv c {strictMode = False} l d) + asNonStrict = local (\(RuntimeEnv f c l d) -> RuntimeEnv f c {strictMode = False} l d) -- | Monadic version of @unavailableToolkits@ unavailableToolkitsM :: PlotM [Toolkit]
src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs view
@@ -22,6 +22,7 @@ ) where +import Data.Functor ((<&>)) import Data.Maybe (isJust) import Data.Text (Text, unpack) import System.Directory (findExecutable) @@ -42,7 +43,7 @@ -- | Checks that an executable is available on path, at all. existsOnPath :: FilePath -> IO Bool -existsOnPath fp = findExecutable fp >>= fmap isJust . return +existsOnPath fp = findExecutable fp <&> isJust -- | A shortcut to append capture script fragments to scripts appendCapture ::
src/Text/Pandoc/Filter/Plot/Scripting.hs view
@@ -20,7 +20,6 @@ ) where -import Control.Monad.Reader import Data.Default (def) import Data.Functor.Identity (Identity (..)) import Data.Hashable (hash) @@ -42,7 +41,7 @@ (</>), ) import Text.Pandoc.Class (runPure) -import Text.Pandoc.Definition +import Text.Pandoc.Definition (Block (CodeBlock), Pandoc (Pandoc)) import Text.Pandoc.Filter.Plot.Monad import Text.Pandoc.Filter.Plot.Scripting.Template import Text.Pandoc.Options (WriterOptions (..)) @@ -85,7 +84,7 @@ -- | Format a script to show in error messages formatScript :: Script -> Text -formatScript s = T.unlines . fmap (\(n, l) -> formatLine n l) $ zip linenos (T.lines s) +formatScript s = T.unlines . fmap (uncurry formatLine) $ zip linenos (T.lines s) where nlines = length (T.lines s) linenos = [1 .. nlines]
src/Text/Pandoc/Filter/Plot/Scripting/Template.hs view
@@ -4,11 +4,17 @@ import Data.String (fromString) import Language.Haskell.TH.Syntax + ( Exp (AppE, LitE, VarE), + Lit (StringL), + Q, + Quasi (qAddDependentFile), + runIO, + ) import System.FilePath ((</>)) sourceTemplate_ :: Q Exp sourceTemplate_ = do - let fp = ("data" </> "srctemplate.html") + let fp = "data" </> "srctemplate.html" qAddDependentFile fp d <- runIO $ readFile fp strToExp d
stack.yaml view
@@ -1,11 +1,11 @@-resolver: lts-17.5 # GHC 8.10.4 +resolver: lts-17.9 # GHC 8.10.4 packages: - . extra-deps: -- pandoc-2.12 -- hslua-module-path-0.1.0.1 +- pandoc-2.13 +- hslua-module-path-0.1.0.1@sha256:1fe319f18315618491260c912e85b6f73e406499c8fe2fa505e59036a4ff78fd,2502 # For development - git: https://github.com/owickstrom/pandoc-include-code.git
tests/Common.hs view
@@ -45,7 +45,7 @@ ensureDirectoryExistsAndEmpty tempDir let cb = (addDirectory tempDir $ codeBlock tk (trivialContent tk)) - _ <- runPlotM defaultTestConfig $ make cb + _ <- runPlotM Nothing defaultTestConfig $ make cb filesCreated <- length <$> listDirectory tempDir assertEqual "" 2 filesCreated @@ -60,11 +60,33 @@ ensureDirectoryExistsAndEmpty tempDir let cb = (addDirectory tempDir $ codeBlock tk (trivialContent tk)) - _ <- runPlotM defaultTestConfig $ make cb + _ <- runPlotM Nothing defaultTestConfig $ make cb filesCreated <- length <$> listDirectory tempDir assertEqual "" 2 filesCreated ------------------------------------------------------------------------------- +-- Test that pandoc-plot appropriately transforms code blocks that are +-- nested in other blocks (e.g. Divs) +testNestedCodeBlocks :: Toolkit -> TestTree +testNestedCodeBlocks tk = + testCase "transforms code blocks nested in other blocks" $ do + let postfix = unpack . cls $ tk + tempDir <- (</> "test-nester-blocks-" <> postfix) <$> getTemporaryDirectory + ensureDirectoryExistsAndEmpty tempDir + + let block = + Div mempty $ + singleton $ + addDirectory tempDir $ + codeBlock tk (trivialContent tk) + _ <- runPlotM Nothing defaultTestConfig $ make block + filesCreated <- length <$> listDirectory tempDir + assertEqual "" 2 filesCreated + where + singleton :: a -> [a] + singleton = return + +------------------------------------------------------------------------------- -- Test that included files are found within the source testFileInclusion :: Toolkit -> TestTree testFileInclusion tk = @@ -77,7 +99,7 @@ ( addPreamble (include tk) $ addDirectory tempDir $ codeBlock tk (trivialContent tk) ) - _ <- runPlotM defaultTestConfig $ make cb + _ <- runPlotM Nothing defaultTestConfig $ make cb inclusion <- readFile (include tk) sourcePath <- head . filter (isExtensionOf ".src.html") <$> listDirectory tempDir src <- readFile (tempDir </> sourcePath) @@ -109,7 +131,7 @@ ( addSaveFormat fmt $ addDirectory tempDir $ codeBlock tk (trivialContent tk) ) - _ <- runPlotM defaultTestConfig $ make cb + _ <- runPlotM Nothing defaultTestConfig $ make cb numberjpgFiles <- length <$> filter (isExtensionOf (extension fmt)) <$> listDirectory tempDir @@ -135,8 +157,8 @@ addDirectory tempDir $ addCaption expected $ codeBlock tk (trivialContent tk) - blockNoSource <- runPlotM defaultTestConfig $ make noSource - blockWithSource <- runPlotM defaultTestConfig $ make withSource + blockNoSource <- runPlotM Nothing defaultTestConfig $ make noSource + blockWithSource <- runPlotM Nothing defaultTestConfig $ make withSource -- In the case where source=false, the caption is used verbatim. -- Otherwise, links will be appended to the caption; hence, the caption @@ -166,7 +188,7 @@ addDirectory tempDir $ addCaption mempty $ -- This test requires that the actual caption be empty codeBlock tk (trivialContent tk) - blockWithSource <- runPlotM defaultTestConfig {sourceCodeLabel = "Test label"} $ make withSource + blockWithSource <- runPlotM Nothing defaultTestConfig {sourceCodeLabel = "Test label"} $ make withSource -- The caption will look like [Space, Str "(", Link ... ]. Hence, we skip the first elements with (!!) let resultCaption = linkLabel $ (!! 2) . B.toList $ extractCaption blockWithSource @@ -210,7 +232,7 @@ addDirectory tempDir $ addSaveFormat PNG $ codeBlock tk (trivialContent tk) - _ <- runPlotM config $ make cb + _ <- runPlotM Nothing config $ make cb numberPngFiles <- length <$> filter (isExtensionOf (extension PNG)) @@ -238,7 +260,7 @@ addCaption "**caption**" $ codeBlock tk (trivialContent tk) fmt = B.Format "markdown" - result <- runPlotM (defaultTestConfig {captionFormat = fmt}) $ make cb + result <- runPlotM Nothing (defaultTestConfig {captionFormat = fmt}) $ make cb assertIsInfix expected (extractCaption result) where extractCaption (B.Para blocks) = extractImageCaption . head $ blocks @@ -264,7 +286,7 @@ addCaption "[title](https://google.com)" $ codeBlock tk (trivialContent tk) fmt = B.Format "markdown" - result <- runPlotM (defaultTestConfig {captionFormat = fmt}) $ make cb + result <- runPlotM Nothing (defaultTestConfig {captionFormat = fmt}) $ make cb assertIsInfix expected (extractCaption result) where extractCaption (B.Para blocks) = extractImageCaption . head $ blocks @@ -286,7 +308,7 @@ addDirectory tempDir $ codeBlock tk (trivialContent tk) - result <- runPlotM defaultTestConfig $ make cb + result <- runPlotM Nothing defaultTestConfig $ make cb cleanedDirs <- cleanOutputDirs defaultTestConfig cb assertEqual "" [tempDir] cleanedDirs @@ -307,7 +329,7 @@ ensureDirectoryExistsAndEmpty tempDir let cb = addDirectory tempDir $ codeBlock Matplotlib "plt.show()" - result <- runPlotM defaultTestConfig $ makeEither cb + result <- runPlotM Nothing defaultTestConfig $ makeEither cb let expectedCheck :: Either PandocPlotError a -> Bool expectedCheck (Left (ScriptChecksFailedError _)) = True expectedCheck _ = False
tests/Main.hs view
@@ -49,6 +49,7 @@ testGroup (show tk) $ [ testFileCreation, testFileCreationPathWithSpaces, + testNestedCodeBlocks, testFileInclusion, testSaveFormat, testWithSource,