pandoc-plot 0.9.1.0 → 0.9.2.0
raw patch · 23 files changed
+262/−150 lines, 23 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
+ Text.Pandoc.Filter.Plot: Executable :: FilePath -> Text -> Executable
+ Text.Pandoc.Filter.Plot: data Executable
- Text.Pandoc.Filter.Plot: executable :: Toolkit -> PlotM FilePath
+ Text.Pandoc.Filter.Plot: executable :: Toolkit -> PlotM (Maybe Executable)
Files
- CHANGELOG.md +8/−0
- MANUAL.md +1/−1
- docs/MANUAL.html +3/−3
- executable/Main.hs +5/−3
- pandoc-plot.cabal +1/−1
- src/Text/Pandoc/Filter/Plot.hs +1/−0
- src/Text/Pandoc/Filter/Plot/Clean.hs +8/−9
- src/Text/Pandoc/Filter/Plot/Monad.hs +7/−3
- src/Text/Pandoc/Filter/Plot/Renderers.hs +4/−2
- src/Text/Pandoc/Filter/Plot/Renderers/Bokeh.hs +11/−7
- src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs +18/−8
- src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs +15/−7
- src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs +9/−7
- src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs +12/−7
- src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs +7/−5
- src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs +12/−7
- src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs +12/−7
- src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs +13/−7
- src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs +20/−9
- src/Text/Pandoc/Filter/Plot/Renderers/Plotsjl.hs +12/−7
- src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs +43/−21
- src/Text/Pandoc/Filter/Plot/Scripting.hs +34/−27
- tests/Common.hs +6/−2
CHANGELOG.md view
@@ -2,6 +2,14 @@ pandoc-plot uses [Semantic Versioning](http://semver.org/spec/v2.0.0.html) +Release 0.9.2.0 +--------------- + +* Fixed an issue where executables located on paths with spaces would not be invoked correctly (#12) +* Fixed an issue where R-paths were not normalized correctly. +* Fixed an issue where executables specified in configuration that did not exist would crash `pandoc-plot`. +* Fixed an issue where some R-based toolkits appeared to be available, but were not. + Release 0.9.1.0 ---------------
MANUAL.md view
@@ -159,7 +159,7 @@ `pandoc-plot` is a command line executable with a few functions. You can take a look at the help using the `-h`/`--help` flag: ``` bash -pandoc-plot 0.9.1.0 - generate figures directly in documents +pandoc-plot 0.9.2.0 - generate figures directly in documents Usage: pandoc-plot.EXE [(-v|--version) | --full-version | (-m|--manual)] [COMMAND] [AST]
docs/MANUAL.html view
@@ -4,7 +4,7 @@ <meta charset="utf-8" /> <meta name="generator" content="pandoc" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> - <title>pandoc-plot 0.9.1.0 manual</title> + <title>pandoc-plot 0.9.2.0 manual</title> <style> code{white-space: pre-wrap;} span.smallcaps{font-variant: small-caps;} @@ -83,7 +83,7 @@ </head> <body> <header id="title-block-header"> -<h1 class="title">pandoc-plot 0.9.1.0 manual</h1> +<h1 class="title">pandoc-plot 0.9.2.0 manual</h1> </header> <!-- The file MANUAL.md is automatically generated by the tools/mkmanual.ps1 script. Do not edit manually. @@ -187,7 +187,7 @@ <div class="sourceCode" id="cb10"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true"></a><span class="ex">pandoc</span> --filter pandoc-plot --filter pandoc-crossref -i myfile.md -o myfile.html</span></code></pre></div> <h2 id="detailed-usage">Detailed usage</h2> <p><code>pandoc-plot</code> is a command line executable with a few functions. You can take a look at the help using the <code>-h</code>/<code>--help</code> flag:</p> -<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true"></a><span class="ex">pandoc-plot</span> 0.9.1.0 - generate figures directly in documents</span> +<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true"></a><span class="ex">pandoc-plot</span> 0.9.2.0 - generate figures directly in documents</span> <span id="cb11-2"><a href="#cb11-2" aria-hidden="true"></a></span> <span id="cb11-3"><a href="#cb11-3" aria-hidden="true"></a><span class="ex">Usage</span>: pandoc-plot.EXE [(-v<span class="kw">|</span><span class="ex">--version</span>) <span class="kw">|</span> <span class="ex">--full-version</span> <span class="kw">|</span> <span class="kw">(</span><span class="ex">-m</span><span class="kw">|</span><span class="ex">--manual</span><span class="kw">)</span>] </span> <span id="cb11-4"><a href="#cb11-4" aria-hidden="true"></a> [<span class="ex">COMMAND</span>] [AST]</span>
executable/Main.hs view
@@ -10,6 +10,7 @@ import Control.Monad (join, when, msum) import Data.List (intersperse, (\\)) +import Data.Maybe (fromJust) import Data.Text (unpack) import qualified Data.Text.IO as TIO import Data.Version (parseVersion, showVersion) @@ -34,7 +35,7 @@ toolkits, readDoc, cleanOutputDirs, configurationPathMeta, - executable, runPlotM) + executable, runPlotM, Executable(..)) import Text.Pandoc (pandocVersion) import Text.Pandoc.Definition (pandocTypesVersion) @@ -233,9 +234,10 @@ return unavailable >>= mapM_ (unavailToolkitInfo c) where toolkitInfo avail conf tk = do - exe <- runPlotM conf $ executable tk putStrLn $ "Toolkit: " <> show tk - when avail $ putStrLn $ " Executable: " <> exe + when avail $ do + Executable dir exe <- fmap fromJust $ runPlotM conf $ executable tk + putStrLn $ " Executable: " <> (dir </> unpack exe) putStrLn $ " Code block trigger: " <> (unpack . cls $ tk) putStrLn $ " Supported save formats: " <> (mconcat . intersperse ", " . fmap show $ supportedSaveFormats tk) putStrLn mempty
pandoc-plot.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: pandoc-plot -version: 0.9.1.0 +version: 0.9.2.0 synopsis: A Pandoc filter to include figures generated from code blocks using your plotting toolkit of choice. description: A Pandoc filter to include figures generated from code blocks. Keep the document and code in the same location. Output is captured and included as a figure. category: Text
src/Text/Pandoc/Filter/Plot.hs view
@@ -116,6 +116,7 @@ , cls , configurationPathMeta , executable + , Executable(..) -- ** Embedding HTML content , extractPlot ) where
src/Text/Pandoc/Filter/Plot/Clean.hs view
@@ -52,7 +52,14 @@ -- The cleaned directories are returned. cleanOutputDirs :: Walkable Block b => Configuration -> b -> IO [FilePath] -cleanOutputDirs conf = runPlotM conf . cleanOutputDirsM +cleanOutputDirs conf doc = do + dirs <- runPlotM conf . cleanOutputDirsM $ doc + -- Deletion of the log file must be done outside of PlotM + -- to ensure the log file has been closed. + case logSink conf of + LogFile path -> removePathForcibly path + _ -> return () + return dirs -- | Analyze a document to determine where would the pandoc-plot output directories be. @@ -67,15 +74,7 @@ cleanOutputDirsM :: Walkable Block b => b -> PlotM [FilePath] cleanOutputDirsM doc = do - conf <- asksConfig id - case logSink conf of - LogFile path -> do - info $ "Removing log file " <> pack path - liftIO $ removePathForcibly path - _ -> return () - directories <- outputDirs doc - forM directories $ \fp -> do info $ "Removing directory " <> pack fp -- It is important to use `removePathForcibly` here, because it does
src/Text/Pandoc/Filter/Plot/Monad.hs view
@@ -57,6 +57,7 @@ import System.Exit (ExitCode (..)) import System.Process.Typed ( readProcessStderr, shell, nullStream , setStdout, setStderr, byteStringOutput + , setWorkingDir ) import Text.Pandoc.Definition (Format(..)) @@ -119,13 +120,16 @@ -- | Run a command within the @PlotM@ monad. Stderr stream -- is read and decoded, while Stdout is ignored. -- Logging happens at the debug level if the command succeeds, or at --- the error level if it does not succeed.. -runCommand :: Text -> PlotM (ExitCode, Text) -runCommand command = do +-- the error level if it does not succeed. +runCommand :: FilePath -- Directory from which to run the command + -> Text -- Command to run, including executable + -> PlotM (ExitCode, Text) +runCommand wordir command = do (ec, processOutput') <- liftIO $ readProcessStderr $ setStdout nullStream $ setStderr byteStringOutput + $ setWorkingDir wordir $ shell (unpack command) let processOutput = decodeUtf8With lenientDecode $ toStrict processOutput' logFunc = if ec == ExitSuccess
src/Text/Pandoc/Filter/Plot/Renderers.hs view
@@ -27,6 +27,7 @@ , unavailableToolkits , unavailableToolkitsM , OutputSpec(..) + , Executable(..) ) where import Control.Concurrent.Async.Lifted (forConcurrently) @@ -47,7 +48,7 @@ import Text.Pandoc.Filter.Plot.Renderers.Graphviz import Text.Pandoc.Filter.Plot.Renderers.Bokeh import Text.Pandoc.Filter.Plot.Renderers.Plotsjl -import Text.Pandoc.Filter.Plot.Renderers.Prelude (executable, OutputSpec(..)) +import Text.Pandoc.Filter.Plot.Renderers.Prelude (executable, Executable(..), OutputSpec(..)) import Text.Pandoc.Filter.Plot.Monad @@ -132,7 +133,8 @@ -- The executable will need to be found first, hence the IO monad. command :: Toolkit -> OutputSpec - -> PlotM Text + -> Text -- Executable name (e.g. "python3") + -> Text command Matplotlib = matplotlibCommand command PlotlyPython = plotlyPythonCommand command PlotlyR = plotlyRCommand
src/Text/Pandoc/Filter/Plot/Renderers/Bokeh.hs view
@@ -30,16 +30,16 @@ bokehSupportedSaveFormats = [PNG, SVG, HTML] -bokehCommand :: OutputSpec -> PlotM Text -bokehCommand OutputSpec{..} = do - exe <- executable Bokeh - return [st|#{exe} "#{oScriptPath}"|] +bokehCommand :: OutputSpec -> Text -> Text +bokehCommand OutputSpec{..} exe = [st|#{exe} "#{oScriptPath}"|] bokehAvailable :: PlotM Bool bokehAvailable = do - exe <- executable Bokeh - commandSuccess [st|#{exe} -c "import bokeh; import selenium"|] + mexe <- executable Bokeh + case mexe of + Nothing -> return False + Just (Executable dir exe) -> commandSuccess dir [st|#{exe} -c "import bokeh; import selenium"|] -- | Check if `matplotlib.pyplot.show()` calls are present in the script, @@ -57,7 +57,11 @@ bokehCapture :: FigureSpec -> FilePath -> Script -bokehCapture FigureSpec{..} fname = [st| +bokehCapture = appendCapture bokehCaptureFragment + + +bokehCaptureFragment :: FigureSpec -> FilePath -> Script +bokehCaptureFragment FigureSpec{..} fname = [st| from bokeh.io import export_png, export_svgs, save from bokeh.models import Model from bokeh.resources import CDN
src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs view
@@ -20,6 +20,7 @@ , ggplot2Available ) where +import qualified Data.Text as T import Text.Pandoc.Filter.Plot.Renderers.Prelude @@ -27,20 +28,29 @@ ggplot2SupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, TIF] -ggplot2Command :: OutputSpec -> PlotM Text -ggplot2Command OutputSpec{..} = do - exe <- executable GGPlot2 - return [st|#{exe} "#{oScriptPath}"|] +ggplot2Command :: OutputSpec -> Text -> Text +ggplot2Command OutputSpec{..} exe = [st|#{exe} "#{oScriptPath}"|] ggplot2Available :: PlotM Bool ggplot2Available = do - exe <- executable GGPlot2 - commandSuccess [st|#{exe} -e 'library("ggplot2")'|] + mexe <- executable GGPlot2 + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -e 'if(!require("ggplot2")) {quit(status=1)}'|] ggplot2Capture :: FigureSpec -> FilePath -> Script -ggplot2Capture FigureSpec{..} fname = [st| +ggplot2Capture fs fp = + T.unlines [ "pdf(NULL)" -- Prevent the creation of empty Rplots.pdf + , script fs + , ggplot2CaptureFragment fs fp + ] + + +ggplot2CaptureFragment :: FigureSpec -> FilePath -> Script +ggplot2CaptureFragment FigureSpec{..} fname = [st| library(ggplot2) # just in case -ggsave("#{fname}", plot = last_plot(), dpi = #{dpi}) +ggsave("#{toRPath fname}", plot = last_plot(), dpi = #{dpi}) |]
src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs view
@@ -26,23 +26,31 @@ gnuplotSupportedSaveFormats = [PNG, SVG, EPS, GIF, JPG, PDF] -gnuplotCommand :: OutputSpec -> PlotM Text -gnuplotCommand OutputSpec{..} = do - exe <- executable GNUPlot - return [st|#{exe} -c "#{oScriptPath}"|] +gnuplotCommand :: OutputSpec -> Text -> Text +gnuplotCommand OutputSpec{..} exe = [st|#{exe} -c "#{oScriptPath}"|] gnuplotAvailable :: PlotM Bool gnuplotAvailable = do - exe <- executable GNUPlot - commandSuccess [st|#{exe} -h|] + mexe <- executable GNUPlot + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|"#{exe}" -h|] gnuplotCapture :: FigureSpec -> FilePath -> Script -gnuplotCapture FigureSpec{..} fname = [st| +gnuplotCapture = prependCapture gnuplotCaptureFragment + where + prependCapture f s fp = mconcat [f s fp, "\n", script s] + + +gnuplotCaptureFragment :: FigureSpec -> FilePath -> Script +gnuplotCaptureFragment FigureSpec{..} fname = [st| set terminal #{terminalString saveFormat} set output '#{fname}' |] + -- | Terminal name for supported save formats terminalString :: SaveFormat -> Text
src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs view
@@ -28,21 +28,23 @@ graphvizSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, WEBP, GIF] -graphvizCommand :: OutputSpec -> PlotM Text -graphvizCommand OutputSpec{..} = do - exe <- executable Graphviz +graphvizCommand :: OutputSpec -> Text -> Text +graphvizCommand OutputSpec{..} exe = let fmt = fmap toLower . show . saveFormat $ oFigureSpec dpi' = dpi oFigureSpec - return [st|#{exe} -T#{fmt} -Gdpi=#{dpi'} -o "#{oFigurePath}" "#{oScriptPath}"|] + in [st|#{exe} -T#{fmt} -Gdpi=#{dpi'} -o "#{oFigurePath}" "#{oScriptPath}"|] graphvizAvailable :: PlotM Bool graphvizAvailable = do - exe <- executable Graphviz - commandSuccess [st|#{exe} -?|] + mexe <- executable Graphviz + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -?|] -- Graphviz export is entirely based on command-line arguments -- so there is no need to modify the script itself. graphvizCapture :: FigureSpec -> FilePath -> Script -graphvizCapture _ _ = mempty +graphvizCapture FigureSpec{..} _ = script
src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs view
@@ -26,19 +26,24 @@ mathematicaSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF] -mathematicaCommand :: OutputSpec -> PlotM Text -mathematicaCommand OutputSpec{..} = do - exe <- executable Mathematica - return [st|#{exe} -script "#{oScriptPath}"|] +mathematicaCommand :: OutputSpec -> Text -> Text +mathematicaCommand OutputSpec{..} exe = [st|#{exe} -script "#{oScriptPath}"|] mathematicaAvailable :: PlotM Bool mathematicaAvailable = do - exe <- executable Mathematica - commandSuccess [st|#{exe} -h|] -- TODO: test this + mexe <- executable Mathematica + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -h|] -- TODO: test this mathematicaCapture :: FigureSpec -> FilePath -> Script -mathematicaCapture FigureSpec{..} fname = [st| +mathematicaCapture = appendCapture mathematicaCaptureFragment + + +mathematicaCaptureFragment :: FigureSpec -> FilePath -> Script +mathematicaCaptureFragment FigureSpec{..} fname = [st| Export["#{fname}", %, #{show saveFormat}, ImageResolution -> #{dpi}] |]
src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs view
@@ -29,10 +29,8 @@ matlabSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF] -matlabCommand :: OutputSpec -> PlotM Text -matlabCommand OutputSpec{..} = do - exe <- executable Matlab - return [st|#{exe} -batch "run('#{oScriptPath}')"|] +matlabCommand :: OutputSpec -> Text -> Text +matlabCommand OutputSpec{..} exe = [st|#{exe} -batch "run('#{oScriptPath}')"|] -- On Windows at least, "matlab -help" actually returns -1, even though the @@ -44,7 +42,11 @@ matlabCapture :: FigureSpec -> FilePath -> Script -matlabCapture FigureSpec{..} fname = [st| +matlabCapture = appendCapture matlabCaptureFragment + + +matlabCaptureFragment :: FigureSpec -> FilePath -> Script +matlabCaptureFragment FigureSpec{..} fname = [st| if exist("exportgraphics")>0 exportgraphics(gcf, '#{fname}', 'Resolution', #{dpi}); else
src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs view
@@ -36,14 +36,16 @@ matplotlibSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF] -matplotlibCommand :: OutputSpec -> PlotM Text -matplotlibCommand OutputSpec{..} = do - exe <- executable Matplotlib - return [st|#{exe} "#{oScriptPath}"|] +matplotlibCommand :: OutputSpec -> Text -> Text +matplotlibCommand OutputSpec{..} exe = [st|#{exe} "#{oScriptPath}"|] matplotlibCapture :: FigureSpec -> FilePath -> Script -matplotlibCapture FigureSpec{..} fname = [st| +matplotlibCapture = appendCapture matplotlibCaptureFragment + + +matplotlibCaptureFragment :: FigureSpec -> FilePath -> Script +matplotlibCaptureFragment FigureSpec{..} fname = [st| import matplotlib.pyplot as plt plt.savefig(r"#{fname}", dpi=#{dpi}, transparent=#{transparent}, bbox_inches=#{tightBox}) |] @@ -60,8 +62,11 @@ matplotlibAvailable :: PlotM Bool matplotlibAvailable = do - exe <- executable Matplotlib - commandSuccess [st|#{exe} -c "import matplotlib"|] + mexe <- executable Matplotlib + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -c "import matplotlib"|] -- | Check if `matplotlib.pyplot.show()` calls are present in the script,
src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs view
@@ -27,19 +27,24 @@ octaveSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF] -octaveCommand :: OutputSpec -> PlotM Text -octaveCommand OutputSpec{..} = do - exe <- executable Octave - return [st|#{exe} --no-gui --no-window-system "#{oScriptPath}"|] +octaveCommand :: OutputSpec -> Text -> Text +octaveCommand OutputSpec{..} exe = [st|#{exe} --no-gui --no-window-system "#{oScriptPath}"|] octaveAvailable :: PlotM Bool octaveAvailable = do - exe <- executable Octave - commandSuccess [st|#{exe} -h|] + mexe <- executable Octave + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -h|] octaveCapture :: FigureSpec -> FilePath -> Script -octaveCapture _ fname = [st| +octaveCapture = appendCapture octaveCaptureFragment + + +octaveCaptureFragment :: FigureSpec -> FilePath -> Script +octaveCaptureFragment _ fname = [st| saveas(gcf, '#{fname}') |]
src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs view
@@ -27,19 +27,25 @@ plotlyPythonSupportedSaveFormats = [PNG, JPG, WEBP, PDF, SVG, EPS, HTML] -plotlyPythonCommand :: OutputSpec -> PlotM Text -plotlyPythonCommand OutputSpec{..} = do - exe <- executable PlotlyPython - return [st|#{exe} "#{oScriptPath}"|] +plotlyPythonCommand :: OutputSpec -> Text -> Text +plotlyPythonCommand OutputSpec{..} exe = [st|#{exe} "#{oScriptPath}"|] plotlyPythonAvailable :: PlotM Bool plotlyPythonAvailable = do - exe <- executable PlotlyPython - commandSuccess [st|#{exe} -c "import plotly.graph_objects"|] + mexe <- executable Matplotlib + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -c "import plotly.graph_objects"|] + plotlyPythonCapture :: FigureSpec -> FilePath -> Script -plotlyPythonCapture FigureSpec{..} fname = [st| +plotlyPythonCapture = appendCapture plotlyPythonCaptureFragment + + +plotlyPythonCaptureFragment :: FigureSpec -> FilePath -> Script +plotlyPythonCaptureFragment FigureSpec{..} fname = [st| import plotly.graph_objects as go __current_plotly_figure = next(obj for obj in globals().values() if type(obj) == go.Figure) __current_plotly_figure.#{write_method}(r"#{fname}"#{extra_args})
src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs view
@@ -20,6 +20,7 @@ , plotlyRAvailable ) where +import qualified Data.Text as T import Text.Pandoc.Filter.Plot.Renderers.Prelude @@ -27,20 +28,29 @@ plotlyRSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, HTML] -plotlyRCommand :: OutputSpec -> PlotM Text -plotlyRCommand OutputSpec{..} = do - exe <- executable PlotlyR - return [st|#{exe} "#{oScriptPath}"|] +plotlyRCommand :: OutputSpec -> Text -> Text +plotlyRCommand OutputSpec{..} exe = [st|#{exe} "#{oScriptPath}"|] plotlyRAvailable :: PlotM Bool plotlyRAvailable = do - exe <- executable GGPlot2 - commandSuccess [st|#{exe} -e 'library("plotly")'|] + mexe <- executable PlotlyR + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -e 'if(!require("plotly")) {quit(status=1)}'|] plotlyRCapture :: FigureSpec -> FilePath -> Script -plotlyRCapture spec@FigureSpec{..} fname = case saveFormat of +plotlyRCapture fs fp = + T.unlines [ "pdf(NULL)" -- Prevent the creation of empty Rplots.pdf + , script fs + , plotlyRCaptureFragment fs fp + ] + + +plotlyRCaptureFragment :: FigureSpec -> FilePath -> Script +plotlyRCaptureFragment spec@FigureSpec{..} fname = case saveFormat of HTML -> plotlyRCaptureHtml spec fname _ -> plotlyRCaptureStatic spec fname @@ -52,7 +62,7 @@ library(plotly) # just in case library(htmlwidgets) p <- last_plot() -htmlwidgets::saveWidget(as_widget(p), "#{fname}") +htmlwidgets::saveWidget(as_widget(p), "#{toRPath fname}") |] @@ -62,5 +72,6 @@ plotlyRCaptureStatic _ fname = [st| library(plotly) # just in case if (!require("processx")) install.packages("processx") -orca(last_plot(), file = "#{fname}") +pdf(NULL) +orca(last_plot(), file = "#{toRPath fname}") |]
src/Text/Pandoc/Filter/Plot/Renderers/Plotsjl.hs view
@@ -29,19 +29,24 @@ plotsjlSupportedSaveFormats = [PNG, SVG, PDF] -plotsjlCommand :: OutputSpec -> PlotM Text -plotsjlCommand OutputSpec{..} = do - exe <- executable Plotsjl - return [st|#{exe} "#{oScriptPath}"|] +plotsjlCommand :: OutputSpec -> Text -> Text +plotsjlCommand OutputSpec{..} exe = [st|#{exe} "#{oScriptPath}"|] plotsjlAvailable :: PlotM Bool plotsjlAvailable = do - exe <- executable Plotsjl - commandSuccess [st|#{exe} -e "using Plots"|] + mexe <- executable Plotsjl + case mexe of + Nothing -> return False + Just (Executable dir exe) -> + commandSuccess dir [st|#{exe} -e "using Plots"|] plotsjlCapture :: FigureSpec -> FilePath -> Script -plotsjlCapture _ fname = [st| +plotsjlCapture = appendCapture plotsjlCaptureFragment + + +plotsjlCaptureFragment :: FigureSpec -> FilePath -> Script +plotsjlCaptureFragment _ fname = [st| savefig(raw"#{fname}") |]
src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs view
@@ -1,4 +1,4 @@- +{-# LANGUAGE OverloadedStrings #-} {-| Module : $header$ Copyright : (c) Laurent P René de Cotret, 2020 @@ -20,24 +20,36 @@ , existsOnPath , executable , OutputSpec(..) + , Executable(..) + , appendCapture + , toRPath ) where import Data.Maybe (isJust) -import Data.Text (Text, unpack) +import Data.Text (Text, unpack, pack) import System.Directory (findExecutable) +import System.FilePath (splitFileName, isPathSeparator) import System.Exit (ExitCode(..)) import Text.Shakespeare.Text (st) import Text.Pandoc.Filter.Plot.Monad +data Executable = Executable FilePath Text + +exeFromPath :: FilePath -> Executable +exeFromPath fp = let (dir, name) = splitFileName fp + in Executable dir (pack name) + -- | Check that the supplied command results in -- an exit code of 0 (i.e. no errors) -commandSuccess :: Text -> PlotM Bool -commandSuccess s = do - (ec, _) <- runCommand s +commandSuccess :: FilePath -- Directory from which to run the command + -> Text -- Command to run, including the executable + -> PlotM Bool +commandSuccess fp s = do + (ec, _) <- runCommand fp s return $ ec == ExitSuccess @@ -48,26 +60,31 @@ -- | Try to find the executable and normalise its path. -- If it cannot be found, it is left unchanged - just in case. -tryToFindExe :: String -> IO FilePath -tryToFindExe fp = findExecutable fp >>= maybe (return fp) return +tryToFindExe :: String -> IO (Maybe Executable) +tryToFindExe fp = findExecutable fp >>= return . fmap exeFromPath --- | Path to the executable of a toolkit. If the executable can --- be found, then it will be the full path to it. -executable :: Toolkit -> PlotM FilePath -executable Matplotlib = asksConfig matplotlibExe >>= liftIO . tryToFindExe -executable PlotlyPython = asksConfig plotlyPythonExe >>= liftIO . tryToFindExe -executable PlotlyR = asksConfig plotlyRExe >>= liftIO . tryToFindExe -executable Matlab = asksConfig matlabExe >>= liftIO . tryToFindExe -executable Mathematica = asksConfig mathematicaExe >>= liftIO . tryToFindExe -executable Octave = asksConfig octaveExe >>= liftIO . tryToFindExe -executable GGPlot2 = asksConfig ggplot2Exe >>= liftIO . tryToFindExe -executable GNUPlot = asksConfig gnuplotExe >>= liftIO . tryToFindExe -executable Graphviz = asksConfig graphvizExe >>= liftIO . tryToFindExe -executable Bokeh = asksConfig bokehExe >>= liftIO . tryToFindExe -executable Plotsjl = asksConfig plotsjlExe >>= liftIO . tryToFindExe +-- | Path to (directory, executable) of a toolkit. +executable :: Toolkit -> PlotM (Maybe Executable) +executable Matplotlib = asksConfig matplotlibExe >>= liftIO . tryToFindExe +executable PlotlyPython = asksConfig plotlyPythonExe >>= liftIO . tryToFindExe +executable PlotlyR = asksConfig plotlyRExe >>= liftIO . tryToFindExe +executable Matlab = asksConfig matlabExe >>= liftIO . tryToFindExe +executable Mathematica = asksConfig mathematicaExe >>= liftIO . tryToFindExe +executable Octave = asksConfig octaveExe >>= liftIO . tryToFindExe +executable GGPlot2 = asksConfig ggplot2Exe >>= liftIO . tryToFindExe +executable GNUPlot = asksConfig gnuplotExe >>= liftIO . tryToFindExe +executable Graphviz = asksConfig graphvizExe >>= liftIO . tryToFindExe +executable Bokeh = asksConfig bokehExe >>= liftIO . tryToFindExe +executable Plotsjl = asksConfig plotsjlExe >>= liftIO . tryToFindExe +-- | A shortcut to append capture script fragments to scripts +appendCapture :: (FigureSpec -> FilePath -> Script) + -> FigureSpec -> FilePath -> Script +appendCapture f s fp = mconcat [script s, "\n", f s fp] + + -- | Internal description of all information -- needed to output a figure. data OutputSpec = OutputSpec @@ -75,3 +92,8 @@ , oScriptPath :: FilePath -- ^ Path to the script to render , oFigurePath :: FilePath -- ^ Figure output path } + + +-- | R paths use the '/' path separator +toRPath :: FilePath -> FilePath +toRPath = fmap (\c -> if isPathSeparator c then '/' else c)
src/Text/Pandoc/Filter/Plot/Scripting.hs view
@@ -27,7 +27,8 @@ import Paths_pandoc_plot (version) import System.Directory (createDirectoryIfMissing, - doesFileExist, getTemporaryDirectory) + doesFileExist, getTemporaryDirectory, + getCurrentDirectory) import System.Exit (ExitCode (..)) import System.FilePath (addExtension, normalise, replaceExtension, @@ -88,40 +89,46 @@ CheckPassed -> do scriptPath <- tempScriptPath spec target <- figurePath spec - let captureFragment = (capture toolkit) spec target - -- 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 (toolkit == GNUPlot) - then mconcat [captureFragment, "\n", script] - else mconcat [script, "\n", captureFragment] - liftIO $ T.writeFile scriptPath scriptWithCapture - let outputSpec = OutputSpec { oFigureSpec = spec - , oScriptPath = scriptPath - , oFigurePath = target - } - 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 <- toolkitAvailable toolkit - if toolkitInstalled - then return $ ScriptFailure command_ code - else return $ ToolkitNotInstalled toolkit + -- Check if executable is present + exe <- executable toolkit + case exe of + Nothing -> error $ "Toolkit " <> show toolkit <> " is not installed." + Just (Executable exedir exename) -> do + -- Commands are run from the executable directory, + -- so we need to tell the full absolute path where to save the + -- figure + curdir <- liftIO $ getCurrentDirectory + let scriptWithCapture = (capture toolkit) spec (curdir </> target) + liftIO $ T.writeFile scriptPath scriptWithCapture + let outputSpec = OutputSpec { oFigureSpec = spec + , oScriptPath = scriptPath + , oFigurePath = target + } + + let command_ = command toolkit outputSpec exename + (ec, _) <- runCommand exedir 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 <- toolkitAvailable toolkit + if toolkitInstalled + then return $ ScriptFailure command_ code + else return $ ToolkitNotInstalled toolkit + + -- | Determine the temp script path from Figure specifications -- Note that for certain renderers, the appropriate file extension -- is important. tempScriptPath :: FigureSpec -> PlotM FilePath tempScriptPath FigureSpec{..} = do 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 + -- MATLAB will refuse to process files that don't start with + -- a letter -- 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.
tests/Common.hs view
@@ -31,7 +31,9 @@ defaultTestConfig :: Configuration -defaultTestConfig = defaultConfiguration {logVerbosity=Silent, logSink=StdErr} +defaultTestConfig = defaultConfiguration { logVerbosity = Silent + , logSink = StdErr --LogFile "test_log.txt" + } ------------------------------------------------------------------------------- -- Test that plot files and source files are created when the filter is run @@ -66,6 +68,7 @@ where include Matplotlib = "tests/includes/matplotlib.py" include PlotlyPython = "tests/includes/plotly-python.py" + include PlotlyR = "tests/includes/plotly-r.r" include Matlab = "tests/includes/matlabplot.m" include Mathematica = "tests/includes/mathplot.m" include Octave = "tests/includes/octave.m" @@ -73,7 +76,7 @@ include GNUPlot = "tests/includes/gnuplot.gp" include Graphviz = "tests/includes/graphviz.dot" include Bokeh = "tests/includes/bokeh.py" - include Plotsjl = "tests/includes/plotsjl.jl" + include Plotsjl = "tests/includes/plotsjl.jl" ------------------------------------------------------------------------------- -- Test that the files are saved in the appropriate format @@ -262,6 +265,7 @@ trivialContent :: Toolkit -> Script trivialContent Matplotlib = "import matplotlib.pyplot as plt\n" trivialContent PlotlyPython = "import plotly.graph_objects as go; fit = go.Figure()\n" +trivialContent PlotlyR = "library(plotly)\nfig <- plot_ly(midwest, x = ~percollege, color = ~state, type = \"box\")" trivialContent Matlab = "figure('visible', 'off')\n" trivialContent Mathematica = "\n" trivialContent Octave = "figure('visible', 'off')\nplot (-10:0.1:10);"