summaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Filter
diff options
context:
space:
mode:
authorLaurentRDC <>2018-12-29 19:13:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2018-12-29 19:13:00 (GMT)
commitddff8579481f7ce672d15886ab5a38574fc7255f (patch)
tree61c88f41150726cd58fa27f8319d22bc0075fe5d /src/Text/Pandoc/Filter
parentf04836983b6e75707808341d0f6a5af4e12b8caf (diff)
version 1.1.0.01.1.0.0
Diffstat (limited to 'src/Text/Pandoc/Filter')
-rw-r--r--src/Text/Pandoc/Filter/Pyplot.hs103
1 files changed, 66 insertions, 37 deletions
diff --git a/src/Text/Pandoc/Filter/Pyplot.hs b/src/Text/Pandoc/Filter/Pyplot.hs
index 79eeb30..53152b4 100644
--- a/src/Text/Pandoc/Filter/Pyplot.hs
+++ b/src/Text/Pandoc/Filter/Pyplot.hs
@@ -12,17 +12,42 @@ Portability : portable
This module defines a Pandoc filter @makePlot@ that can be
used to walk over a Pandoc document and generate figures from
Python code blocks.
+
+The syntax for code blocks is simple, Code blocks with the @plot_target=...@
+attribute will trigger the filter. The code block will be reworked into a Python
+script and the output figure will be captured.
+
+Here are the possible attributes what pandoc-pyplot understands:
+
+ * @plot_target=...@ (_required_): Filepath where the resulting figure should be saved.
+ * @plot_alt="..."@ (_optional_): Specify a plot caption (or alternate text).
+ * @plot_include=...@ (_optional_): Path to a Python script to include before the code block.
+ Ideal to avoid repetition over many figures.
+
+Here are some example blocks in Markdown:
+
+@
+This is a paragraph
+
+```{plot_target=my_figure.jpg plot_alt="This is a caption."}
+import matplotlib.pyplot as plt
+
+plt.figure()
+plt.plot([0,1,2,3,4], [1,2,3,4,5])
+plt.title('This is an example figure')
+```
+@
-}
module Text.Pandoc.Filter.Pyplot (
makePlot
- , makePlot'
+ , makePlot' -- For testing
, plotTransform
, PandocPyplotError(..)
- , showError
) where
import Control.Monad ((>=>))
import qualified Data.Map.Strict as M
+import Data.Maybe (fromMaybe)
import Data.Monoid ((<>))
import System.Directory (doesDirectoryExist)
import System.FilePath (isValid, replaceExtension, takeDirectory)
@@ -37,33 +62,39 @@ data PandocPyplotError = ScriptError Int -- ^ Running Python scri
| InvalidTargetError FilePath -- ^ Invalid figure path
| MissingDirectoryError FilePath -- ^ Directory where to save figure does not exist
| BlockingCallError -- ^ Python script contains a block call to 'show()'
+ deriving Eq
+
+instance Show PandocPyplotError where
+ -- | Translate filter error to an error message
+ show (ScriptError exitcode) = "Script error: plot could not be generated. Exit code " <> (show exitcode)
+ show (InvalidTargetError fname) = "Target filename " <> fname <> " is not valid."
+ show (MissingDirectoryError dirname) = "Target directory " <> dirname <> " does not exist."
+ show BlockingCallError = "Script contains a blocking call to show, like 'plt.show()'"
+
-- | Datatype containing all parameters required
-- to run pandoc-pyplot
data FigureSpec = FigureSpec
- { target :: FilePath -- ^ filepath where generated figure will be saved
- , alt :: String -- ^ Alternate text for the figure (optional)
- , script :: PythonScript -- ^ Source code for the figure
- , blockAttrs :: Attr -- ^ Attributes not related to @pandoc-pyplot@ will be propagated
+ { target :: FilePath -- ^ filepath where generated figure will be saved.
+ , alt :: String -- ^ Alternate text for the figure (optional).
+ , script :: PythonScript -- ^ Source code for the figure.
+ , includePath :: Maybe FilePath -- ^ Path to a Python to be included before the script.
+ , blockAttrs :: Attr -- ^ Attributes not related to @pandoc-pyplot@ will be propagated.
}
--- | Get the source code for a script including provisions to capture
--- the output.
-scriptWithCapture :: FigureSpec -> PythonScript
-scriptWithCapture spec = addPlotCapture (target spec) (script spec)
-
--- | Determine where to save the script source based on plot target
-scriptSourcePath :: FigureSpec -> FilePath
-scriptSourcePath spec = replaceExtension (target spec) ".txt"
-
--- | Get the source code for a figure script in a presentable way
-presentableScript :: FigureSpec -> PythonScript
-presentableScript spec = mconcat [ "# Source code for ", target spec, "\n", script spec ]
+-- | Use figure specification to render a full plot script, including everything except plot capture
+renderScript :: FigureSpec -> IO PythonScript
+renderScript spec = do
+ includeScript <- fromMaybe (return "") $ readFile <$> (includePath spec)
+ return $ mconcat [ "# Source code for ", target spec, "\n"
+ , "# Generated by pandoc-pyplot\n"
+ , includeScript, "\n", script spec]
-- Keys that pandoc-pyplot will look for in code blocks
-targetKey, altTextKey :: String
-targetKey = "plot_target"
-altTextKey = "plot_alt"
+targetKey, altTextKey, includePathKey :: String
+targetKey = "plot_target"
+altTextKey = "plot_alt"
+includePathKey = "plot_include"
-- | Determine inclusion specifications from Block attributes.
-- Note that the target key is required, but all other parameters are optional
@@ -78,6 +109,7 @@ parseFigureSpec (CodeBlock (id', cls, attrs) content) =
{ target = fname
, alt = M.findWithDefault "Figure generated by pandoc-pyplot" altTextKey attrs'
, script = content
+ , includePath = M.lookup includePathKey attrs'
-- Propagate attributes that are not related to pandoc-pyplot
, blockAttrs = (id', cls, filteredAttrs)
}
@@ -95,21 +127,25 @@ makePlot' block =
Nothing -> return $ Right block
-- Could parse : run the script and capture output
Just spec -> do
+
+ -- Rendered script, including possible inclusions and other additions
+ -- except the plot capture.
+ rendered <- renderScript spec
+
let figurePath = target spec
figureDir = takeDirectory figurePath
- scriptSource = script spec
-- Check that the directory in which to save the figure exists
- validDirectory <- doesDirectoryExist figureDir
+ validDirectory <- doesDirectoryExist $ takeDirectory figurePath
if | not (isValid figurePath) -> return $ Left $ InvalidTargetError figurePath
| not validDirectory -> return $ Left $ MissingDirectoryError figureDir
- | hasBlockingShowCall scriptSource -> return $ Left $ BlockingCallError
+ | hasBlockingShowCall rendered -> return $ Left $ BlockingCallError
| otherwise -> do
- -- Running the script happens on the next line
- -- Note that the script is slightly modified to be able to capture the output
- result <- runTempPythonScript (scriptWithCapture spec)
+ -- Running the script
+ -- A plot capture (plt.savefig(...)) is added as well
+ result <- runTempPythonScript $ addPlotCapture (target spec) rendered
case result of
ScriptFailure code -> return $ Left $ ScriptError code
@@ -118,8 +154,8 @@ makePlot' block =
-- so it can be inspected
-- Note : using a .txt file allows to view source directly
-- in the browser, in the case of HTML output
- let sourcePath = scriptSourcePath spec
- writeFile sourcePath (presentableScript spec)
+ let sourcePath = replaceExtension figurePath ".txt"
+ writeFile sourcePath rendered
-- Propagate attributes that are not related to pandoc-pyplot
let relevantAttrs = blockAttrs spec
@@ -132,18 +168,11 @@ makePlot' block =
return $ Right $ Para $ [image]
--- | Translate filter error to an error message
-showError :: PandocPyplotError -> String
-showError (ScriptError exitcode) = "Script error: plot could not be generated. Exit code " <> (show exitcode)
-showError (InvalidTargetError fname) = "Target filename " <> fname <> " is not valid."
-showError (MissingDirectoryError dirname) = "Target directory " <> dirname <> " does not exist."
-showError BlockingCallError = "Script contains a blocking call to show, like 'plt.show()'"
-
-- | Highest-level function that can be walked over a Pandoc tree.
-- All code blocks that have the 'plot_target' parameter will be considered
-- figures.
makePlot :: Block -> IO Block
-makePlot = makePlot' >=> either (fail . showError) return
+makePlot = makePlot' >=> either (fail . show) return
-- | Walk over an entire Pandoc document, changing appropriate code blocks
-- into figures.