packages feed

pandoc-plot 0.6.1.0 → 0.7.0.0

raw patch · 31 files changed

+980/−724 lines, 31 filesdep +timedep −data-default-classdep −deepseqdep −temporarydep ~directoryPVP ok

version bump matches the API change (PVP)

Dependencies added: time

Dependencies removed: data-default-class, deepseq, temporary, turtle

Dependency ranges changed: directory

API changes (from Hackage documentation)

- Text.Pandoc.Filter.Plot: make' :: Toolkit -> Configuration -> Block -> IO (Either PandocPlotError Block)
- Text.Pandoc.Filter.Plot.Internal: PlotEnv :: !Toolkit -> !Configuration -> PlotEnv
- Text.Pandoc.Filter.Plot.Internal: [config] :: PlotEnv -> !Configuration
- Text.Pandoc.Filter.Plot.Internal: [oConfiguration] :: OutputSpec -> Configuration
- Text.Pandoc.Filter.Plot.Internal: data PlotEnv
+ Text.Pandoc.Filter.Plot: Debug :: Verbosity
+ Text.Pandoc.Filter.Plot: Error :: Verbosity
+ Text.Pandoc.Filter.Plot: Info :: Verbosity
+ Text.Pandoc.Filter.Plot: LogFile :: FilePath -> LogSink
+ Text.Pandoc.Filter.Plot: Silent :: Verbosity
+ Text.Pandoc.Filter.Plot: StdErr :: LogSink
+ Text.Pandoc.Filter.Plot: Warning :: Verbosity
+ Text.Pandoc.Filter.Plot: [logSink] :: Configuration -> !LogSink
+ Text.Pandoc.Filter.Plot: [logVerbosity] :: Configuration -> !Verbosity
+ Text.Pandoc.Filter.Plot: data LogSink
+ Text.Pandoc.Filter.Plot: data Verbosity
+ Text.Pandoc.Filter.Plot: makeEither :: Configuration -> Block -> IO (Either PandocPlotError Block)
+ Text.Pandoc.Filter.Plot.Internal: Debug :: Verbosity
+ Text.Pandoc.Filter.Plot.Internal: Error :: Verbosity
+ Text.Pandoc.Filter.Plot.Internal: Info :: Verbosity
+ Text.Pandoc.Filter.Plot.Internal: LogFile :: FilePath -> LogSink
+ Text.Pandoc.Filter.Plot.Internal: Silent :: Verbosity
+ Text.Pandoc.Filter.Plot.Internal: StdErr :: LogSink
+ Text.Pandoc.Filter.Plot.Internal: Warning :: Verbosity
+ Text.Pandoc.Filter.Plot.Internal: [logSink] :: Configuration -> !LogSink
+ Text.Pandoc.Filter.Plot.Internal: [logVerbosity] :: Configuration -> !Verbosity
+ Text.Pandoc.Filter.Plot.Internal: ask :: MonadReader r m => m r
+ Text.Pandoc.Filter.Plot.Internal: asks :: MonadReader r m => (r -> a) -> m a
+ Text.Pandoc.Filter.Plot.Internal: availableToolkitsM :: PlotM [Toolkit]
+ Text.Pandoc.Filter.Plot.Internal: data LogSink
+ Text.Pandoc.Filter.Plot.Internal: data Verbosity
+ Text.Pandoc.Filter.Plot.Internal: debug :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: defaultReaderOptions :: ReaderOptions
+ Text.Pandoc.Filter.Plot.Internal: err :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: info :: Text -> PlotM ()
+ Text.Pandoc.Filter.Plot.Internal: liftIO :: MonadIO m => IO a -> m a
+ Text.Pandoc.Filter.Plot.Internal: runCommand :: Text -> PlotM (ExitCode, Text)
+ Text.Pandoc.Filter.Plot.Internal: runPlotM :: Configuration -> PlotM a -> IO a
+ Text.Pandoc.Filter.Plot.Internal: unavailableToolkitsM :: PlotM [Toolkit]
+ Text.Pandoc.Filter.Plot.Internal: warning :: Text -> PlotM ()
- Text.Pandoc.Filter.Plot: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
+ Text.Pandoc.Filter.Plot: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Verbosity -> !LogSink -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
- Text.Pandoc.Filter.Plot: ScriptChecksFailedError :: String -> PandocPlotError
+ Text.Pandoc.Filter.Plot: ScriptChecksFailedError :: Text -> PandocPlotError
- Text.Pandoc.Filter.Plot: ScriptRuntimeError :: String -> Int -> PandocPlotError
+ Text.Pandoc.Filter.Plot: ScriptRuntimeError :: Text -> Int -> PandocPlotError
- Text.Pandoc.Filter.Plot: make :: Toolkit -> Configuration -> Block -> IO Block
+ Text.Pandoc.Filter.Plot: make :: Configuration -> Block -> IO Block
- Text.Pandoc.Filter.Plot.Internal: CheckFailed :: String -> CheckResult
+ Text.Pandoc.Filter.Plot.Internal: CheckFailed :: Text -> CheckResult
- Text.Pandoc.Filter.Plot.Internal: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
+ Text.Pandoc.Filter.Plot.Internal: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Verbosity -> !LogSink -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
- Text.Pandoc.Filter.Plot.Internal: FigureSpec :: !Text -> !Bool -> !Script -> !SaveFormat -> !FilePath -> !Int -> ![(Text, Text)] -> !Attr -> FigureSpec
+ Text.Pandoc.Filter.Plot.Internal: FigureSpec :: !Toolkit -> !Text -> !Bool -> !Script -> !SaveFormat -> !FilePath -> !Int -> ![(Text, Text)] -> !Attr -> FigureSpec
- Text.Pandoc.Filter.Plot.Internal: OutputSpec :: Configuration -> FigureSpec -> FilePath -> FilePath -> OutputSpec
+ Text.Pandoc.Filter.Plot.Internal: OutputSpec :: FigureSpec -> FilePath -> FilePath -> OutputSpec
- Text.Pandoc.Filter.Plot.Internal: ScriptChecksFailed :: String -> ScriptResult
+ Text.Pandoc.Filter.Plot.Internal: ScriptChecksFailed :: Text -> ScriptResult
- Text.Pandoc.Filter.Plot.Internal: ScriptFailure :: String -> Int -> ScriptResult
+ Text.Pandoc.Filter.Plot.Internal: ScriptFailure :: Text -> Int -> ScriptResult
- Text.Pandoc.Filter.Plot.Internal: [toolkit] :: PlotEnv -> !Toolkit
+ Text.Pandoc.Filter.Plot.Internal: [toolkit] :: FigureSpec -> !Toolkit
- Text.Pandoc.Filter.Plot.Internal: command :: Toolkit -> OutputSpec -> IO Text
+ Text.Pandoc.Filter.Plot.Internal: command :: Toolkit -> OutputSpec -> PlotM Text
- Text.Pandoc.Filter.Plot.Internal: executable :: Toolkit -> Configuration -> IO FilePath
+ Text.Pandoc.Filter.Plot.Internal: executable :: Toolkit -> PlotM FilePath
- Text.Pandoc.Filter.Plot.Internal: toolkitAvailable :: Toolkit -> Configuration -> IO Bool
+ Text.Pandoc.Filter.Plot.Internal: toolkitAvailable :: Toolkit -> PlotM Bool
- Text.Pandoc.Filter.Plot.Internal: type PlotM a = ReaderT PlotEnv IO a
+ Text.Pandoc.Filter.Plot.Internal: type PlotM a = ReaderT Configuration LoggingM a

Files

