summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurentRDC <>2020-06-15 12:01:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2020-06-15 12:01:00 (GMT)
commitc867a5ea03b68c705faf151813c18bd285085ec6 (patch)
tree7c3626f1e21c69a76915e028edff25669cfcc8ee
parent4d7da799de1ab45c30b61c168ef2e08e37d86210 (diff)
version 0.7.0.00.7.0.0
-rw-r--r--CHANGELOG.md19
-rw-r--r--README.md122
-rw-r--r--example-config.yml14
-rw-r--r--executable/ExampleConfig.hs11
-rw-r--r--executable/Main.hs26
-rw-r--r--executable/ManPage.hs11
-rw-r--r--pandoc-plot.cabal19
-rw-r--r--src/Text/Pandoc/Filter/Plot.hs73
-rw-r--r--src/Text/Pandoc/Filter/Plot/Clean.hs24
-rw-r--r--src/Text/Pandoc/Filter/Plot/Configuration.hs157
-rw-r--r--src/Text/Pandoc/Filter/Plot/Internal.hs8
-rw-r--r--src/Text/Pandoc/Filter/Plot/Monad.hs152
-rw-r--r--src/Text/Pandoc/Filter/Plot/Monad/Logging.hs116
-rw-r--r--src/Text/Pandoc/Filter/Plot/Monad/Types.hs (renamed from src/Text/Pandoc/Filter/Plot/Types.hs)110
-rw-r--r--src/Text/Pandoc/Filter/Plot/Parse.hs49
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers.hs41
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs8
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs42
-rw-r--r--src/Text/Pandoc/Filter/Plot/Scripting.hs71
-rw-r--r--stack.yaml61
-rw-r--r--tests/Common.hs57
-rw-r--r--tests/Main.hs13
30 files changed, 770 insertions, 514 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7fd3ba3..1fd1d15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
---------------
diff --git a/README.md b/README.md
index e5b846a..2ebcc2b 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Do not edit manually
## 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 @@ Do not edit manually
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 options:
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 @@ so:
pandoc --filter pandoc-plot --filter pandoc-crossref -i input.md -o output.html
```
-### Parameters and options
+### Syntax
+
+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
+ ```
+````
-`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.
+#### 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 @@ include the programming language. In Markdown:
```
````
-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 @@ files. Here are **all** the possible parameters:
# 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 @@ format: PNG
# 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 @@ matlabplot:
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 @@ Here is the full help text for the `clean` command:
``` 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.
diff --git a/example-config.yml b/example-config.yml
index c23b175..13bd289 100644
--- a/example-config.yml
+++ b/example-config.yml
@@ -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.
@@ -33,6 +31,14 @@ format: PNG
# 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
diff --git a/executable/ExampleConfig.hs b/executable/ExampleConfig.hs
index 4709b0a..e827a17 100644
--- a/executable/ExampleConfig.hs
+++ b/executable/ExampleConfig.hs
@@ -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
diff --git a/executable/Main.hs b/executable/Main.hs
index 6bc1d14..a01b8cd 100644
--- a/executable/Main.hs
+++ b/executable/Main.hs
@@ -9,28 +9,29 @@ import Control.Monad (join, forM_, when, msum)
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 @@ commandParser = optional $ subparser $ mconcat
)
, 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 @@ showAvailableToolkits mfp = do
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 @@ clean mfp fp = do
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
diff --git a/executable/ManPage.hs b/executable/ManPage.hs
index 3b3b4c6..be85c24 100644
--- a/executable/ManPage.hs
+++ b/executable/ManPage.hs
@@ -5,27 +5,20 @@ This module was inspired by pandoc-crossref
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
diff --git a/pandoc-plot.cabal b/pandoc-plot.cabal
index 477d6cd..ee05492 100644
--- a/pandoc-plot.cabal
+++ b/pandoc-plot.cabal
@@ -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 @@ library
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 @@ library
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 @@ library
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 @@ executable pandoc-plot
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 @@ executable pandoc-plot
, pandoc-plot
, pandoc-types > 1.12 && <2
, template-haskell > 2.7 && < 3
- , temporary
, text
default-language: Haskell2010
@@ -104,7 +102,6 @@ test-suite tests
build-depends: base >= 4.11 && < 5
, containers
, directory
- , data-default-class >= 0.1.2
, filepath
, hspec
, hspec-expectations
@@ -113,9 +110,7 @@ test-suite tests
, tasty
, tasty-hunit
, tasty-hspec
- , temporary
, text
- , mtl >= 2.2 && < 2.3
default-language: Haskell2010
benchmark benchmark-pandoc-plot
diff --git a/src/Text/Pandoc/Filter/Plot.hs b/src/Text/Pandoc/Filter/Plot.hs
index d83c22d..7a57ad6 100644
--- a/src/Text/Pandoc/Filter/Plot.hs
+++ b/src/Text/Pandoc/Filter/Plot.hs
@@ -75,6 +75,8 @@ module Text.Pandoc.Filter.Plot (
, configuration
, defaultConfiguration
, Configuration(..)
+ , Verbosity(..)
+ , LogSink(..)
, SaveFormat(..)
, Script
-- * Determining available plotting toolkits
@@ -82,16 +84,14 @@ module Text.Pandoc.Filter.Plot (
, 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.Walk (walkM, Walkable)
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 @@ plotTransform conf (Pandoc meta blocks) =
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 @@ make' tk conf block = runReaderT (make'' block) (PlotEnv tk conf)
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." \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Plot/Clean.hs b/src/Text/Pandoc/Filter/Plot/Clean.hs
index 4d81893..8bc7b00 100644
--- a/src/Text/Pandoc/Filter/Plot/Clean.hs
+++ b/src/Text/Pandoc/Filter/Plot/Clean.hs
@@ -17,12 +17,11 @@ module Text.Pandoc.Filter.Plot.Clean (
, 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 qualified Text.Pandoc.Options as P
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 :: FilePath -> IO Pandoc
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
diff --git a/src/Text/Pandoc/Filter/Plot/Configuration.hs b/src/Text/Pandoc/Filter/Plot/Configuration.hs
index 75741bf..bf409fe 100644
--- a/src/Text/Pandoc/Filter/Plot/Configuration.hs
+++ b/src/Text/Pandoc/Filter/Plot/Configuration.hs
@@ -19,7 +19,6 @@ module Text.Pandoc.Filter.Plot.Configuration (
, 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 Data.Yaml.Config (ignoreEnv, loadYamlSettings)
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,7 +42,40 @@ configuration fp = (loadYamlSettings [fp] [] ignoreEnv) >>= renderConfig
--
-- @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.
@@ -83,6 +115,8 @@ data ConfigPrecursor = ConfigPrecursor
, _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 @@ data ConfigPrecursor = ConfigPrecursor
, _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,82 +169,77 @@ data MathematicaPrecursor = MathematicaPrecursor {_mathematicaPreamble :: !(
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)
-
- _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
+ _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)
+
+ _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."
@@ -217,6 +253,9 @@ renderConfig ConfigPrecursor{..} = do
defaultSaveFormat = _defaultSaveFormat
captionFormat = _captionFormat
+ logVerbosity = _logVerbosity _logPrec
+ logSink = maybe StdErr LogFile (_logFilePath _logPrec)
+
matplotlibTightBBox = _matplotlibTightBBox _matplotlibPrec
matplotlibTransparent = _matplotlibTransparent _matplotlibPrec
diff --git a/src/Text/Pandoc/Filter/Plot/Internal.hs b/src/Text/Pandoc/Filter/Plot/Internal.hs
index 7da1cf8..c4ec2b0 100644
--- a/src/Text/Pandoc/Filter/Plot/Internal.hs
+++ b/src/Text/Pandoc/Filter/Plot/Internal.hs
@@ -12,17 +12,17 @@ The external use of content from this module is discouraged.
-}
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 \ No newline at end of file
+import Text.Pandoc.Filter.Plot.Clean
+import Text.Pandoc.Filter.Plot.Monad \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Plot/Monad.hs b/src/Text/Pandoc/Filter/Plot/Monad.hs
new file mode 100644
index 0000000..9edce2b
--- /dev/null
+++ b/src/Text/Pandoc/Filter/Plot/Monad.hs
@@ -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) \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Plot/Monad/Logging.hs b/src/Text/Pandoc/Filter/Plot/Monad/Logging.hs
new file mode 100644
index 0000000..60f6ef0
--- /dev/null
+++ b/src/Text/Pandoc/Filter/Plot/Monad/Logging.hs
@@ -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 \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Plot/Types.hs b/src/Text/Pandoc/Filter/Plot/Monad/Types.hs
index 53e6ea8..11fdf14 100644
--- a/src/Text/Pandoc/Filter/Plot/Types.hs
+++ b/src/Text/Pandoc/Filter/Plot/Monad/Types.hs
@@ -11,14 +11,11 @@ Maintainer : laurent.decotret@outlook.com
Stability : internal
Portability : portable
-This module defines types in use in pandoc-plot
+This module defines base types in use in pandoc-plot
-}
-module Text.Pandoc.Filter.Plot.Types (
+module Text.Pandoc.Filter.Plot.Monad.Types (
Toolkit(..)
- , PlotM
- , PlotEnv(..)
- , Configuration(..)
, Script
, CheckResult(..)
, InclusionKey(..)
@@ -33,10 +30,7 @@ module Text.Pandoc.Filter.Plot.Types (
, 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 (..))
@@ -47,7 +41,7 @@ import Data.Yaml
import GHC.Generics (Generic)
import System.Info (os)
-import Text.Pandoc.Definition (Attr, Format(..))
+import Text.Pandoc.Definition (Attr)
toolkits :: [Toolkit]
@@ -94,104 +88,13 @@ 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
+ | CheckFailed Text
deriving (Eq)
instance Semigroup CheckResult where
@@ -244,7 +147,8 @@ inclusionKeys = enumFromTo (minBound::InclusionKey) maxBound
-- 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.
+ { 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.
@@ -262,7 +166,7 @@ data FigureSpec = FigureSpec
-- For example, changing the caption should not require running the figure again.
figureContentHash :: FigureSpec -> Int
figureContentHash FigureSpec{..} =
- hash (script, fromEnum saveFormat, directory, dpi, extraAttrs)
+ hash (fromEnum toolkit, script, fromEnum saveFormat, directory, dpi, extraAttrs)
-- | Generated figure file format supported by pandoc-plot.
diff --git a/src/Text/Pandoc/Filter/Plot/Parse.hs b/src/Text/Pandoc/Filter/Plot/Parse.hs
index ed79aeb..21b4e42 100644
--- a/src/Text/Pandoc/Filter/Plot/Parse.hs
+++ b/src/Text/Pandoc/Filter/Plot/Parse.hs
@@ -19,12 +19,11 @@ module Text.Pandoc.Filter.Plot.Parse (
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 @@ import Text.Pandoc.Definition (Block (..), Inline,
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 @@ tshow = pack . show
-- 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 @@ parseFigureSpec (CodeBlock (id', classes, attrs) content) = do
-- 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 @@ plotToolkit _ = Nothing
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 :: Text -> Bool
readBool s | s `elem` ["True", "true", "'True'", "'true'", "1"] = True
| s `elem` ["False", "false", "'False'", "'false'", "0"] = False
| otherwise = error $ unpack $ mconcat ["Could not parse '", s, "' into a boolean. Please use 'True' or 'False'"]
+
+
+-- | 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
+ } \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers.hs b/src/Text/Pandoc/Filter/Plot/Renderers.hs
index 528343a..9091dd5 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers.hs
@@ -24,11 +24,13 @@ module Text.Pandoc.Filter.Plot.Renderers (
, 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.GNUPlot
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 @@ parseExtraAttrs _ = return mempty
-- 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 @@ capture Graphviz = graphvizCapture
-- | 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 @@ toolkitAvailable Graphviz = graphvizAvailable
-- | 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
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs b/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs
index 8e833f6..e8ceb8b 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs
@@ -27,15 +27,15 @@ ggplot2SupportedSaveFormats :: [SaveFormat]
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")'|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs b/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs
index bdcfbeb..2cae6b6 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs
@@ -26,15 +26,15 @@ gnuplotSupportedSaveFormats :: [SaveFormat]
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|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs
index 6bce0b4..c27e3dc 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs
@@ -28,16 +28,16 @@ graphvizSupportedSaveFormats :: [SaveFormat]
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} -?|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs
index 4eec412..c8d292c 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs
@@ -26,15 +26,15 @@ mathematicaSupportedSaveFormats :: [SaveFormat]
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
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs
index 52e524e..090f182 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs
@@ -29,9 +29,9 @@ matlabSupportedSaveFormats :: [SaveFormat]
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 @@ matlabCommand OutputSpec{..} = do
-- 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
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs
index 80fa6bb..5c9bf99 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs
@@ -36,9 +36,9 @@ matplotlibSupportedSaveFormats :: [SaveFormat]
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 :: M.Map Text Text -> (M.Map Text Text)
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"|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs
index 77bf576..d95eba9 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs
@@ -27,15 +27,15 @@ octaveSupportedSaveFormats :: [SaveFormat]
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|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs
index 7a4820c..f24b8d6 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs
@@ -27,15 +27,15 @@ plotlyPythonSupportedSaveFormats :: [SaveFormat]
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"|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs
index bb1782c..af83951 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs
@@ -27,15 +27,15 @@ plotlyRSupportedSaveFormats :: [SaveFormat]
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")'|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs
index 3eb5443..2b4105f 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs
@@ -13,7 +13,7 @@ Prelude for renderers, containing some helpful utilities.
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 Data.Text (Text, unpack)
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 @@ tryToFindExe fp = findExecutable fp >>= maybe (return fp) return
-- | 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
}
diff --git a/src/Text/Pandoc/Filter/Plot/Scripting.hs b/src/Text/Pandoc/Filter/Plot/Scripting.hs
index 0ae89e0..843b3e9 100644
--- a/src/Text/Pandoc/Filter/Plot/Scripting.hs
+++ b/src/Text/Pandoc/Filter/Plot/Scripting.hs
@@ -23,17 +23,15 @@ import Control.Monad.Reader
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.Definition (Block (..), Format)
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,9 +52,29 @@ runScriptIfNecessary spec = do
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
@@ -73,43 +83,36 @@ runScriptIfNecessary spec = do
-- 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 @@ runTempScript spec@FigureSpec{..} = do
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 @@ toImage fmt spec = head . toList $ para $ imageWith attrs' (T.pack target') "fig
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 @@ toImage fmt spec = head . toList $ para $ imageWith attrs' (T.pack target') "fig
-- 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.
diff --git a/stack.yaml b/stack.yaml
index b644084..0a80fd7 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -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
diff --git a/tests/Common.hs b/tests/Common.hs
index 0e54b4e..764b203 100644
--- a/tests/Common.hs
+++ b/tests/Common.hs
@@ -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,9 +25,13 @@ import System.Directory (createDirectory,
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
@@ -37,11 +39,11 @@ 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 :: Toolkit -> TestTree
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 :: Toolkit -> TestTree
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 :: Toolkit -> TestTree
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 @@ testWithSource tk =
$ 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 @@ testOverrideConfiguration tk =
-- 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 @@ testOverrideConfiguration tk =
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 :: Toolkit -> TestTree
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 @@ testMarkdownFormattingCaption1 tk =
$ 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 :: Toolkit -> TestTree
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 @@ testMarkdownFormattingCaption2 tk =
$ 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 :: Toolkit -> TestTree
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,11 +240,11 @@ testChecksFail tk =
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
@@ -250,6 +252,7 @@ testChecksFail tk =
assertChecksFail _ = assertEqual "Test skipped" True True
+
codeBlock :: Toolkit -> Script -> Block
codeBlock tk script = CodeBlock (mempty, [cls tk], mempty) script
diff --git a/tests/Main.hs b/tests/Main.hs
index e592e0c..d7a1ae4 100644
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -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 @@ import Text.Pandoc.Filter.Plot.Internal
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 @@ toolkitSuite tk =
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 @@ testExampleConfiguration =
-- 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