pandoc-plot-1.0.1.0: src/Text/Pandoc/Filter/Plot.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module : $header$
-- Description : Pandoc filter to create figures from code blocks using your plotting toolkit of choice
-- Copyright : (c) Laurent P René de Cotret, 2020
-- License : GNU GPL, version 2 or above
-- Maintainer : laurent.decotret@outlook.com
-- Stability : unstable
-- Portability : portable
--
-- This module defines a Pandoc filter @plotTransform@ and related functions
-- that can be used to walk over a Pandoc document and generate figures from
-- code blocks, using a multitude of plotting toolkits.
--
-- The syntax for code blocks is simple. Code blocks with the appropriate class
-- attribute will trigger the filter:
--
-- * @matplotlib@ for matplotlib-based Python plots;
-- * @plotly_python@ for Plotly-based Python plots;
-- * @plotly_r@ for Plotly-based R plots;
-- * @matlabplot@ for MATLAB plots;
-- * @mathplot@ for Mathematica plots;
-- * @octaveplot@ for GNU Octave plots;
-- * @ggplot2@ for ggplot2-based R plots;
-- * @gnuplot@ for gnuplot plots;
-- * @graphviz@ for Graphviz graphs;
-- * @bokeh@ for Bokeh-based Python plots;
-- * @plotsjl@ for Plots.jl-based Julia plots;
--
-- For example, in Markdown:
--
-- @
-- This is a paragraph.
--
-- ```{.matlabplot}
-- figure()
-- plot([1,2,3,4,5], [1,2,3,4,5], '-k')
-- ```
-- @
--
-- The code block will be reworked into a script and the output figure will be captured. Optionally, the source code
-- used to generate the figure will be linked in the caption.
--
-- Here are the possible attributes what pandoc-plot understands for ALL toolkits:
--
-- * @directory=...@ : Directory where to save the figure. This path should be specified with
-- respect to the current working directory, and not with respect to the document.
-- * @source=true|false@ : Whether or not to link the source code of this figure in the caption.
-- Ideal for web pages, for example. Default is false.
-- * @format=...@: Format of the generated figure. This can be an extension or an acronym,
-- e.g. @format=PNG@.
-- * @caption="..."@: Specify a plot caption (or alternate text). Format
-- for captions is specified in the documentation for the @Configuration@ type.
-- * @dpi=...@: Specify a value for figure resolution, or dots-per-inch. Certain toolkits ignore this.
-- * @dependencies=[...]@: Specify files/directories on which a figure depends, e.g. data file.
-- Figures will be re-rendered if one of those file/directory changes. These paths should
-- be specified with respect to the current working directory, and not with respect to the document.
-- * @preamble=...@: Path to a file to include before the code block. Ideal to avoid repetition over
-- many figures.
-- * @file=...@: Path to a file from which to read the content of the figure. The content of the
-- code block will be ignored. This path should be specified with respect to the current working
-- directory, and not with respect to the document.
--
-- Default values for the above attributes are stored in the @Configuration@ datatype. These can be specified in a
-- YAML file.
--
-- Here is an example code block which will render a figure using gnuplot, in Markdown:
--
-- @
-- ```{.gnuplot format=png caption="Sinusoidal function" source=true}
-- sin(x)
--
-- set xlabel "x"
-- set ylabel "y"
-- ```
-- @
module Text.Pandoc.Filter.Plot
( -- * Operating on whole Pandoc documents
plotTransform,
-- * Cleaning output directories
cleanOutputDirs,
-- * Runtime configuration
configuration,
defaultConfiguration,
Configuration (..),
Verbosity (..),
LogSink (..),
SaveFormat (..),
Script,
-- * Determining available plotting toolkits
Toolkit (..),
availableToolkits,
unavailableToolkits,
toolkits,
supportedSaveFormats,
-- * Version information
pandocPlotVersion,
-- * For embedding, testing and internal purposes ONLY. Might change without notice.
make,
makeEither,
PandocPlotError (..),
)
where
import Control.Concurrent.Async.Lifted (mapConcurrently)
import Data.Text (Text, unpack)
import Data.Version (Version)
import Paths_pandoc_plot (version)
import Text.Pandoc.Definition (Block, Pandoc (..))
import Text.Pandoc.Filter.Plot.Internal
( Configuration (..),
FigureSpec,
LogSink (..),
PlotM,
RuntimeEnv (envConfig),
SaveFormat (..),
Script,
ScriptResult (..),
Toolkit (..),
Verbosity (..),
asks,
availableToolkits,
cleanOutputDirs,
configuration,
defaultConfiguration,
parseFigureSpec,
runPlotM,
runScriptIfNecessary,
supportedSaveFormats,
toFigure,
toolkits,
unavailableToolkits,
)
-- | Walk over an entire Pandoc document, transforming appropriate code blocks
-- into figures. This function will operate on blocks in parallel if possible.
--
-- 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.
plotTransform ::
-- | Configuration for default values
Configuration ->
-- | Input document
Pandoc ->
IO Pandoc
plotTransform conf (Pandoc meta blocks) =
runPlotM conf $ mapConcurrently make blocks >>= return . Pandoc meta
-- | The version of the pandoc-plot package.
--
-- @since 0.8.0.0
pandocPlotVersion :: Version
pandocPlotVersion = version
-- | Try to process the block with `pandoc-plot`. If a failure happens (or the block)
-- was not meant to become a figure, return the block as-is.
make :: Block -> PlotM Block
make blk = either (const (return blk)) return =<< makeEither blk
-- | Try to process the block with `pandoc-plot`, documenting the error.
makeEither :: Block -> PlotM (Either PandocPlotError Block)
makeEither block =
parseFigureSpec block
>>= maybe
(return $ Right block)
(\s -> runScriptIfNecessary s >>= handleResult s)
where
-- Logging of errors has been taken care of in @runScriptIfNecessary@
handleResult :: FigureSpec -> ScriptResult -> PlotM (Either PandocPlotError Block)
handleResult _ (ScriptFailure msg code) = return $ Left (ScriptRuntimeError msg code)
handleResult _ (ScriptChecksFailed msg) = return $ Left (ScriptChecksFailedError msg)
handleResult _ (ToolkitNotInstalled tk') = return $ Left (ToolkitNotInstalledError tk')
handleResult spec ScriptSuccess = asks envConfig >>= \c -> Right <$> toFigure (captionFormat c) spec
data PandocPlotError
= 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: " <> unpack msg <> "."
show (ToolkitNotInstalledError tk) = "ERROR (pandoc-plot) The " <> show tk <> " toolkit is required but not installed."