CHANGELOG.md view
@@ -2,6 +2,25 @@ 
 pandoc-plot uses [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
 
+Release 0.7.0.0
+---------------
+* Added documentation on using `pandoc-plot` with LaTeX documents as well.
+* Added preliminary support for logging to `pandoc-plot`. You can turn on this feature in the configuration as follows:
+
+````yaml
+logging:
+    # Possible verbosity values: debug, error, warning, info, silent
+    # debug level shows all messages
+    # error level shows all but debug messages, etc.
+    verbosity: info
+    
+    # OPTIONAL: log to file
+    # Remove line below to log to stderr
+    filepath: log.txt
+````
+
+* Removed dependencies `turtle`, `temporary`, `deepseq`, and `data-default-class`, resulting in improved build times by ~10%, and makes the executable smaller by 15-20%!
+
 Release 0.6.1.0
 ---------------
 
README.md view
@@ -7,7 +7,7 @@ 
 ## A Pandoc filter to generate figures from code blocks in documents
 
-[![Hackage version](https://img.shields.io/hackage/v/pandoc-plot.svg)](http://hackage.haskell.org/package/pandoc-plot) [![Stackage version (nightly)](http://stackage.org/package/pandoc-plot/badge/nightly)](http://stackage.org/nightly/package/pandoc-plot) [![Conda Version](https://img.shields.io/conda/vn/conda-forge/pandoc-plot.svg)](https://anaconda.org/conda-forge/pandoc-plot) [![license](https://img.shields.io/badge/license-GPLv2+-lightgray.svg)](https://www.gnu.org/licenses/gpl.html) 
+[![Hackage version](https://img.shields.io/hackage/v/pandoc-plot.svg)](http://hackage.haskell.org/package/pandoc-plot) [![Stackage version (latest long-term service)](http://stackage.org/package/pandoc-plot/badge/lts)](http://stackage.org/lts/package/pandoc-plot) [![Stackage version (nightly)](http://stackage.org/package/pandoc-plot/badge/nightly)](http://stackage.org/nightly/package/pandoc-plot) [![Conda Version](https://img.shields.io/conda/vn/conda-forge/pandoc-plot.svg)](https://anaconda.org/conda-forge/pandoc-plot) [![license](https://img.shields.io/badge/license-GPLv2+-lightgray.svg)](https://www.gnu.org/licenses/gpl.html) 
 
 `pandoc-plot` turns code blocks present in your documents (Markdown, LaTeX, etc.) into embedded figures, using your plotting toolkit of choice, including Matplotlib, ggplot2, MATLAB, Mathematica, and more.
 
@@ -22,6 +22,7 @@         pandoc-crossref](#compatibility-with-pandoc-crossref)
   - [Detailed usage](#detailed-usage)
       - [As a filter](#as-a-filter)
+      - [Syntax](#syntax)
       - [Parameters and options](#parameters-and-options)
       - [Configuration](#configuration)
       - [Other commands](#other-commands)
@@ -274,9 +275,9 @@ 
 Available commands:
   toolkits                 Show information on toolkits and exit.
-  clean                    Clean output directories where figures from FILE
-                           might be stored. WARNING: All files in those
-                           directories will be deleted.
+  clean                    Clean output directories where figures from FILE and
+                           log files might be stored. WARNING: All files in
+                           those directories will be deleted.
   write-example-config     Write example configuration to a file and exit.
 
 More information can be found via the manual (pandoc-plot --manual) or the repository README, located at
@@ -304,28 +305,67 @@ pandoc --filter pandoc-plot --filter pandoc-crossref -i input.md -o output.html
 ```
 
-### Parameters and options
+### Syntax
 
-`pandoc-plot` looks for code blocks with a specific class, depending on
-the toolkit you want to use. `pandoc-plot` will run the code and capture
-the figure output. There can only be **one** figure per code block.
+The syntax for code blocks in documents is shown below. `pandoc-plot`
+looks for code blocks with a specific class, depending on the toolkit
+you want to use. `pandoc-plot` will run the code and capture the figure
+output. There can only be **one** figure per code block.
 
+The possible parameters and options are described in [further
+below](#parameters-and-options).
+
+#### Markdown
+
+```` markdown
+  ```{.cls param1=value1 param2=value2 ...}
+  # script content
+  ```
+````
+
+#### LaTeX
+
+Note that the `minted` LaTeX package need not be installed.
+
+``` latex
+\begin{minted}[param1=value1, param2=value2, ...]{cls}
+...
+\end{minted}
+```
+
+### Parameters and options
+
 There are parameters that affect the figure that will be included in
-your document. Here are all the possible general parameters:
+your document. Here are all the possible general parameters, in Markdown
+syntax:
 
 ```` markdown
-  ```{.cls directory=(path) caption=(text) format=(PNG|PDF|SVG|JPG|EPS|GIF|TIF|WEBP) source=(true|false) preamble=(path) dpi=(integer) executable=(path) caption_format=(text)}
+  ```{.cls 
+      .language
+      directory=(path) 
+      caption=(text) 
+      format=(PNG|PDF|SVG|JPG|EPS|GIF|TIF|WEBP) 
+      source=(true|false) 
+      preamble=(path) 
+      dpi=(integer) 
+      executable=(path) 
+      caption_format=(text)
+      }
   # script content
   ```
 ````
 
   - `cls` must be one of the following: `matplotlib`, `matlabplot`,
     `plotly_python`, `plotly_r`, `mathplot`, `octaveplot`, `ggplot2`,
-    `gnuplot`
+    `gnuplot`, or `graphviz`.
 
 All following parameters are optional, with their default values
 controlled by the [configuration](#configuration)
 
+  - `language` specifies the programming language used in this block.
+    This parameter is ignored by `pandoc-plot`, but your text editor may
+    use it to highlight code. See [Code
+    highlighting](#code-highlighting) below.
   - `directory` is a path to the directory where the figure and source
     code will be saved. You cannot control the file name. This path is
     either absolute, or relative from the working directory where you
@@ -366,22 +406,30 @@   ```
 ````
 
-For example, for Matplotlib plots:
+or Latex:
 
-```` markdown
-  ```{.python .matplotlib}
+``` latex
+  \begin{minted}[(options)]{language, cls}
   # script content
-  ```
-````
+  \end{minted}
+```
 
-or for GGPlot2 figures:
+For example, for GGPlot2 figures:
 
 ```` markdown
-  ```{.r .ggplot2}
+  ```{.r .ggplot2 caption=Highlighted code block}
   # script content
   ```
 ````
 
+or (Latex):
+
+``` latex
+  \begin{minted}[caption=Highlighted code block]{r, ggplot2}
+  # script content
+  \end{minted}
+```
+
 This way, you benefit from code highlighting *and* `pandoc-plot`.
 
 ### Configuration
@@ -400,12 +448,10 @@ # E.g.:
 #  executable: python3
 #  executable: "C:\Python37\Scripts\python.exe"
-#
-# Note that this file should be re-named to ".pandoc-plot.yml" before pandoc-plot 
-# notices it.
 
 # The following parameters affect all toolkits
-# Directory where to save the plots. The path can be relative to this file, or absolute.
+# Directory where to save the plots. The path can be relative to pandoc-plot's
+# current working directory, or absolute.
 directory: plots/
 
 # Whether or not to include a link to the source script in the caption. 
@@ -425,6 +471,14 @@ # Example: markdown, rst+raw_tex
 caption_format: markdown+tex_math_dollars
 
+# Logging configuration
+logging:
+  # Possible verbosity values: debug, error, warning, info, silent
+  verbosity: warning
+  # If the filepath below is not present, then pandoc-plot will log to stderr
+  # Otherwise, log messages will be appended to the filepath.
+  # filepath: path/to/file.txt
+
 # The possible parameters for the Matplotlib toolkit
 matplotlib:
   # preamble: matplotlib.py
@@ -535,6 +589,26 @@     web page. High-resolution figures are not affected. For example,
     `transparent: true`.
 
+#### Logging
+
+If you are running `pandoc-plot` on a large document, you might want to
+turn on logging. You can do so via the configuration file as follows:
+
+``` yaml
+logging:
+    # Possible verbosity values: debug, error, warning, info, silent
+    # debug level shows all messages
+    # error level shows all but debug messages, etc.
+    verbosity: info
+    
+    # OPTIONAL: log to file
+    # Remove line below to log to stderr
+    filepath: log.txt
+```
+
+By default, `pandoc-plot` logs warnings and errors to the standard error
+stream only.
+
 ### Other commands
 
 #### Finding installed toolkits
@@ -573,8 +647,8 @@ 
 ``` bash
 Usage: pandoc-plot.exe clean [--config PATH] FILE
-  Clean output directories where figures from FILE might be stored. WARNING: All
-  files in those directories will be deleted.
+  Clean output directories where figures from FILE and log files might be
+  stored. WARNING: All files in those directories will be deleted.
 
 Available options:
   --config PATH            Path to optional configuration file.
example-config.yml view
@@ -8,12 +8,10 @@ # E.g.:
 #  executable: python3
 #  executable: "C:\Python37\Scripts\python.exe"
-#
-# Note that this file should be re-named to ".pandoc-plot.yml" before pandoc-plot 
-# notices it.
 
 # The following parameters affect all toolkits
-# Directory where to save the plots. The path can be relative to this file, or absolute.
+# Directory where to save the plots. The path can be relative to pandoc-plot's
+# current working directory, or absolute.
 directory: plots/
 
 # Whether or not to include a link to the source script in the caption. 
@@ -32,6 +30,14 @@ # https://pandoc.org/MANUAL.html#option--from
 # Example: markdown, rst+raw_tex
 caption_format: markdown+tex_math_dollars
+
+# Logging configuration
+logging:
+  # Possible verbosity values: debug, error, warning, info, silent
+  verbosity: warning
+  # If the filepath below is not present, then pandoc-plot will log to stderr
+  # Otherwise, log messages will be appended to the filepath.
+  # filepath: path/to/file.txt
 
 # The possible parameters for the Matplotlib toolkit
 matplotlib:
executable/ExampleConfig.hs view
@@ -3,22 +3,17 @@ module ExampleConfig ( embedExampleConfig ) where
 
 
-import           Control.DeepSeq            (($!!))
-
 import           Data.String
+import           Data.Text                  (unpack)
+import qualified Data.Text.IO               as TIO
 
 import           Language.Haskell.TH.Syntax
 
-import System.IO
-
 docFile :: FilePath
 docFile = "example-config.yml"
 
 readDocFile :: IO String
-readDocFile = withFile docFile ReadMode $ \h -> do
-    hSetEncoding h utf8
-    cont <- hGetContents h
-    return $!! cont
+readDocFile = TIO.readFile docFile >>= return . unpack
 
 embedExampleConfig :: Q Exp
 embedExampleConfig = do
executable/Main.hs view
@@ -9,28 +9,29 @@ 
 import           Data.List                        (intersperse, (\\))
 import           Data.Text                        (unpack)
+import qualified Data.Text.IO                     as TIO
 import           Data.Version                     (parseVersion, showVersion)
 
+import           GHC.IO.Encoding                  (setLocaleEncoding, utf8)
 import           GitHash                          as Git
 
 import           Options.Applicative
 import qualified Options.Applicative.Help.Pretty  as P
 
-import           System.Directory                 (doesFileExist)
+import           System.Directory                 (doesFileExist, getTemporaryDirectory)
 import           System.Environment               (lookupEnv)
+import           System.FilePath                  ((</>))
 import           System.IO                        (hPutStrLn, stderr)
-import           System.IO.Temp                   (writeSystemTempFile)
 
 import           Text.Pandoc.Filter.Plot          (availableToolkits,
                                                    plotTransform,
                                                    defaultConfiguration, 
-                                                   configuration, 
-                                                   Configuration(..))
+                                                   configuration, Configuration(..))
 import           Text.Pandoc.Filter.Plot.Internal (cls, supportedSaveFormats, 
                                                    toolkits, readDoc, 
                                                    cleanOutputDirs, 
                                                    configurationPathMeta,
-                                                   executable)
+                                                   executable, runPlotM)
 
 import           Text.Pandoc                      (pandocVersion)
 import           Text.Pandoc.Definition           (pandocTypesVersion)
@@ -115,7 +116,7 @@                 )  
             , command "clean" (
                 info (cleanP <**> helper) ( 
-                    progDesc "Clean output directories where figures from FILE might be stored.\
+                    progDesc "Clean output directories where figures from FILE and log files might be stored.\
                               \ WARNING: All files in those directories will be deleted." 
                     )
                 )
@@ -221,7 +222,7 @@     return unavailable >>= mapM_ (unavailToolkitInfo c)
     where
         toolkitInfo avail conf tk = do
-            exe <- executable tk conf
+            exe <- runPlotM conf $ executable tk
             putStrLn $ "Toolkit: " <> show tk
             when avail $ putStrLn $ "    Executable: " <> exe
             putStrLn $ "    Code block trigger: " <> (unpack . cls $ tk)
@@ -249,11 +250,14 @@         firstJusts :: [Maybe a] -> Maybe a
         firstJusts = msum
 
+
 showManPage :: IO ()
-showManPage = 
-    writeSystemTempFile "pandoc-plot-manual.html" $(embedManualHtml)
-        >>= \fp -> openBrowser ("file:///" <> fp)
-        >> return ()
+showManPage = do
+    setLocaleEncoding utf8 -- This is required to write the manual file, for some reason.
+    manualPath <- (</> "pandoc-plot-manual.html") <$> getTemporaryDirectory
+    TIO.writeFile manualPath $(embedManualHtml)
+    openBrowser ("file:///" <> manualPath)
+    return ()
 
 -- | Use Doc type directly because of newline formatting
 footer' :: P.Doc
executable/ManPage.hs view
@@ -5,27 +5,20 @@ 
 module ManPage ( embedManualHtml ) where
 
-import           Control.DeepSeq            (($!!))
-
 import           Data.String 
 import qualified Data.Text                  as T
+import qualified Data.Text.IO               as TIO
 
 import           Language.Haskell.TH.Syntax
 
 import qualified Text.Pandoc                as P
 import           Text.Pandoc.Highlighting   (pygments)
 
-import System.FilePath                      (FilePath)
-import System.IO
-
 docFile :: FilePath
 docFile = "README.md"
 
 readDocFile :: IO String
-readDocFile = withFile docFile ReadMode $ \h -> do
-    hSetEncoding h utf8
-    cont <- hGetContents h
-    return $!! cont
+readDocFile = TIO.readFile docFile >>= return . T.unpack 
 
 readerOpts :: P.ReaderOptions
 readerOpts = P.def { P.readerExtensions = P.githubMarkdownExtensions
pandoc-plot.cabal view
@@ -1,5 +1,5 @@ name:           pandoc-plot
-version:        0.6.1.0
+version:        0.7.0.0
 cabal-version:  >= 1.12
 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 captured and included as a figure.
@@ -31,7 +31,6 @@         Paths_pandoc_plot
         Text.Pandoc.Filter.Plot.Clean
         Text.Pandoc.Filter.Plot.Configuration
-        Text.Pandoc.Filter.Plot.Types
         Text.Pandoc.Filter.Plot.Parse
         Text.Pandoc.Filter.Plot.Scripting
         Text.Pandoc.Filter.Plot.Renderers
@@ -45,6 +44,9 @@         Text.Pandoc.Filter.Plot.Renderers.GGPlot2
         Text.Pandoc.Filter.Plot.Renderers.GNUPlot
         Text.Pandoc.Filter.Plot.Renderers.Graphviz
+        Text.Pandoc.Filter.Plot.Monad
+        Text.Pandoc.Filter.Plot.Monad.Logging
+        Text.Pandoc.Filter.Plot.Monad.Types
     hs-source-dirs:
         src
     ghc-options: 
@@ -54,17 +56,15 @@           base               >= 4.11  && <5
         , bytestring
         , containers
-        , directory
-        , data-default-class >= 0.1.2 && < 0.2
+        , directory          >= 1.2.7 && < 2
         , filepath           >= 1.4   && < 2
         , hashable           >= 1     && < 2
         , pandoc             >= 2.8   && < 3
         , pandoc-types       >= 1.20  && < 2
         , async              >= 2     && < 3
         , shakespeare        >= 2.0   && < 3
-        , temporary
         , text               >= 1     && < 2
-        , turtle             >= 1.5   && < 2
+        , time               >= 1     && < 2
         , typed-process      >= 0.2.1 && < 1
         , yaml               >= 0.8   && < 1
         , mtl                >= 2.2   && < 2.3
@@ -78,11 +78,10 @@         Paths_pandoc_plot
     hs-source-dirs:
         executable
-    ghc-options: -Wall -Wcompat -rtsopts -O2 -threaded -with-rtsopts=-N
+    ghc-options: -Wall -Wcompat -threaded -rtsopts -with-rtsopts=-N
     build-depends:
           base                  >= 4.11 && <5
         , directory
-        , deepseq
         , filepath
         , githash               >= 0.1.3.0 && < 1
         , open-browser          >= 0.2.1.0
@@ -91,7 +90,6 @@         , pandoc-plot
         , pandoc-types          > 1.12 && <2
         , template-haskell      > 2.7 && < 3
-        , temporary
         , text
     default-language: Haskell2010
 
@@ -104,7 +102,6 @@     build-depends:   base                 >= 4.11 && < 5
                    , containers
                    , directory
-                   , data-default-class   >= 0.1.2
                    , filepath
                    , hspec
                    , hspec-expectations
@@ -113,9 +110,7 @@                    , tasty
                    , tasty-hunit
                    , tasty-hspec
-                   , temporary
                    , text
-                   , mtl                >= 2.2   && < 2.3
     default-language: Haskell2010
   
 benchmark benchmark-pandoc-plot
src/Text/Pandoc/Filter/Plot.hs view
@@ -75,6 +75,8 @@     , configuration
     , defaultConfiguration
     , Configuration(..)
+    , Verbosity(..)
+    , LogSink(..)
     , SaveFormat(..)
     , Script
     -- * Determining available plotting toolkits
@@ -82,16 +84,14 @@     , unavailableToolkits
     -- * For testing and internal purposes ONLY
     , make
-    , make'
+    , makeEither
     , PandocPlotError(..)
     , readDoc
     ) where
 
 import Control.Concurrent.Async          (mapConcurrently)
 
-import Control.Monad.Reader              (runReaderT)
-
-import System.IO                         (hPutStrLn, stderr)
+import Data.Text                         (Text, unpack)
 
 import Text.Pandoc.Definition            (Pandoc(..), Block)
 import Text.Pandoc.Walk                  (walkM, Walkable)
@@ -99,26 +99,6 @@ import Text.Pandoc.Filter.Plot.Internal
 
 
--- | Highest-level function that can be walked over a Pandoc tree.
--- All code blocks that have the appropriate class names will be considered
--- figures, e.g. @.matplotlib@.
---
--- This function can be made to operation on whole @Pandoc@ documents. However,
--- you should prefer the @plotTransform@ function for whole documents, as it
--- is optimized for parallel operations.
---
--- Failing to render a figure does not stop the filter, so that you may run the filter
--- on documents without having all necessary toolkits installed. In this case, error
--- messages are printed to stderr, and blocks are left unchanged.
-makePlot :: Walkable Block a
-         => Configuration -- ^ Configuration for default values
-         -> a             -- ^ Input block or document
-         -> IO a
-makePlot conf = walkM makePlot'
-    where
-        makePlot' block = maybe (return block) (\tk -> make tk conf block) (plotToolkit block)
-
-
 -- | Walk over an entire Pandoc document, transforming appropriate code blocks
 -- into figures. This function will operate on blocks in parallel if possible.
 --
@@ -132,33 +112,44 @@     mapConcurrently (makePlot conf) blocks >>= return . Pandoc meta
 
 
--- | Force to use a particular toolkit to render appropriate code blocks.
+-- | Highest-level function that can be walked over a Pandoc tree.
+-- All code blocks that have the appropriate class names will be considered
+-- figures, e.g. @.matplotlib@.
 --
+-- This function can be made to operation on whole @Pandoc@ documents. However,
+-- you should prefer the @plotTransform@ function for whole documents, as it
+-- is optimized for parallel operations.
+--
 -- Failing to render a figure does not stop the filter, so that you may run the filter
 -- on documents without having all necessary toolkits installed. In this case, error
 -- messages are printed to stderr, and blocks are left unchanged.
-make :: Toolkit       -- ^ Plotting toolkit.
-     -> Configuration -- ^ Configuration for default values.
+makePlot :: Walkable Block a
+         => Configuration -- ^ Configuration for default values
+         -> a             -- ^ Input block or document
+         -> IO a
+makePlot conf = walkM (make conf)
+
+
+make :: Configuration
      -> Block 
      -> IO Block
-make tk conf blk = 
-    either (const (return blk) . showErr) return =<< make' tk conf blk
-    where
-        showErr e = hPutStrLn stderr $ show e
+make conf blk = 
+    either (const (return blk) ) return =<< makeEither conf blk
 
 
-make' :: Toolkit 
-      -> Configuration 
-      -> Block 
-      -> IO (Either PandocPlotError Block)
-make' tk conf block = runReaderT (make'' block) (PlotEnv tk conf)
+makeEither :: Configuration 
+           -> Block 
+           -> IO (Either PandocPlotError Block)
+makeEither conf block = runPlotM conf (go block)
     where
-        make'' :: Block -> PlotM (Either PandocPlotError Block)
-        make'' blk = parseFigureSpec blk
+        go :: Block -> PlotM (Either PandocPlotError Block)
+        go blk = parseFigureSpec blk
                     >>= maybe
                         (return $ Right blk)
                         (\s -> runScriptIfNecessary s >>= handleResult s)
             where
+                -- Logging of errors has been taken care of in @runScriptIfNecessary@ 
+                handleResult :: FigureSpec -> ScriptResult -> PlotM (Either PandocPlotError Block)
                 handleResult spec ScriptSuccess          = return $ Right $ toImage (captionFormat conf) spec
                 handleResult _ (ScriptFailure msg code)  = return $ Left (ScriptRuntimeError msg code) 
                 handleResult _ (ScriptChecksFailed msg)  = return $ Left (ScriptChecksFailedError msg)
@@ -166,11 +157,11 @@ 
 
 data PandocPlotError
-    = ScriptRuntimeError String Int
-    | ScriptChecksFailedError String
+    = ScriptRuntimeError Text Int
+    | ScriptChecksFailedError Text
     | ToolkitNotInstalledError Toolkit
 
 instance Show PandocPlotError where
     show (ScriptRuntimeError _ exitcode) = "ERROR (pandoc-plot) The script failed with exit code " <> show exitcode <> "."
-    show (ScriptChecksFailedError msg)   = "ERROR (pandoc-plot) A script check failed with message: " <> msg <> "."
+    show (ScriptChecksFailedError msg)   = "ERROR (pandoc-plot) A script check failed with message: " <> unpack msg <> "."
     show (ToolkitNotInstalledError tk)   = "ERROR (pandoc-plot) The " <> show tk <> " toolkit is required but not installed."
src/Text/Pandoc/Filter/Plot/Clean.hs view
@@ -17,12 +17,11 @@     , readDoc
 ) where
 
-
-import           Control.Monad.Reader             (runReaderT, forM, liftIO)
+-- TODO: forConcurrently
+import           Control.Monad.Reader             (forM)
 
 import qualified Data.ByteString.Lazy             as B
 import           Data.Char                        (toLower)
-import           Data.Default.Class               (def)
 import           Data.List                        (nub)
 import           Data.Maybe                       (fromMaybe, catMaybes)
 
@@ -41,24 +40,25 @@ import           Text.Pandoc.Walk                 (query, Walkable)
 
 import Text.Pandoc.Filter.Plot.Parse
-import Text.Pandoc.Filter.Plot.Types
+import Text.Pandoc.Filter.Plot.Monad
 
 
 -- | Clean all output related to pandoc-plot. This includes output directories specified 
--- in the configuration and in the document/block. Note that *all* files in pandoc-plot output 
--- directories will be removed.
+-- in the configuration and in the document/block, as well as log files. 
+-- Note that *all* files in pandoc-plot output directories will be removed.
 --
 -- The cleaned directories are returned.
 cleanOutputDirs :: Walkable Block b => Configuration -> b -> IO [FilePath]
 cleanOutputDirs conf doc = do
+    
+    case logSink conf of 
+        LogFile fp -> removePathForcibly fp
+        _          -> return ()
+    
     directories <- sequence $ query (\b -> [outputDir b]) doc
     forM (nub . catMaybes $ directories) removeDir
     where
-        outputDir b = 
-            maybe
-                (return Nothing)
-                (\tk -> runReaderT (parseFigureSpec b >>= return . fmap directory) (PlotEnv tk conf))
-                (plotToolkit b)
+        outputDir b = runPlotM conf (parseFigureSpec b >>= return . fmap directory)
         
         removeDir :: FilePath -> IO FilePath
         removeDir d = removePathForcibly d >> return d
@@ -70,7 +70,7 @@ readDoc fp = handleError =<< (runIO $ do
         let fmt = fromMaybe mempty (formatFromFilePath fp)
         (reader, exts) <- P.getReader fmt
-        let readerOpts = def {P.readerExtensions = exts}
+        let readerOpts = defaultReaderOptions {P.readerExtensions = exts}
         case reader of 
             P.TextReader fct -> do
                 t <- liftIO $ Text.readFile fp 
src/Text/Pandoc/Filter/Plot/Configuration.hs view
@@ -19,7 +19,6 @@     , defaultConfiguration
 ) where
 
-import           Data.Default.Class     (Default, def)
 import           Data.Maybe             (fromMaybe)
 import           Data.Text              (Text, pack, unpack)
 import qualified Data.Text.IO           as TIO
@@ -28,7 +27,7 @@ 
 import           Text.Pandoc.Definition (Format(..), Pandoc(..), MetaValue(..), lookupMeta)
 
-import Text.Pandoc.Filter.Plot.Types
+import Text.Pandoc.Filter.Plot.Monad   
 
 -- | Read configuration from a YAML file. The
 -- keys are exactly the same as for code blocks.
@@ -43,9 +42,42 @@ --
 -- @since 0.5.0.0
 defaultConfiguration :: Configuration
-defaultConfiguration = def
+defaultConfiguration = 
+    Configuration
+        { defaultDirectory  = "plots/"
+        , defaultWithSource = False
+        , defaultDPI        = 80
+        , defaultSaveFormat = PNG
+        , captionFormat     = Format "markdown+tex_math_dollars"
 
+        , logVerbosity      = Warning
+        , logSink           = StdErr
+        
+        , matplotlibPreamble  = mempty
+        , plotlyPythonPreamble= mempty
+        , plotlyRPreamble     = mempty
+        , matlabPreamble      = mempty
+        , mathematicaPreamble = mempty
+        , octavePreamble      = mempty
+        , ggplot2Preamble     = mempty
+        , gnuplotPreamble     = mempty
+        , graphvizPreamble    = mempty
 
+        , matplotlibExe       = if isWindows then "python" else "python3"
+        , matlabExe           = "matlab"
+        , plotlyPythonExe     = if isWindows then "python" else "python3"
+        , plotlyRExe          = "Rscript"
+        , mathematicaExe      = "math"
+        , octaveExe           = "octave"
+        , ggplot2Exe          = "Rscript"
+        , gnuplotExe          = "gnuplot"
+        , graphvizExe         = "dot"
+        
+        , matplotlibTightBBox   = False
+        , matplotlibTransparent = False
+        }
+
+
 -- | Extact path to configuration from the metadata in a Pandoc document.
 -- The path to the configuration file should be under the @plot-configuration@ key.
 -- In case there is no such metadata, return the default configuration.
@@ -83,6 +115,8 @@     , _defaultSaveFormat :: !SaveFormat  -- ^ The default save format of generated figures.
     , _captionFormat     :: !Format      -- ^ Caption format in Pandoc notation, e.g. "markdown+tex_math_dollars".
     
+    , _logPrec           :: !LoggingPrecursor
+
     , _matplotlibPrec    :: !MatplotlibPrecursor
     , _matlabPrec        :: !MatlabPrecursor
     , _plotlyPythonPrec  :: !PlotlyPythonPrecursor
@@ -94,26 +128,33 @@     , _graphvizPrec      :: !GraphvizPrecursor
     }
 
-instance Default ConfigPrecursor where
-    def = ConfigPrecursor
-        { _defaultDirectory  = defaultDirectory def
-        , _defaultWithSource = defaultWithSource def
-        , _defaultDPI        = defaultDPI def
-        , _defaultSaveFormat = defaultSaveFormat def
-        , _captionFormat     = captionFormat def
+defaultConfigPrecursor :: ConfigPrecursor
+defaultConfigPrecursor =  
+    ConfigPrecursor
+        { _defaultDirectory  = defaultDirectory defaultConfiguration
+        , _defaultWithSource = defaultWithSource defaultConfiguration
+        , _defaultDPI        = defaultDPI defaultConfiguration
+        , _defaultSaveFormat = defaultSaveFormat defaultConfiguration
+        , _captionFormat     = captionFormat defaultConfiguration
+
+        , _logPrec           = LoggingPrecursor (logVerbosity defaultConfiguration) Nothing -- _logFilePath=Nothing implies log to stderr
         
-        , _matplotlibPrec    = def
-        , _matlabPrec        = def
-        , _plotlyPythonPrec  = def
-        , _plotlyRPrec       = def
-        , _mathematicaPrec   = def
-        , _octavePrec        = def
-        , _ggplot2Prec       = def
-        , _gnuplotPrec       = def
-        , _graphvizPrec      = def
+        , _matplotlibPrec    = MatplotlibPrecursor Nothing (matplotlibTightBBox defaultConfiguration) (matplotlibTransparent defaultConfiguration) (matplotlibExe defaultConfiguration)
+        , _matlabPrec        = MatlabPrecursor Nothing (matlabExe defaultConfiguration)
+        , _plotlyPythonPrec  = PlotlyPythonPrecursor Nothing (plotlyPythonExe defaultConfiguration)
+        , _plotlyRPrec       = PlotlyRPrecursor      Nothing (plotlyRExe defaultConfiguration)
+        , _mathematicaPrec   = MathematicaPrecursor  Nothing (mathematicaExe defaultConfiguration)
+        , _octavePrec        = OctavePrecursor       Nothing (octaveExe defaultConfiguration)
+        , _ggplot2Prec       = GGPlot2Precursor      Nothing (ggplot2Exe defaultConfiguration)
+        , _gnuplotPrec       = GNUPlotPrecursor      Nothing (gnuplotExe defaultConfiguration)
+        , _graphvizPrec      = GraphvizPrecursor     Nothing (graphvizExe defaultConfiguration)
         }
 
 
+data LoggingPrecursor = LoggingPrecursor { _logVerbosity :: !Verbosity
+                                         , _logFilePath  :: !(Maybe FilePath)
+                                         }
+
 -- Separate YAML clauses have their own types.
 data MatplotlibPrecursor = MatplotlibPrecursor
         { _matplotlibPreamble    :: !(Maybe FilePath)
@@ -128,83 +169,78 @@ data OctavePrecursor        = OctavePrecursor       {_octavePreamble       :: !(Maybe FilePath), _octaveExe       :: !FilePath}
 data GGPlot2Precursor       = GGPlot2Precursor      {_ggplot2Preamble      :: !(Maybe FilePath), _ggplot2Exe      :: !FilePath}
 data GNUPlotPrecursor       = GNUPlotPrecursor      {_gnuplotPreamble      :: !(Maybe FilePath), _gnuplotExe      :: !FilePath}
-data GraphvizPrecursor      = GraphvizPrecursor     {_graphvizPreamble     :: !(Maybe FilePath), _graphvizExe    :: !FilePath}
-
-
-instance Default MatplotlibPrecursor where
-    def = MatplotlibPrecursor Nothing (matplotlibTightBBox def) (matplotlibTransparent def) (matplotlibExe def)
+data GraphvizPrecursor      = GraphvizPrecursor     {_graphvizPreamble     :: !(Maybe FilePath), _graphvizExe     :: !FilePath}
 
-instance Default MatlabPrecursor        where def = MatlabPrecursor       Nothing (matlabExe def)
-instance Default PlotlyPythonPrecursor  where def = PlotlyPythonPrecursor Nothing (plotlyPythonExe def)
-instance Default PlotlyRPrecursor       where def = PlotlyRPrecursor      Nothing (plotlyRExe def)
-instance Default MathematicaPrecursor   where def = MathematicaPrecursor  Nothing (mathematicaExe def)
-instance Default OctavePrecursor        where def = OctavePrecursor       Nothing (octaveExe def)
-instance Default GGPlot2Precursor       where def = GGPlot2Precursor      Nothing (ggplot2Exe def)
-instance Default GNUPlotPrecursor       where def = GNUPlotPrecursor      Nothing (gnuplotExe def)
-instance Default GraphvizPrecursor      where def = GraphvizPrecursor     Nothing (graphvizExe def)
+instance FromJSON LoggingPrecursor where
+    parseJSON (Object v) = 
+        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 def) 
-            <*> v .:? (tshow MatplotlibTransparentK) .!= (matplotlibTransparent def)
-            <*> v .:? (tshow ExecutableK)  .!= (matplotlibExe def)
+            <*> v .:? (tshow MatplotlibTightBBoxK)   .!= (matplotlibTightBBox defaultConfiguration) 
+            <*> v .:? (tshow MatplotlibTransparentK) .!= (matplotlibTransparent defaultConfiguration)
+            <*> v .:? (tshow ExecutableK)  .!= (matplotlibExe 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 def)
+    parseJSON (Object v) = MatlabPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (matlabExe 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 def)
+    parseJSON (Object v) = PlotlyPythonPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyPythonExe 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 def)
+    parseJSON (Object v) = PlotlyRPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyRExe 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 def)
+    parseJSON (Object v) = MathematicaPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (mathematicaExe 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 def)
+    parseJSON (Object v) = OctavePrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (octaveExe 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 def)
+    parseJSON (Object v) = GGPlot2Precursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (ggplot2Exe 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 def)
+    parseJSON (Object v) = GNUPlotPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (gnuplotExe 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 def)
+    parseJSON (Object v) = GraphvizPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (graphvizExe defaultConfiguration)
     parseJSON _ = fail $ mconcat ["Could not parse ", show Graphviz, " configuration."]
 
 
 instance FromJSON ConfigPrecursor where
-    parseJSON (Null) = return def -- In case of empty file
+    parseJSON (Null) = return defaultConfigPrecursor -- In case of empty file
     parseJSON (Object v) = do
         
-        _defaultDirectory  <- v .:? (tshow DirectoryK)     .!= (_defaultDirectory def)
-        _defaultWithSource <- v .:? (tshow WithSourceK)    .!= (_defaultWithSource def)
-        _defaultDPI        <- v .:? (tshow DpiK)           .!= (_defaultDPI def)
-        _defaultSaveFormat <- v .:? (tshow SaveFormatK)    .!= (_defaultSaveFormat def)
-        _captionFormat     <- v .:? (tshow CaptionFormatK) .!= (_captionFormat def)
+        _defaultDirectory  <- v .:? (tshow DirectoryK)     .!= (_defaultDirectory defaultConfigPrecursor)
+        _defaultWithSource <- v .:? (tshow WithSourceK)    .!= (_defaultWithSource defaultConfigPrecursor)
+        _defaultDPI        <- v .:? (tshow DpiK)           .!= (_defaultDPI defaultConfigPrecursor)
+        _defaultSaveFormat <- v .:? (tshow SaveFormatK)    .!= (_defaultSaveFormat defaultConfigPrecursor)
+        _captionFormat     <- v .:? (tshow CaptionFormatK) .!= (_captionFormat defaultConfigPrecursor)
 
-        _matplotlibPrec    <- v .:? (cls Matplotlib)       .!= def
-        _matlabPrec        <- v .:? (cls Matlab)           .!= def
-        _plotlyPythonPrec  <- v .:? (cls PlotlyPython)     .!= def
-        _plotlyRPrec       <- v .:? (cls PlotlyR)          .!= def
-        _mathematicaPrec   <- v .:? (cls Mathematica)      .!= def
-        _octavePrec        <- v .:? (cls Octave)           .!= def
-        _ggplot2Prec       <- v .:? (cls GGPlot2)          .!= def
-        _gnuplotPrec       <- v .:? (cls GNUPlot)          .!= def
-        _graphvizPrec      <- v .:? (cls Graphviz)         .!= def
+        _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
+
         return $ ConfigPrecursor{..}
     parseJSON _          = fail "Could not parse configuration."
 
@@ -216,6 +252,9 @@         defaultDPI        = _defaultDPI
         defaultSaveFormat = _defaultSaveFormat
         captionFormat     = _captionFormat
+
+        logVerbosity = _logVerbosity _logPrec
+        logSink      = maybe StdErr LogFile (_logFilePath _logPrec)
 
         matplotlibTightBBox   = _matplotlibTightBBox _matplotlibPrec
         matplotlibTransparent = _matplotlibTransparent _matplotlibPrec
src/Text/Pandoc/Filter/Plot/Internal.hs view
@@ -12,17 +12,17 @@ -}
 
 module Text.Pandoc.Filter.Plot.Internal (
-      module Text.Pandoc.Filter.Plot.Types
-    , module Text.Pandoc.Filter.Plot.Renderers
+      module Text.Pandoc.Filter.Plot.Renderers
     , module Text.Pandoc.Filter.Plot.Scripting
     , module Text.Pandoc.Filter.Plot.Parse
     , module Text.Pandoc.Filter.Plot.Configuration
     , module Text.Pandoc.Filter.Plot.Clean
+    , module Text.Pandoc.Filter.Plot.Monad
  ) where
 
 import Text.Pandoc.Filter.Plot.Parse
 import Text.Pandoc.Filter.Plot.Renderers
 import Text.Pandoc.Filter.Plot.Scripting
-import Text.Pandoc.Filter.Plot.Types
 import Text.Pandoc.Filter.Plot.Configuration
-import Text.Pandoc.Filter.Plot.Clean+import Text.Pandoc.Filter.Plot.Clean
+import Text.Pandoc.Filter.Plot.Monad
+ src/Text/Pandoc/Filter/Plot/Monad.hs view
@@ -0,0 +1,152 @@+{-# LANGUAGE OverloadedStrings #-}
+{-|
+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 the @PlotM@ monad and related capabilities.
+-}
+
+module Text.Pandoc.Filter.Plot.Monad (
+      Configuration(..)
+    , PlotM
+    , runPlotM
+    -- * Running external commands
+    , runCommand
+    -- * Logging
+    , Verbosity(..)
+    , LogSink(..)
+    , debug
+    , err
+    , warning
+    , info
+    -- * Lifting and other monadic operations
+    , liftIO
+    , ask
+    , asks
+    -- * Base types
+    , module Text.Pandoc.Filter.Plot.Monad.Types
+) where
+
+import           Control.Monad.Reader
+
+import           Data.ByteString.Lazy        (toStrict)
+import           Data.Text                   (Text, pack, unpack)
+import           Data.Text.Encoding          (decodeUtf8With)
+import           Data.Text.Encoding.Error    (lenientDecode)
+
+import           System.Exit                 (ExitCode (..))
+import           System.Process.Typed        ( readProcessStderr, shell, nullStream
+                                             , setStdout, setStderr, byteStringOutput
+                                             )
+
+import           Text.Pandoc.Definition      (Format(..))
+
+import           Prelude                     hiding (log, fst, snd)
+
+import Text.Pandoc.Filter.Plot.Monad.Logging
+import Text.Pandoc.Filter.Plot.Monad.Types
+
+-- | pandoc-plot monad
+type PlotM a = ReaderT Configuration LoggingM a
+
+
+-- | Evaluate a @PlotM@ action 
+runPlotM :: Configuration -> PlotM a -> IO a
+runPlotM conf v = 
+    let verbosity = logVerbosity conf
+        sink      = logSink conf 
+    in runLoggingM verbosity sink (runReaderT v conf)
+
+
+debug :: Text -> PlotM ()
+debug t = lift $ log "DEBUG| " Debug t
+
+
+err :: Text -> PlotM ()
+err t = lift $ log "ERROR| " Error t
+
+
+warning :: Text -> PlotM ()
+warning t = lift $ log "WARN | " Warning t
+
+
+info :: Text -> PlotM ()
+info t = lift $ log "INFO | " Info t
+
+
+-- | Run a command within the @PlotM@ monad. Stdout and Stderr
+-- are read and decoded. Logging happens at the debug level.
+runCommand :: Text -> PlotM (ExitCode, Text)
+runCommand command = do
+    (ec, processOutput') <- liftIO 
+                        $ readProcessStderr 
+                        $ setStdout nullStream
+                        $ setStderr byteStringOutput 
+                        $ shell (unpack command)
+    let processOutput = decodeUtf8With lenientDecode $ toStrict processOutput'
+    debug $ mconcat [ "Running command\n"
+                    , "    ", command, "\n"
+                    , "ended with exit code ", pack . show $ ec
+                    ,  if processOutput /= mempty then " and output\n" <> "    " <> processOutput else mempty
+                    , "\n"
+                    ] 
+    return (ec, processOutput)
+
+
+-- | The @Configuration@ type holds the default values to use
+-- when running pandoc-plot. These values can be overridden in code blocks.
+--
+-- You can create an instance of the @Configuration@ type from file using the @configuration@ function.
+--
+-- You can store the path to a configuration file in metadata under the key @plot-configuration@. For example, in Markdown:
+--
+-- @
+--     ---
+--     title: My document
+--     author: John Doe
+--     plot-configuration: /path/to/file.yml
+--     ---     
+-- @
+--
+-- The same can be specified via the command line using Pandoc's @-M@ flag:
+--
+-- > pandoc --filter pandoc-plot -M plot-configuration="path/to/file.yml" ...
+--
+-- In this case, use @configurationPathMeta@ to extact the path from @Pandoc@ documents.
+data Configuration = Configuration
+    { defaultDirectory      :: !FilePath   -- ^ The default directory where figures will be saved.
+    , defaultWithSource     :: !Bool       -- ^ The default behavior of whether or not to include links to source code and high-res
+    , defaultDPI            :: !Int        -- ^ The default dots-per-inch value for generated figures. Renderers might ignore this.
+    , defaultSaveFormat     :: !SaveFormat -- ^ The default save format of generated figures.
+    , captionFormat         :: !Format     -- ^ Caption format, in the same notation as Pandoc format, e.g. "markdown+tex_math_dollars"
+
+    , logVerbosity          :: !Verbosity  -- ^ Level of logging verbosity.
+    , logSink               :: !LogSink    -- ^ Method of logging, i.e. printing to stderr or file.
+
+    , matplotlibPreamble    :: !Script     -- ^ The default preamble script for the matplotlib toolkit.
+    , plotlyPythonPreamble  :: !Script     -- ^ The default preamble script for the Plotly/Python toolkit.
+    , plotlyRPreamble       :: !Script     -- ^ The default preamble script for the Plotly/R toolkit.
+    , matlabPreamble        :: !Script     -- ^ The default preamble script for the MATLAB toolkit.
+    , mathematicaPreamble   :: !Script     -- ^ The default preamble script for the Mathematica toolkit.
+    , octavePreamble        :: !Script     -- ^ The default preamble script for the GNU Octave toolkit.
+    , ggplot2Preamble       :: !Script     -- ^ The default preamble script for the GGPlot2 toolkit.
+    , gnuplotPreamble       :: !Script     -- ^ The default preamble script for the gnuplot toolkit.
+    , graphvizPreamble      :: !Script     -- ^ The default preamble script for the Graphviz toolkit.
+    
+    , matplotlibExe         :: !FilePath   -- ^ The executable to use to generate figures using the matplotlib toolkit.
+    , matlabExe             :: !FilePath   -- ^ The executable to use to generate figures using the MATLAB toolkit.
+    , plotlyPythonExe       :: !FilePath   -- ^ The executable to use to generate figures using the Plotly/Python toolkit.
+    , plotlyRExe            :: !FilePath   -- ^ The executable to use to generate figures using the Plotly/R toolkit.
+    , mathematicaExe        :: !FilePath   -- ^ The executable to use to generate figures using the Mathematica toolkit.
+    , octaveExe             :: !FilePath   -- ^ The executable to use to generate figures using the GNU Octave toolkit.
+    , ggplot2Exe            :: !FilePath   -- ^ The executable to use to generate figures using the GGPlot2 toolkit.
+    , gnuplotExe            :: !FilePath   -- ^ The executable to use to generate figures using the gnuplot toolkit.
+    , graphvizExe           :: !FilePath   -- ^ The executable to use to generate figures using the Graphviz toolkit.
+    
+    , matplotlibTightBBox   :: !Bool       -- ^ Whether or not to make Matplotlib figures tight by default.
+    , matplotlibTransparent :: !Bool       -- ^ Whether or not to make Matplotlib figures transparent by default.
+    } deriving (Eq, Show)
+ src/Text/Pandoc/Filter/Plot/Monad/Logging.hs view
@@ -0,0 +1,116 @@+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+{-|
+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
+
+Logging primitives.
+-}
+
+module Text.Pandoc.Filter.Plot.Monad.Logging 
+    ( Verbosity(..)
+    , LogSink(..)
+    , LoggingM
+    , runLoggingM
+    , log
+    ) where
+
+
+import           Control.Monad.Trans         (liftIO)
+import           Control.Monad.Writer.Strict (WriterT, runWriterT, tell)
+
+import           Data.Char                   (toLower)
+import           Data.List                   (sortOn)
+import           Data.String                 (IsString(..))
+import           Data.Text                   (Text, unpack)
+import qualified Data.Text                   as T
+import           Data.Text.IO                (hPutStr)
+import           Data.Time.Clock.System      (getSystemTime, SystemTime(..))
+import           Data.Yaml
+
+import           System.IO                   (stderr, withFile, IOMode (AppendMode) )
+
+import           Prelude                     hiding (log, fst, snd)
+
+
+-- | Verbosity of the logger.
+data Verbosity = Debug    -- ^ Log all messages, including debug messages.
+               | Error    -- ^ Log information, warnings, and errors.
+               | Warning  -- ^ Log information and warning messages.
+               | Info     -- ^ Only log information messages.
+               | Silent   -- ^ Don't log anything. 
+               deriving (Eq, Ord, Show)
+
+
+-- | Description of the possible ways to sink log messages.
+data LogSink = StdErr           -- ^ Standard error stream.
+             | LogFile FilePath -- ^ Appended to file.
+             deriving (Eq, Show)
+
+type LogMessage = (Verbosity, SystemTime, Text)
+
+type LoggingM = WriterT [LogMessage] IO
+
+
+runLoggingM :: Verbosity -> LogSink -> LoggingM a -> IO a
+runLoggingM Silent _       = runLoggingM' Silent $ mapM_ (return . trd)
+runLoggingM v StdErr       = runLoggingM' v $ mapM_ (hPutStr stderr . trd)
+runLoggingM v (LogFile fp) = runLoggingM' v $ mapM_ (\m -> withFile fp AppendMode $ \h -> hPutStr h (trd m))
+
+
+runLoggingM' :: Verbosity                -- ^ Minimum verbosity to keep
+             -> ([LogMessage] -> IO ())  -- ^ Log sink
+             -> LoggingM a
+             -> IO a
+runLoggingM' v f m = do
+    (r, t) <- runWriterT m
+    -- Messages with lower level than minimum are discarded
+    -- We also re-order messages to be chronological
+    let t' = sortOn snd $ filter (\message -> fst message >= v) t
+    liftIO $ f t'
+    return r
+
+
+-- | General logging function.
+-- Input text will be decomposed into lines, with each
+-- line becoming a log line.    
+log :: Text       -- ^ Header  
+    -> Verbosity  
+    -> Text       -- ^ Message
+    -> LoggingM ()
+log h v t = do
+    timestamp <- liftIO $ getSystemTime
+    tell [(v, timestamp, h <> l <> "\n") | l <- T.lines t]
+
+
+instance IsString Verbosity where
+    fromString s
+        | ls == "silent"  = Silent
+        | ls == "info"    = Info
+        | ls == "warning" = Warning
+        | ls == "error"   = Error
+        | ls == "debug"   = Debug
+        | otherwise = error $ "Unrecognized verbosity " <> s
+        where
+            ls = toLower <$> s
+
+instance FromJSON Verbosity where
+    parseJSON (String t) = pure $ fromString . unpack $ t
+    parseJSON _ = fail $ "Could not parse the logging verbosity."
+
+
+fst :: (a,b,c) -> a
+fst (a,_,_) = a
+
+
+snd :: (a,b,c) -> b
+snd (_,b,_) = b
+
+
+trd :: (a,b,c) -> c
+trd (_,_,c) = c
+ src/Text/Pandoc/Filter/Plot/Monad/Types.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+
+
+{-|
+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 base types in use in pandoc-plot
+-}
+
+module Text.Pandoc.Filter.Plot.Monad.Types (
+      Toolkit(..)
+    , Script
+    , CheckResult(..)
+    , InclusionKey(..)
+    , FigureSpec(..)
+    , SaveFormat(..)
+    , figureContentHash
+    , cls
+    , extension
+    , toolkits
+    , inclusionKeys
+    -- Utilities
+    , isWindows
+) where
+
+import           Data.Char              (toLower)
+import           Data.Hashable          (hash)
+import           Data.List              (intersperse)
+import           Data.Semigroup         (Semigroup (..))
+import           Data.String            (IsString (..))
+import           Data.Text              (Text)
+import           Data.Yaml
+
+import           GHC.Generics           (Generic)
+import           System.Info            (os)
+
+import           Text.Pandoc.Definition (Attr)
+
+
+toolkits :: [Toolkit]
+toolkits = enumFromTo minBound maxBound
+
+
+-- | Enumeration of supported toolkits
+data Toolkit
+    = Matplotlib
+    | Matlab
+    | PlotlyPython
+    | PlotlyR
+    | Mathematica
+    | Octave
+    | GGPlot2
+    | GNUPlot
+    | Graphviz
+    deriving (Bounded, Eq, Enum, Generic)
+
+
+-- | This instance should only be used to display toolkit names
+instance Show Toolkit where
+    show Matplotlib   = "Python/Matplotlib"
+    show Matlab       = "MATLAB"
+    show PlotlyPython = "Python/Plotly"
+    show PlotlyR      = "R/Plotly"
+    show Mathematica  = "Mathematica"
+    show Octave       = "GNU Octave"
+    show GGPlot2      = "ggplot2"
+    show GNUPlot      = "gnuplot"
+    show Graphviz     = "graphviz"
+
+
+-- | Class name which will trigger the filter
+cls :: Toolkit -> Text
+cls Matplotlib   = "matplotlib"
+cls Matlab       = "matlabplot"
+cls PlotlyPython = "plotly_python"
+cls PlotlyR      = "plotly_r"
+cls Mathematica  = "mathplot"
+cls Octave       = "octaveplot"
+cls GGPlot2      = "ggplot2"
+cls GNUPlot      = "gnuplot"
+cls Graphviz     = "graphviz"
+
+
+type Script = Text
+
+
+-- | Result of checking scripts for problems
+data CheckResult
+    = CheckPassed
+    | CheckFailed Text
+    deriving (Eq)
+
+instance Semigroup CheckResult where
+    (<>) CheckPassed a                         = a
+    (<>) a CheckPassed                         = a
+    (<>) (CheckFailed msg1) (CheckFailed msg2) = CheckFailed (msg1 <> msg2)
+
+instance Monoid CheckResult where
+    mempty = CheckPassed
+
+
+-- | Description of any possible inclusion key, both in documents
+-- and in configuration files.
+data InclusionKey
+    = DirectoryK
+    | CaptionK
+    | SaveFormatK
+    | WithSourceK
+    | CaptionFormatK
+    | PreambleK
+    | DpiK
+    | ExecutableK
+    | MatplotlibTightBBoxK
+    | MatplotlibTransparentK
+    deriving (Bounded, Eq, Enum)
+
+-- | Keys that pandoc-plot will look for in code blocks.
+-- These are only exported for testing purposes.
+instance Show InclusionKey where
+    show DirectoryK             = "directory"
+    show CaptionK               = "caption"
+    show SaveFormatK            = "format"
+    show WithSourceK            = "source"
+    show CaptionFormatK         = "caption_format"
+    show PreambleK              = "preamble"
+    show DpiK                   = "dpi"
+    show ExecutableK            = "executable"
+    show MatplotlibTightBBoxK   = "tight_bbox"
+    show MatplotlibTransparentK = "transparent"
+
+
+-- | List of all keys related to pandoc-plot that
+-- can be specified in source material.
+inclusionKeys :: [InclusionKey]
+inclusionKeys = enumFromTo (minBound::InclusionKey) maxBound
+
+
+-- | Datatype containing all parameters required to run pandoc-plot.
+--
+-- It is assumed that once a @FigureSpec@ has been created, no configuration
+-- can overload it; hence, a @FigureSpec@ completely encodes a particular figure.
+data FigureSpec = FigureSpec
+    { toolkit    :: !Toolkit        -- ^ Plotting toolkit to use for this figure.
+    , caption    :: !Text           -- ^ Figure caption.
+    , withSource :: !Bool           -- ^ Append link to source code in caption.
+    , script     :: !Script         -- ^ Source code for the figure.
+    , saveFormat :: !SaveFormat     -- ^ Save format of the figure.
+    , directory  :: !FilePath       -- ^ Directory where to save the file.
+    , dpi        :: !Int            -- ^ Dots-per-inch of figure.
+    , extraAttrs :: ![(Text, Text)] -- ^ Renderer-specific extra attributes.
+    , blockAttrs :: !Attr           -- ^ Attributes not related to @pandoc-plot@ will be propagated.
+    }
+
+
+-- | Hash of the content of a @FigureSpec@. Note that unlike usual hashes,
+-- two @FigureSpec@ with the same @figureContentHash@ does not mean that they are equal!
+--
+-- Not all parts of a FigureSpec are related to running code.
+-- For example, changing the caption should not require running the figure again.
+figureContentHash :: FigureSpec -> Int
+figureContentHash FigureSpec{..} = 
+    hash (fromEnum toolkit, script, fromEnum saveFormat, directory, dpi, extraAttrs)
+
+
+-- | Generated figure file format supported by pandoc-plot.
+-- Note that not all formats are supported by all toolkits.
+data SaveFormat
+    = PNG   -- ^ Portable network graphics
+    | PDF   -- ^ Portable document format
+    | SVG   -- ^ Scalable vector graphics
+    | JPG   -- ^ JPEG/JPG compressed image
+    | EPS   -- ^ Encapsulated postscript
+    | GIF   -- ^ GIF format
+    | TIF   -- ^ Tagged image format
+    | WEBP  -- ^ WebP image format
+    deriving (Bounded, Enum, Eq, Show, Generic)
+
+instance IsString SaveFormat where
+    -- | An error is thrown if the save format cannot be parsed. That's OK
+    -- since pandoc-plot is a command-line tool and isn't expected to run
+    -- long.
+    fromString s
+        | s `elem` ["png", "PNG", ".png"] = PNG
+        | s `elem` ["pdf", "PDF", ".pdf"] = PDF
+        | s `elem` ["svg", "SVG", ".svg"] = SVG
+        | s `elem` ["eps", "EPS", ".eps"] = EPS
+        | s `elem` ["gif", "GIF", ".gif"] = GIF
+        | s `elem` ["jpg", "jpeg", "JPG", "JPEG", ".jpg", ".jpeg"] = JPG
+        | s `elem` ["tif", "tiff", "TIF", "TIFF", ".tif", ".tiff"] = TIF
+        | s `elem` ["webp", "WEBP", ".webp"] = WEBP
+        | otherwise = error $
+                mconcat [ s
+                        , " is not one of valid save format : "
+                        , mconcat $ intersperse ", " $ show <$> saveFormats
+                        ]
+        where
+            saveFormats =  (enumFromTo minBound maxBound) :: [SaveFormat]
+
+instance FromJSON SaveFormat -- TODO: test this parsing
+
+instance ToJSON SaveFormat where
+    toJSON = toJSON . extension
+
+
+-- | Save format file extension
+extension :: SaveFormat -> String
+extension fmt = mconcat [".", fmap toLower . show $ fmt]
+
+
+isWindows :: Bool
+isWindows = os `elem` ["mingw32", "win32", "cygwin32"] -- Aliases taken from cabal's Distribution.System module
src/Text/Pandoc/Filter/Plot/Parse.hs view
@@ -19,12 +19,11 @@       plotToolkit
     , parseFigureSpec
     , captionReader
+    , defaultReaderOptions
 ) 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)
@@ -41,11 +40,12 @@                                                     Pandoc (..), Format(..))
 
 import           Text.Pandoc.Class                 (runPure)
-import           Text.Pandoc.Options               (ReaderOptions (..))
+import           Text.Pandoc.Extensions            (emptyExtensions)
+import           Text.Pandoc.Options               (ReaderOptions (..), TrackChanges(..))
 import           Text.Pandoc.Readers               (getReader, Reader(..))
 
 import           Text.Pandoc.Filter.Plot.Renderers
-import           Text.Pandoc.Filter.Plot.Types
+import           Text.Pandoc.Filter.Plot.Monad
 
 tshow :: Show a => a -> Text
 tshow = pack . show
@@ -54,20 +54,22 @@ -- 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
+parseFigureSpec block@(CodeBlock (id', classes, attrs) content) = do
+    let toolkit = plotToolkit block
+    case toolkit of
+        Nothing -> return Nothing
+        Just tk -> do
+            if not (cls tk `elem` classes)
+                then return Nothing
+                else Just <$> figureSpec tk
 
     where
         attrs'        = Map.fromList attrs
         preamblePath  = unpack <$> Map.lookup (tshow PreambleK) attrs'
 
-        figureSpec :: PlotM FigureSpec
-        figureSpec = do
-            conf <- asks config
-            toolkit <- asks toolkit
+        figureSpec :: Toolkit -> PlotM FigureSpec
+        figureSpec toolkit = do
+            conf <- ask
             let extraAttrs' = parseExtraAttrs toolkit attrs'
                 header = comment toolkit $ "Generated by pandoc-plot " <> ((pack . showVersion) version)
                 defaultPreamble = preambleSelector toolkit conf
@@ -94,7 +96,8 @@             -- 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 ])
+                let msg = pack $ mconcat ["Save format ", show saveFormat, " not supported by ", show toolkit ]
+                err msg
             return FigureSpec{..}
 
 parseFigureSpec _ = return Nothing
@@ -112,7 +115,7 @@ 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}
+    let readerOpts = defaultReaderOptions {readerExtensions = exts}
     -- Assuming no ByteString readers...
     case reader of
         TextReader fct -> fct readerOpts t
@@ -131,3 +134,19 @@ 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'"]
+
+
+-- | Default reader options, straight out of Text.Pandoc.Options
+defaultReaderOptions :: ReaderOptions
+defaultReaderOptions = 
+    ReaderOptions
+        { readerExtensions            = emptyExtensions
+        , readerStandalone            = False
+        , readerColumns               = 80
+        , readerTabStop               = 4
+        , readerIndentedCodeClasses   = []
+        , readerAbbreviations         = mempty
+        , readerDefaultImageExtension = ""
+        , readerTrackChanges          = AcceptChanges
+        , readerStripComments         = False
+        }
src/Text/Pandoc/Filter/Plot/Renderers.hs view
@@ -24,11 +24,13 @@     , executable
     , toolkitAvailable
     , availableToolkits
+    , availableToolkitsM
     , unavailableToolkits
+    , unavailableToolkitsM
     , OutputSpec(..)
 ) where
 
-import           Control.Concurrent.Async                      (mapConcurrently)
+import           Control.Concurrent.Async                      (forConcurrently)
 
 import           Data.List                                     ((\\))
 import           Data.Map.Strict                               (Map)
@@ -46,7 +48,7 @@ import           Text.Pandoc.Filter.Plot.Renderers.Graphviz
 import           Text.Pandoc.Filter.Plot.Renderers.Prelude     (executable, OutputSpec(..))
 
-import           Text.Pandoc.Filter.Plot.Types
+import           Text.Pandoc.Filter.Plot.Monad
 
 
 -- Extension for script files, e.g. ".py", or ".m".
@@ -120,7 +122,7 @@ -- The executable will need to be found first, hence the IO monad.
 command :: Toolkit 
         -> OutputSpec
-        -> IO Text
+        -> PlotM Text
 command Matplotlib   = matplotlibCommand
 command PlotlyPython = plotlyPythonCommand
 command PlotlyR      = plotlyRCommand
@@ -146,7 +148,7 @@ 
 
 -- | Check if a toolkit is available, based on the current configuration
-toolkitAvailable :: Toolkit -> Configuration -> IO Bool
+toolkitAvailable :: Toolkit -> PlotM Bool
 toolkitAvailable Matplotlib   = matplotlibAvailable
 toolkitAvailable PlotlyPython = plotlyPythonAvailable
 toolkitAvailable PlotlyR      = plotlyRAvailable
@@ -161,16 +163,27 @@ -- | List of toolkits available on this machine.
 -- The executables to look for are taken from the configuration.
 availableToolkits :: Configuration -> IO [Toolkit]
-availableToolkits conf = catMaybes <$> (mapConcurrently maybeToolkit toolkits)
-    where
-        maybeToolkit tk = do
-            available <- toolkitAvailable tk conf
-            if available
-                then return $ Just tk
-                else return Nothing
+availableToolkits conf = runPlotM conf availableToolkitsM
 
-    
+
 -- | List of toolkits not available on this machine.
--- The executables to look for are taken from the configuration.
+-- The executables to look for are taken from the configur
 unavailableToolkits :: Configuration -> IO [Toolkit]
-unavailableToolkits conf = ((\\) toolkits) <$> availableToolkits conf
+unavailableToolkits conf = runPlotM conf unavailableToolkitsM
+
+
+-- | Monadic version of @availableToolkits@.
+availableToolkitsM :: PlotM [Toolkit]
+availableToolkitsM = do
+    conf <- ask
+    mtks <- liftIO $ forConcurrently toolkits $  \tk -> do
+        available <- runPlotM conf $ toolkitAvailable tk
+        if available
+            then return $ Just tk
+            else return Nothing
+    return $ catMaybes mtks
+
+    
+-- | Monadic version of @unavailableToolkits@
+unavailableToolkitsM :: PlotM [Toolkit]
+unavailableToolkitsM = (\\) toolkits <$> availableToolkitsM
src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs view
@@ -27,15 +27,15 @@ ggplot2SupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, TIF]
 
 
-ggplot2Command :: OutputSpec -> IO Text
+ggplot2Command :: OutputSpec -> PlotM Text
 ggplot2Command OutputSpec{..} = do
-    exe <- executable GGPlot2 oConfiguration
+    exe <- executable GGPlot2
     return [st|#{exe} "#{oScriptPath}"|]
 
 
-ggplot2Available :: Configuration -> IO Bool
-ggplot2Available conf = do
-    exe <- executable GGPlot2 conf
+ggplot2Available :: PlotM Bool
+ggplot2Available = do
+    exe <- executable GGPlot2
     commandSuccess [st|#{exe} -e 'library("ggplot2")'|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs view
@@ -26,15 +26,15 @@ gnuplotSupportedSaveFormats = [PNG, SVG, EPS, GIF, JPG, PDF]
 
 
-gnuplotCommand :: OutputSpec -> IO Text
+gnuplotCommand :: OutputSpec -> PlotM Text
 gnuplotCommand OutputSpec{..} = do
-    exe <- executable GNUPlot oConfiguration
+    exe <- executable GNUPlot
     return [st|#{exe} -c "#{oScriptPath}"|]
 
 
-gnuplotAvailable :: Configuration -> IO Bool
-gnuplotAvailable conf = do
-    exe <- executable GNUPlot conf
+gnuplotAvailable :: PlotM Bool
+gnuplotAvailable = do
+    exe <- executable GNUPlot 
     commandSuccess [st|#{exe} -h|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs view
@@ -28,16 +28,16 @@ graphvizSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, WEBP, GIF]
 
 
-graphvizCommand :: OutputSpec -> IO Text
+graphvizCommand :: OutputSpec -> PlotM Text
 graphvizCommand OutputSpec{..} = do
-    exe <- executable Graphviz oConfiguration
+    exe <- executable Graphviz
     let fmt = fmap toLower . show . saveFormat $ oFigureSpec
     return [st|#{exe} -T#{fmt} -o "#{oFigurePath}" "#{oScriptPath}"|]
 
 
-graphvizAvailable :: Configuration -> IO Bool
-graphvizAvailable conf = do
-    exe <- executable Graphviz conf
+graphvizAvailable :: PlotM Bool
+graphvizAvailable = do
+    exe <- executable Graphviz
     commandSuccess [st|#{exe} -?|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs view
@@ -26,15 +26,15 @@ mathematicaSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
 
 
-mathematicaCommand :: OutputSpec -> IO Text
+mathematicaCommand :: OutputSpec -> PlotM Text
 mathematicaCommand OutputSpec{..} = do
-    exe <- executable Mathematica oConfiguration
+    exe <- executable Mathematica
     return [st|#{exe} -script "#{oScriptPath}"|]
 
 
-mathematicaAvailable :: Configuration -> IO Bool
-mathematicaAvailable conf = do
-    exe <- executable Mathematica conf
+mathematicaAvailable :: PlotM Bool
+mathematicaAvailable = do
+    exe <- executable Mathematica
     commandSuccess [st|#{exe} -h|] -- TODO: test this
 
 
src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs view
@@ -29,9 +29,9 @@ matlabSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
 
 
-matlabCommand :: OutputSpec -> IO Text
+matlabCommand :: OutputSpec -> PlotM Text
 matlabCommand OutputSpec{..} = do
-    exe <- executable Matlab oConfiguration
+    exe <- executable Matlab
     return [st|#{exe} -batch "run('#{oScriptPath}')"|]
 
 
@@ -39,8 +39,8 @@ -- help text is shown successfully!
 -- Therefore, we cannot rely on this behavior to know if matlab is present, 
 -- like other toolkits.
-matlabAvailable :: Configuration -> IO Bool
-matlabAvailable Configuration{..} = existsOnPath (matlabExe <> exeExtension)
+matlabAvailable :: PlotM Bool
+matlabAvailable = asks matlabExe >>= (\exe -> liftIO $ existsOnPath (exe <> exeExtension))
 
 
 matlabCapture :: FigureSpec -> FilePath -> Script
src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs view
@@ -36,9 +36,9 @@ matplotlibSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
 
 
-matplotlibCommand :: OutputSpec -> IO Text
+matplotlibCommand :: OutputSpec -> PlotM Text
 matplotlibCommand OutputSpec{..} = do
-    exe <- executable Matplotlib oConfiguration
+    exe <- executable Matplotlib
     return [st|#{exe} "#{oScriptPath}"|]
 
 
@@ -58,9 +58,9 @@ matplotlibExtraAttrs kv = M.filterWithKey (\k _ -> k `elem` ["tight_bbox", "transparent"]) kv
 
 
-matplotlibAvailable :: Configuration -> IO Bool
-matplotlibAvailable conf = do
-    exe <- executable Matplotlib conf
+matplotlibAvailable :: PlotM Bool
+matplotlibAvailable = do
+    exe <- executable Matplotlib
     commandSuccess [st|#{exe} -c "import matplotlib"|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs view
@@ -27,15 +27,15 @@ octaveSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
 
 
-octaveCommand :: OutputSpec -> IO Text
+octaveCommand :: OutputSpec -> PlotM Text
 octaveCommand OutputSpec{..} = do
-    exe <- executable Octave oConfiguration
+    exe <- executable Octave
     return [st|#{exe} --no-gui --no-window-system "#{oScriptPath}"|]
 
 
-octaveAvailable :: Configuration -> IO Bool
-octaveAvailable conf = do
-    exe <- executable Octave conf
+octaveAvailable :: PlotM Bool
+octaveAvailable = do
+    exe <- executable Octave
     commandSuccess [st|#{exe} -h|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs view
@@ -27,15 +27,15 @@ plotlyPythonSupportedSaveFormats = [PNG, JPG, WEBP, PDF, SVG, EPS]
 
 
-plotlyPythonCommand :: OutputSpec -> IO Text
+plotlyPythonCommand :: OutputSpec -> PlotM Text
 plotlyPythonCommand OutputSpec{..} = do
-    exe <- executable PlotlyPython oConfiguration
+    exe <- executable PlotlyPython
     return [st|#{exe} "#{oScriptPath}"|]
 
 
-plotlyPythonAvailable :: Configuration -> IO Bool
-plotlyPythonAvailable conf = do
-    exe <- executable PlotlyPython conf
+plotlyPythonAvailable :: PlotM Bool
+plotlyPythonAvailable = do
+    exe <- executable PlotlyPython
     commandSuccess [st|#{exe} -c "import plotly.graph_objects"|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs view
@@ -27,15 +27,15 @@ plotlyRSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS]
 
 
-plotlyRCommand :: OutputSpec -> IO Text
+plotlyRCommand :: OutputSpec -> PlotM Text
 plotlyRCommand OutputSpec{..} = do
-    exe <- executable PlotlyR oConfiguration
+    exe <- executable PlotlyR
     return [st|#{exe} "#{oScriptPath}"|]
 
 
-plotlyRAvailable :: Configuration -> IO Bool
-plotlyRAvailable conf = do
-    exe <- executable GGPlot2 conf
+plotlyRAvailable :: PlotM Bool
+plotlyRAvailable = do
+    exe <- executable GGPlot2
     commandSuccess [st|#{exe} -e 'library("plotly")'|]
 
 
src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs view
@@ -13,7 +13,7 @@ module Text.Pandoc.Filter.Plot.Renderers.Prelude (
 
       module Prelude
-    , module Text.Pandoc.Filter.Plot.Types
+    , module Text.Pandoc.Filter.Plot.Monad
     , Text
     , st
     , unpack
@@ -28,30 +28,23 @@ 
 import           System.Directory              (findExecutable)
 import           System.Exit                   (ExitCode(..))
-import           System.Process.Typed          (runProcess, shell, 
-                                                setStdout, setStderr, 
-                                                nullStream)
-import           Text.Shakespeare.Text         (st)
 
-import qualified Turtle                         as Sh
+import           Text.Shakespeare.Text         (st)
 
-import           Text.Pandoc.Filter.Plot.Types
+import           Text.Pandoc.Filter.Plot.Monad
 
 
 -- | Check that the supplied command results in
 -- an exit code of 0 (i.e. no errors)
-commandSuccess :: Text -> IO Bool
+commandSuccess :: Text -> PlotM Bool
 commandSuccess s = do
-    ec <- runProcess 
-            $ setStdout nullStream 
-            $ setStderr nullStream 
-            $ shell (unpack s)
+    (ec, _) <- runCommand s
     return $ ec == ExitSuccess
 
 
 -- | Checks that an executable is available on path, at all.
 existsOnPath :: FilePath -> IO Bool
-existsOnPath fp = Sh.which (Sh.fromString fp) >>= fmap isJust . return
+existsOnPath fp = findExecutable fp >>= fmap isJust . return
 
 
 -- | Try to find the executable and normalise its path.
@@ -62,23 +55,22 @@ 
 -- | Path to the executable of a toolkit. If the executable can
 -- be found, then it will be the full path to it.
-executable :: Toolkit -> Configuration -> IO FilePath
-executable Matplotlib   = tryToFindExe . matplotlibExe
-executable PlotlyPython = tryToFindExe . plotlyPythonExe
-executable PlotlyR      = tryToFindExe . plotlyRExe
-executable Matlab       = tryToFindExe . matlabExe
-executable Mathematica  = tryToFindExe . mathematicaExe
-executable Octave       = tryToFindExe . octaveExe
-executable GGPlot2      = tryToFindExe . ggplot2Exe
-executable GNUPlot      = tryToFindExe . gnuplotExe
-executable Graphviz     = tryToFindExe . graphvizExe
+executable :: Toolkit -> PlotM FilePath
+executable Matplotlib   = asks matplotlibExe   >>= liftIO . tryToFindExe
+executable PlotlyPython = asks plotlyPythonExe >>= liftIO . tryToFindExe
+executable PlotlyR      = asks plotlyRExe      >>= liftIO . tryToFindExe
+executable Matlab       = asks matlabExe       >>= liftIO . tryToFindExe
+executable Mathematica  = asks mathematicaExe  >>= liftIO . tryToFindExe
+executable Octave       = asks octaveExe       >>= liftIO . tryToFindExe
+executable GGPlot2      = asks ggplot2Exe      >>= liftIO . tryToFindExe
+executable GNUPlot      = asks gnuplotExe      >>= liftIO . tryToFindExe
+executable Graphviz     = asks graphvizExe     >>= liftIO . tryToFindExe
 
 
 -- | Internal description of all information 
 -- needed to output a figure.
 data OutputSpec = OutputSpec 
-    { oConfiguration :: Configuration -- ^ Pandoc-plot configuration
-    , oFigureSpec    :: FigureSpec    -- ^ Figure spec
+    { oFigureSpec    :: FigureSpec    -- ^ Figure spec
     , oScriptPath    :: FilePath      -- ^ Path to the script to render
     , oFigurePath    :: FilePath      -- ^ Figure output path
     } 
src/Text/Pandoc/Filter/Plot/Scripting.hs view
@@ -23,17 +23,15 @@ 
 import           Data.Hashable                     (hash)
 import           Data.Maybe                        (fromMaybe)
-import qualified Data.Text                         as T
+import           Data.Text                         (Text, pack, unpack)
 import qualified Data.Text.IO                      as T
 
 import           System.Directory                  (createDirectoryIfMissing,
-                                                    doesFileExist)
+                                                    doesFileExist, getTemporaryDirectory)
 import           System.Exit                       (ExitCode (..))
 import           System.FilePath                   (addExtension,
                                                     normalise, replaceExtension,
                                                     takeDirectory, (</>))
-import           System.IO.Temp                    (getCanonicalTemporaryDirectory)
-import           System.Process.Typed              (runProcess, shell, setStdout, nullStream)
 
 import           Text.Pandoc.Builder               (fromList, imageWith, link,
                                                     para, toList)
@@ -41,15 +39,7 @@ 
 import           Text.Pandoc.Filter.Plot.Parse     (captionReader)
 import           Text.Pandoc.Filter.Plot.Renderers
-import           Text.Pandoc.Filter.Plot.Types
-
-
--- | Possible result of running a script
-data ScriptResult
-    = ScriptSuccess
-    | ScriptChecksFailed String   -- Message
-    | ScriptFailure String Int    -- Command and exit code
-    | ToolkitNotInstalled Toolkit -- Script failed because toolkit is not installed
+import           Text.Pandoc.Filter.Plot.Monad
 
 
 -- Run script as described by the spec, only if necessary
@@ -62,54 +52,67 @@                 then return ScriptSuccess
                 else runTempScript spec
 
+    logScriptResult result
+
     case result of
-        ScriptSuccess      -> liftIO $ T.writeFile (sourceCodePath spec) (script spec) >> return ScriptSuccess
-        other -> return other
+        ScriptSuccess -> liftIO $ T.writeFile (sourceCodePath spec) (script spec) >> return ScriptSuccess
+        other         -> return other
 
+    where
+        logScriptResult ScriptSuccess = return () 
+        logScriptResult r             = err   . pack . show $ r
 
+
+-- | Possible result of running a script
+data ScriptResult
+    = ScriptSuccess
+    | ScriptChecksFailed Text   -- Message
+    | ScriptFailure Text Int    -- Command and exit code
+    | ToolkitNotInstalled Toolkit -- Script failed because toolkit is not installed
+
+instance Show ScriptResult where
+    show ScriptSuccess            = "Script success."
+    show (ScriptChecksFailed msg) = unpack $ "Script checks failed: " <> msg
+    show (ScriptFailure msg ec)   = mconcat ["Script failed with exit code ", show ec, " and the following message: ", unpack msg]
+    show (ToolkitNotInstalled tk) = (show tk) <> " toolkit not installed."
+
+
 -- Run script as described by the spec
 -- Checks are performed, according to the renderer
 -- Note that stdout from the script is suppressed, but not
 -- stderr.
 runTempScript :: FigureSpec -> PlotM ScriptResult
 runTempScript spec@FigureSpec{..} = do
-    tk <- asks toolkit
-    conf <- asks config
-    let checks = scriptChecks tk
+    let checks = scriptChecks toolkit
         checkResult = mconcat $ checks <*> [script]
     case checkResult of
         CheckFailed msg -> return $ ScriptChecksFailed msg
         CheckPassed -> do
             scriptPath <- tempScriptPath spec
-            let captureFragment = (capture tk) spec (figurePath spec)
+            let captureFragment = (capture toolkit) spec (figurePath spec)
                 -- Note: for gnuplot, the capture string must be placed
                 --       BEFORE plotting happens. Since this is only really an
                 --       issue for gnuplot, we have a special case.
-                scriptWithCapture = if (tk == GNUPlot)
+                scriptWithCapture = if (toolkit == GNUPlot)
                                         then mconcat [captureFragment, "\n", script]
                                         else mconcat [script, "\n", captureFragment]
             liftIO $ T.writeFile scriptPath scriptWithCapture
-            let outputSpec = OutputSpec { oConfiguration = conf
-                                        , oFigureSpec = spec
+            let outputSpec = OutputSpec { oFigureSpec = spec
                                         , oScriptPath = scriptPath
                                         , oFigurePath = figurePath spec
                                         }
-            command_ <- T.unpack <$> (liftIO $ command tk outputSpec)
-
-            ec <- liftIO 
-                    $ runProcess 
-                    $ setStdout nullStream
-                    $ shell command_
+            command_ <- command toolkit outputSpec
+            (ec, _) <- runCommand command_
             case ec of
                 ExitSuccess      -> return   ScriptSuccess
                 ExitFailure code -> do
                     -- Two possible types of failures: either the script
                     -- failed because the toolkit was not available, or
                     -- because of a genuine error
-                    toolkitInstalled <- liftIO $ toolkitAvailable tk conf 
+                    toolkitInstalled <- toolkitAvailable toolkit 
                     if toolkitInstalled
                         then return $ ScriptFailure command_ code
-                        else return $ ToolkitNotInstalled tk
+                        else return $ ToolkitNotInstalled toolkit
 
 
 -- | Convert a @FigureSpec@ to a Pandoc block component.
@@ -118,7 +121,7 @@ toImage :: Format       -- ^ text format of the caption
         -> FigureSpec 
         -> Block
-toImage fmt spec = head . toList $ para $ imageWith attrs' (T.pack target') "fig:" caption'
+toImage fmt spec = head . toList $ para $ imageWith attrs' (pack target') "fig:" caption'
     -- To render images as figures with captions, the target title
     -- must be "fig:"
     -- Janky? yes
@@ -126,7 +129,7 @@         attrs'       = blockAttrs spec
         target'      = figurePath spec
         withSource'  = withSource spec
-        srcLink      = link (T.pack $ replaceExtension target' ".txt") mempty "Source code"
+        srcLink      = link (pack $ replaceExtension target' ".txt") mempty "Source code"
         captionText  = fromList $ fromMaybe mempty (captionReader fmt $ caption spec)
         captionLinks = mconcat [" (", srcLink, ")"]
         caption'     = if withSource' then captionText <> captionLinks else captionText
@@ -137,14 +140,14 @@ -- is important.
 tempScriptPath :: FigureSpec -> PlotM FilePath
 tempScriptPath FigureSpec{..} = do
-    ext <- scriptExtension <$> asks toolkit
+    let ext = scriptExtension toolkit
     -- Note that matlab will refuse to process files that don't start with
     -- a letter... so we append the renderer name
     -- Note that this hash is only so that we are running scripts from unique
     -- file names; it does NOT determine whether this figure should
     -- be rendered or not.
     let hashedPath = "pandocplot" <> (show . abs . hash $ script) <> ext
-    liftIO $ (</> hashedPath) <$> getCanonicalTemporaryDirectory
+    liftIO $ (</> hashedPath) <$> getTemporaryDirectory
 
 
 -- | Determine the path to the source code that generated the figure.
− src/Text/Pandoc/Filter/Plot/Types.hs
@@ -1,314 +0,0 @@-{-# LANGUAGE DeriveGeneric     #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards   #-}
-
-
-{-|
-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 in use in pandoc-plot
--}
-
-module Text.Pandoc.Filter.Plot.Types (
-      Toolkit(..)
-    , PlotM
-    , PlotEnv(..)
-    , Configuration(..)
-    , Script
-    , CheckResult(..)
-    , InclusionKey(..)
-    , FigureSpec(..)
-    , SaveFormat(..)
-    , figureContentHash
-    , cls
-    , extension
-    , toolkits
-    , inclusionKeys
-    -- Utilities
-    , isWindows
-) where
-
-import           Control.Monad.Reader
-
-import           Data.Char              (toLower)
-import           Data.Default.Class     (Default, def)
-import           Data.Hashable          (hash)
-import           Data.List              (intersperse)
-import           Data.Semigroup         (Semigroup (..))
-import           Data.String            (IsString (..))
-import           Data.Text              (Text)
-import           Data.Yaml
-
-import           GHC.Generics           (Generic)
-import           System.Info            (os)
-
-import           Text.Pandoc.Definition (Attr, Format(..))
-
-
-toolkits :: [Toolkit]
-toolkits = enumFromTo minBound maxBound
-
-
--- | Enumeration of supported toolkits
-data Toolkit
-    = Matplotlib
-    | Matlab
-    | PlotlyPython
-    | PlotlyR
-    | Mathematica
-    | Octave
-    | GGPlot2
-    | GNUPlot
-    | Graphviz
-    deriving (Bounded, Eq, Enum, Generic)
-
-
--- | This instance should only be used to display toolkit names
-instance Show Toolkit where
-    show Matplotlib   = "Python/Matplotlib"
-    show Matlab       = "MATLAB"
-    show PlotlyPython = "Python/Plotly"
-    show PlotlyR      = "R/Plotly"
-    show Mathematica  = "Mathematica"
-    show Octave       = "GNU Octave"
-    show GGPlot2      = "ggplot2"
-    show GNUPlot      = "gnuplot"
-    show Graphviz     = "graphviz"
-
-
--- | Class name which will trigger the filter
-cls :: Toolkit -> Text
-cls Matplotlib   = "matplotlib"
-cls Matlab       = "matlabplot"
-cls PlotlyPython = "plotly_python"
-cls PlotlyR      = "plotly_r"
-cls Mathematica  = "mathplot"
-cls Octave       = "octaveplot"
-cls GGPlot2      = "ggplot2"
-cls GNUPlot      = "gnuplot"
-cls Graphviz     = "graphviz"
-
-
-type PlotM a = ReaderT PlotEnv IO a
-
-
-data PlotEnv
-    = PlotEnv { toolkit   :: !Toolkit
-              , config    :: !Configuration
-              }
-
--- | The @Configuration@ type holds the default values to use
--- when running pandoc-plot. These values can be overridden in code blocks.
---
--- You can create an instance of the @Configuration@ type from file using the @configuration@ function.
---
--- You can store the path to a configuration file in metadata under the key @plot-configuration@. For example, in Markdown:
---
--- @
---     ---
---     title: My document
---     author: John Doe
---     plot-configuration: /path/to/file.yml
---     ---     
--- @
---
--- The same can be specified via the command line using Pandoc's @-M@ flag:
---
--- > pandoc --filter pandoc-plot -M plot-configuration="path/to/file.yml" ...
---
--- In this case, use @configurationPathMeta@ to extact the path from @Pandoc@ documents.
-data Configuration = Configuration
-    { defaultDirectory      :: !FilePath   -- ^ The default directory where figures will be saved.
-    , defaultWithSource     :: !Bool       -- ^ The default behavior of whether or not to include links to source code and high-res
-    , defaultDPI            :: !Int        -- ^ The default dots-per-inch value for generated figures. Renderers might ignore this.
-    , defaultSaveFormat     :: !SaveFormat -- ^ The default save format of generated figures.
-    , captionFormat         :: !Format     -- ^ Caption format, in the same notation as Pandoc format, e.g. "markdown+tex_math_dollars"
-
-    , matplotlibPreamble    :: !Script     -- ^ The default preamble script for the matplotlib toolkit.
-    , plotlyPythonPreamble  :: !Script     -- ^ The default preamble script for the Plotly/Python toolkit.
-    , plotlyRPreamble       :: !Script     -- ^ The default preamble script for the Plotly/R toolkit.
-    , matlabPreamble        :: !Script     -- ^ The default preamble script for the MATLAB toolkit.
-    , mathematicaPreamble   :: !Script     -- ^ The default preamble script for the Mathematica toolkit.
-    , octavePreamble        :: !Script     -- ^ The default preamble script for the GNU Octave toolkit.
-    , ggplot2Preamble       :: !Script     -- ^ The default preamble script for the GGPlot2 toolkit.
-    , gnuplotPreamble       :: !Script     -- ^ The default preamble script for the gnuplot toolkit.
-    , graphvizPreamble      :: !Script     -- ^ The default preamble script for the Graphviz toolkit.
-    
-    , matplotlibExe         :: !FilePath   -- ^ The executable to use to generate figures using the matplotlib toolkit.
-    , matlabExe             :: !FilePath   -- ^ The executable to use to generate figures using the MATLAB toolkit.
-    , plotlyPythonExe       :: !FilePath   -- ^ The executable to use to generate figures using the Plotly/Python toolkit.
-    , plotlyRExe            :: !FilePath   -- ^ The executable to use to generate figures using the Plotly/R toolkit.
-    , mathematicaExe        :: !FilePath   -- ^ The executable to use to generate figures using the Mathematica toolkit.
-    , octaveExe             :: !FilePath   -- ^ The executable to use to generate figures using the GNU Octave toolkit.
-    , ggplot2Exe            :: !FilePath   -- ^ The executable to use to generate figures using the GGPlot2 toolkit.
-    , gnuplotExe            :: !FilePath   -- ^ The executable to use to generate figures using the gnuplot toolkit.
-    , graphvizExe           :: !FilePath   -- ^ The executable to use to generate figures using the Graphviz toolkit.
-    
-    , matplotlibTightBBox   :: !Bool       -- ^ Whether or not to make Matplotlib figures tight by default.
-    , matplotlibTransparent :: !Bool       -- ^ Whether or not to make Matplotlib figures transparent by default.
-    } deriving (Eq, Show)
-
-instance Default Configuration where
-    def = Configuration
-          { defaultDirectory  = "plots/"
-          , defaultWithSource = False
-          , defaultDPI        = 80
-          , defaultSaveFormat = PNG
-          , captionFormat     = Format "markdown+tex_math_dollars"
-          
-          , matplotlibPreamble  = mempty
-          , plotlyPythonPreamble= mempty
-          , plotlyRPreamble     = mempty
-          , matlabPreamble      = mempty
-          , mathematicaPreamble = mempty
-          , octavePreamble      = mempty
-          , ggplot2Preamble     = mempty
-          , gnuplotPreamble     = mempty
-          , graphvizPreamble    = mempty
-
-          , matplotlibExe       = if isWindows then "python" else "python3"
-          , matlabExe           = "matlab"
-          , plotlyPythonExe     = if isWindows then "python" else "python3"
-          , plotlyRExe          = "Rscript"
-          , mathematicaExe      = "math"
-          , octaveExe           = "octave"
-          , ggplot2Exe          = "Rscript"
-          , gnuplotExe          = "gnuplot"
-          , graphvizExe         = "dot"
-          
-          , matplotlibTightBBox   = False
-          , matplotlibTransparent = False
-          }
-
-type Script = Text
-
-
--- | Result of checking scripts for problems
-data CheckResult
-    = CheckPassed
-    | CheckFailed String
-    deriving (Eq)
-
-instance Semigroup CheckResult where
-    (<>) CheckPassed a                         = a
-    (<>) a CheckPassed                         = a
-    (<>) (CheckFailed msg1) (CheckFailed msg2) = CheckFailed (msg1 <> msg2)
-
-instance Monoid CheckResult where
-    mempty = CheckPassed
-
-
--- | Description of any possible inclusion key, both in documents
--- and in configuration files.
-data InclusionKey
-    = DirectoryK
-    | CaptionK
-    | SaveFormatK
-    | WithSourceK
-    | CaptionFormatK
-    | PreambleK
-    | DpiK
-    | ExecutableK
-    | MatplotlibTightBBoxK
-    | MatplotlibTransparentK
-    deriving (Bounded, Eq, Enum)
-
--- | Keys that pandoc-plot will look for in code blocks.
--- These are only exported for testing purposes.
-instance Show InclusionKey where
-    show DirectoryK             = "directory"
-    show CaptionK               = "caption"
-    show SaveFormatK            = "format"
-    show WithSourceK            = "source"
-    show CaptionFormatK         = "caption_format"
-    show PreambleK              = "preamble"
-    show DpiK                   = "dpi"
-    show ExecutableK            = "executable"
-    show MatplotlibTightBBoxK   = "tight_bbox"
-    show MatplotlibTransparentK = "transparent"
-
-
--- | List of all keys related to pandoc-plot that
--- can be specified in source material.
-inclusionKeys :: [InclusionKey]
-inclusionKeys = enumFromTo (minBound::InclusionKey) maxBound
-
-
--- | Datatype containing all parameters required to run pandoc-plot.
---
--- It is assumed that once a @FigureSpec@ has been created, no configuration
--- can overload it; hence, a @FigureSpec@ completely encodes a particular figure.
-data FigureSpec = FigureSpec
-    { caption    :: !Text           -- ^ Figure caption.
-    , withSource :: !Bool           -- ^ Append link to source code in caption.
-    , script     :: !Script         -- ^ Source code for the figure.
-    , saveFormat :: !SaveFormat     -- ^ Save format of the figure.
-    , directory  :: !FilePath       -- ^ Directory where to save the file.
-    , dpi        :: !Int            -- ^ Dots-per-inch of figure.
-    , extraAttrs :: ![(Text, Text)] -- ^ Renderer-specific extra attributes.
-    , blockAttrs :: !Attr           -- ^ Attributes not related to @pandoc-plot@ will be propagated.
-    }
-
-
--- | Hash of the content of a @FigureSpec@. Note that unlike usual hashes,
--- two @FigureSpec@ with the same @figureContentHash@ does not mean that they are equal!
---
--- Not all parts of a FigureSpec are related to running code.
--- For example, changing the caption should not require running the figure again.
-figureContentHash :: FigureSpec -> Int
-figureContentHash FigureSpec{..} = 
-    hash (script, fromEnum saveFormat, directory, dpi, extraAttrs)
-
-
--- | Generated figure file format supported by pandoc-plot.
--- Note that not all formats are supported by all toolkits.
-data SaveFormat
-    = PNG   -- ^ Portable network graphics
-    | PDF   -- ^ Portable document format
-    | SVG   -- ^ Scalable vector graphics
-    | JPG   -- ^ JPEG/JPG compressed image
-    | EPS   -- ^ Encapsulated postscript
-    | GIF   -- ^ GIF format
-    | TIF   -- ^ Tagged image format
-    | WEBP  -- ^ WebP image format
-    deriving (Bounded, Enum, Eq, Show, Generic)
-
-instance IsString SaveFormat where
-    -- | An error is thrown if the save format cannot be parsed. That's OK
-    -- since pandoc-plot is a command-line tool and isn't expected to run
-    -- long.
-    fromString s
-        | s `elem` ["png", "PNG", ".png"] = PNG
-        | s `elem` ["pdf", "PDF", ".pdf"] = PDF
-        | s `elem` ["svg", "SVG", ".svg"] = SVG
-        | s `elem` ["eps", "EPS", ".eps"] = EPS
-        | s `elem` ["gif", "GIF", ".gif"] = GIF
-        | s `elem` ["jpg", "jpeg", "JPG", "JPEG", ".jpg", ".jpeg"] = JPG
-        | s `elem` ["tif", "tiff", "TIF", "TIFF", ".tif", ".tiff"] = TIF
-        | s `elem` ["webp", "WEBP", ".webp"] = WEBP
-        | otherwise = error $
-                mconcat [ s
-                        , " is not one of valid save format : "
-                        , mconcat $ intersperse ", " $ show <$> saveFormats
-                        ]
-        where
-            saveFormats =  (enumFromTo minBound maxBound) :: [SaveFormat]
-
-instance FromJSON SaveFormat -- TODO: test this parsing
-
-instance ToJSON SaveFormat where
-    toJSON = toJSON . extension
-
-
--- | Save format file extension
-extension :: SaveFormat -> String
-extension fmt = mconcat [".", fmap toLower . show $ fmt]
-
-
-isWindows :: Bool
-isWindows = os `elem` ["mingw32", "win32", "cygwin32"] -- Aliases taken from cabal's Distribution.System module
stack.yaml view
@@ -1,65 +1,4 @@-# This file was automatically generated by 'stack init'
-#
-# Some commonly used options have been documented as comments in this file.
-# For advanced use and comprehensive documentation of the format, please see:
-# https://docs.haskellstack.org/en/stable/yaml_configuration/
-
-# Resolver to choose a 'specific' stackage snapshot or a compiler version.
-# A snapshot resolver dictates the compiler version and the set of packages
-# to be used for project dependencies. For example:
-#
-# resolver: lts-3.5
-# resolver: nightly-2015-09-21
-# resolver: ghc-7.10.2
-#
-# The location of a snapshot can be provided as a file or url. Stack assumes
-# a snapshot provided as a file might change, whereas a url resource does not.
-#
-# resolver: ./custom-snapshot.yaml
-# resolver: https://example.com/snapshots/2018-01-01.yaml
-
 resolver: lts-15.16 # GHC 8.8.3
 
-# User packages to be built.
-# Various formats can be used as shown in the example below.
-#
-# packages:
-# - some-directory
-# - https://example.com/foo/bar/baz-0.0.2.tar.gz
-# - location:
-#    git: https://github.com/commercialhaskell/stack.git
-#    comGNU GPL, version 2 or above: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
-# - location: https://github.com/commercialhaskell/stack/comGNU GPL, version 2 or above/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
-#  subdirs:
-#  - auto-update
-#  - wai
 packages:
 - .
-# Dependency packages to be pulled from upstream that are not in the resolver
-# using the same syntax as the packages field.
-# (e.g., acme-missiles-0.3)
-extra-deps:
-
-# Override default flag values for local packages and extra-deps
-# flags: {}
-
-# Extra package databases containing global packages
-# extra-package-dbs: []
-
-# Control whether we use the GHC we find on the path
-# system-ghc: true
-#
-# Require a specific version of stack, using version ranges
-# require-stack-version: -any # Default
-# require-stack-version: ">=1.10"
-#
-# Override the architecture used by stack, especially useful on Windows
-# arch: i386
-# arch: x86_64
-#
-# Extra directories used by stack for building
-# extra-include-dirs: [/path/to/dir]
-# extra-lib-dirs: [/path/to/dir]
-#
-# Allow a newer minor version of GHC than the snapshot specifies
-compiler-check: newer-minor
tests/Common.hs view
@@ -3,10 +3,8 @@ 
 module Common where
 
-import           Control.Monad                    (unless)
-import           Control.Monad.Reader
+import           Control.Monad                    (unless, when)
 
-import           Data.Default.Class               (def)
 import           Data.List                        (isInfixOf, isSuffixOf)
 import           Data.Monoid                      ((<>))
 import           Data.String                      (fromString)
@@ -27,21 +25,25 @@                                                    doesDirectoryExist,
                                                    doesFileExist, listDirectory,
                                                    removeDirectoryRecursive,
-                                                   removePathForcibly)
+                                                   removePathForcibly, 
+                                                   getTemporaryDirectory)
 import           System.FilePath                  (takeExtensions, (</>))
-import           System.IO.Temp                   (getCanonicalTemporaryDirectory)
 
+
+defaultTestConfig :: Configuration
+defaultTestConfig = defaultConfiguration {logVerbosity=Silent, logSink=StdErr}
+
 -------------------------------------------------------------------------------
 -- Test that plot files and source files are created when the filter is run
 testFileCreation :: Toolkit -> TestTree
 testFileCreation tk =
     testCase "writes output files in appropriate directory" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-file-creation-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-file-creation-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
         let cb = (addDirectory tempDir $ codeBlock tk (trivialContent tk))
-        _ <- (make tk) def cb
+        _ <- make defaultTestConfig cb
         filesCreated <- length <$> listDirectory tempDir
         assertEqual "" 2 filesCreated
 
@@ -51,12 +53,12 @@ testFileInclusion tk =
     testCase "includes plot inclusions" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-file-inclusion-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-file-inclusion-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
         let cb = (addPreamble (include tk) $
                     addDirectory tempDir $ codeBlock tk (trivialContent tk))
-        _ <- (make tk) def cb
+        _ <- make defaultTestConfig cb
         inclusion <- readFile (include tk)
         sourcePath <- head . filter (isExtensionOf "txt") <$> listDirectory tempDir
         src <- readFile (tempDir </> sourcePath)
@@ -77,12 +79,12 @@ testSaveFormat tk =
     testCase "saves in the appropriate format" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-safe-format-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-safe-format-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
         let fmt = head (supportedSaveFormats tk)
             cb = (addSaveFormat fmt $
                  addDirectory tempDir $ codeBlock tk (trivialContent tk))
-        _ <- (make tk) def cb
+        _ <- make defaultTestConfig cb
         numberjpgFiles <-
             length <$> filter (isExtensionOf (extension fmt)) <$>
             listDirectory tempDir
@@ -94,7 +96,7 @@ testWithSource tk =
     testCase "appropriately omits links to source code" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-caption-links-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-caption-links-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
         let expected = "caption content"
@@ -106,8 +108,8 @@                       $ addDirectory tempDir 
                       $ addCaption expected 
                       $ codeBlock tk (trivialContent tk)
-        blockNoSource   <- (make tk) def noSource
-        blockWithSource <- (make tk) def withSource
+        blockNoSource   <- make defaultTestConfig noSource
+        blockWithSource <- make defaultTestConfig withSource
 
         -- In the case where source=false, the caption is used verbatim.
         -- Otherwise, links will be appended to the caption; hence, the caption
@@ -132,11 +134,11 @@     -- no JPG files.
     testCase "code block attributes override configuration defaults" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-caption-links-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-caption-links-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
-        let config = (def::Configuration) { defaultDirectory = tempDir
-                                          , defaultSaveFormat = JPG}
+        let config = defaultTestConfig { defaultDirectory = tempDir
+                                       , defaultSaveFormat = JPG}
 
         -- Not all toolkits support both save formats
         when (  JPG `elem` supportedSaveFormats tk 
@@ -146,7 +148,7 @@             let cb = addDirectory tempDir 
                         $ addSaveFormat PNG
                         $ codeBlock tk (trivialContent tk)
-            _ <- (make tk) config cb
+            _ <- make config cb
 
             numberPngFiles <-
                 length <$> filter (isExtensionOf (extension PNG)) <$>
@@ -163,7 +165,7 @@ testMarkdownFormattingCaption1 tk =
     testCase "appropriately parses captions 1" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-caption-parsing1-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-caption-parsing1-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
         -- Note that this test is fragile, in the sense that the expected result must be carefully
@@ -173,7 +175,7 @@                     $ addCaption "**caption**" 
                     $ codeBlock tk (trivialContent tk)
             fmt = B.Format "markdown"
-        result <- (make tk) (def {captionFormat=fmt}) cb
+        result <- make (defaultTestConfig {captionFormat=fmt}) cb
         assertIsInfix expected (extractCaption result)
     where
         extractCaption (B.Para blocks) = extractImageCaption . head $ blocks
@@ -188,7 +190,7 @@ testMarkdownFormattingCaption2 tk =
     testCase "appropriately parses captions 2" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-caption-parsing2-" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-caption-parsing2-" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
         -- Note that this test is fragile, in the sense that the expected result must be carefully
@@ -198,7 +200,7 @@                     $ addCaption "[title](https://google.com)" 
                     $ codeBlock tk (trivialContent tk)
             fmt = B.Format "markdown"
-        result <- (make tk) (def {captionFormat=fmt}) cb
+        result <- make (defaultTestConfig {captionFormat=fmt}) cb
         assertIsInfix expected (extractCaption result)
     where
         extractCaption (B.Para blocks) = extractImageCaption . head $ blocks
@@ -214,14 +216,14 @@ testCleanOutputDirs tk = 
     testCase "correctly cleans output directories" $ do
         let postfix = unpack . cls $ tk
-        tempDir <- (</> "test-clean-output-dir" <> postfix) <$> getCanonicalTemporaryDirectory
+        tempDir <- (</> "test-clean-output-dir" <> postfix) <$> getTemporaryDirectory
         ensureDirectoryExistsAndEmpty tempDir
 
         let cb = addDirectory tempDir
                     $ codeBlock tk (trivialContent tk)
         
-        result <- (make tk) def cb
-        cleanedDirs <- cleanOutputDirs def cb
+        result <- make defaultTestConfig cb
+        cleanedDirs <- cleanOutputDirs defaultTestConfig cb
 
         assertEqual "" [tempDir] cleanedDirs
 
@@ -238,17 +240,18 @@     where
         assertChecksFail Matplotlib = do
             let postfix = unpack . cls $ tk
-            tempDir <- (</> "test-checks" <> postfix) <$> getCanonicalTemporaryDirectory
+            tempDir <- (</> "test-checks" <> postfix) <$> getTemporaryDirectory
             ensureDirectoryExistsAndEmpty tempDir
 
             let cb = addDirectory tempDir $ codeBlock Matplotlib "plt.show()"
-            result <- (make' Matplotlib) def cb
+            result <- makeEither defaultTestConfig cb
             let expectedCheck :: Either PandocPlotError a -> Bool
                 expectedCheck (Left (ScriptChecksFailedError _)) = True
                 expectedCheck _ = False
             assertBool "" (expectedCheck result)
 
         assertChecksFail _          = assertEqual "Test skipped" True True
+
 
 codeBlock :: Toolkit -> Script -> Block
 codeBlock tk script = CodeBlock (mempty, [cls tk], mempty) script
tests/Main.hs view
@@ -3,7 +3,6 @@ 
 import           Control.Monad                    (forM_)
 
-import           Data.Default.Class               (Default, def)
 import qualified Data.Map.Strict                  as Map
 import           Data.Text                        (Text, unpack)
 
@@ -19,8 +18,8 @@ 
 main :: IO ()
 main = do
-    available <- availableToolkits def
-    unavailable <- unavailableToolkits def
+    available <- availableToolkits defaultTestConfig
+    unavailable <- unavailableToolkits defaultTestConfig
     forM_ unavailable $ \tk -> do
         putStrLn $ show tk <> " is not availble. Its tests will be skipped."
 
@@ -59,7 +58,7 @@ testEmptyConfiguration :: TestTree
 testEmptyConfiguration = 
     testCase "empty configuration is correctly parsed to default values" $ do
-        let config = def
+        let config = defaultConfiguration
 
         parsedConfig <- configuration "tests/fixtures/.empty-config.yml"
         assertEqual "" config parsedConfig
@@ -74,9 +73,9 @@         -- The example config reflects the Windows default
         -- Therefore, we need to test against the Windows default,
         -- even on other OSes
-        let config = def { matplotlibExe = "python"
-                         , plotlyPythonExe = "python"
-                         }
+        let config = defaultConfiguration { matplotlibExe = "python"
+                                          , plotlyPythonExe = "python"
+                                          }
 
         parsedConfig <- configuration "example-config.yml"
         assertEqual "" config parsedConfig