summaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Filter
diff options
context:
space:
mode:
authorLaurentRDC <>2018-10-14 00:45:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2018-10-14 00:45:00 (GMT)
commit5b5d90b1b074de620d776d5bcb58348b1af297cd (patch)
treed5b5ca98eacd686d8a7afae21ed1d094279255f0 /src/Text/Pandoc/Filter
parent0619d9948775e6bbc8a57c2b9a926054b4c42411 (diff)
version 1.0.1.01.0.1.0
Diffstat (limited to 'src/Text/Pandoc/Filter')
-rw-r--r--src/Text/Pandoc/Filter/Pyplot.hs104
-rw-r--r--src/Text/Pandoc/Filter/Scripting.hs18
2 files changed, 73 insertions, 49 deletions
diff --git a/src/Text/Pandoc/Filter/Pyplot.hs b/src/Text/Pandoc/Filter/Pyplot.hs
index 512b7e1..dc2667f 100644
--- a/src/Text/Pandoc/Filter/Pyplot.hs
+++ b/src/Text/Pandoc/Filter/Pyplot.hs
@@ -16,31 +16,50 @@ Python code blocks.
module Text.Pandoc.Filter.Pyplot (
makePlot
, makePlot'
+ , plotTransform
, PandocPyplotError(..)
, showError
) where
-import Control.Monad ((>=>))
-import qualified Data.Map.Strict as M
-import System.FilePath (replaceExtension, isValid)
+import Control.Monad ((>=>))
+import qualified Data.Map.Strict as M
+import System.Directory (doesDirectoryExist)
+import System.FilePath (isValid, replaceExtension, takeDirectory)
import Text.Pandoc.Definition
+import Text.Pandoc.Walk (walkM)
-import Text.Pandoc.Filter.Scripting
+import Text.Pandoc.Filter.Scripting
-- | Possible errors returned by the filter
data PandocPyplotError = ScriptError Int -- ^ Running Python script has yielded an error
| InvalidTargetError FilePath -- ^ Invalid figure path
+ | MissingDirectoryError FilePath -- ^ Directory where to save figure does not exist
| BlockingCallError -- ^ Python script contains a block call to '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)
- , caption :: String -- ^ Figure caption (optional)
+ { target :: FilePath -- ^ filepath where generated figure will be saved
+ , alt :: String -- ^ Alternate text for the figure (optional)
+ , caption :: String -- ^ Figure caption (optional)
+ , script :: PythonScript -- ^ Source code for the figure
+ , 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 ]
+
-- Keys that pandoc-pyplot will look for in code blocks
targetKey, altTextKey, captionKey :: String
targetKey = "plot_target"
@@ -49,23 +68,22 @@ captionKey = "plot_caption"
-- | Determine inclusion specifications from Block attributes.
-- Note that the target key is required, but all other parameters are optional
-parseFigureSpec :: M.Map String String -> Maybe FigureSpec
-parseFigureSpec attrs = createInclusion <$> M.lookup targetKey attrs
+parseFigureSpec :: Block -> Maybe FigureSpec
+parseFigureSpec (CodeBlock (id', cls, attrs) content) =
+ createInclusion <$> M.lookup targetKey attrs'
where
- defaultAltText = "Figure generated by pandoc-pyplot"
- defaultCaption = mempty
+ attrs' = M.fromList attrs
+ inclusionKeys = [ targetKey, altTextKey, captionKey ]
+ filteredAttrs = filter (\(k,_) -> k `notElem` inclusionKeys) attrs
createInclusion fname = FigureSpec
- { target = fname
- , alt = M.findWithDefault defaultAltText altTextKey attrs
- , caption = M.findWithDefault defaultCaption captionKey attrs
+ { target = fname
+ , alt = M.findWithDefault "Figure generated by pandoc-pyplot" altTextKey attrs'
+ , caption = M.findWithDefault mempty captionKey attrs'
+ , script = content
+ -- Propagate attributes that are not related to pandoc-pyplot
+ , blockAttrs = (id', cls, filteredAttrs)
}
-
--- | Format the script source based on figure spec.
-formatScriptSource :: FigureSpec -> PythonScript -> PythonScript
-formatScriptSource spec script = mconcat [ "# Source code for " <> target spec
- , "\n"
- , script
- ]
+parseFigureSpec _ = Nothing
-- | Main routine to include Matplotlib plots.
-- Code blocks containing the attributes @plot_target@ are considered
@@ -73,20 +91,27 @@ formatScriptSource spec script = mconcat [ "# Source code for " <> target spec
-- The source code is also saved in another file, which can be access by
-- clicking the image
makePlot' :: Block -> IO (Either PandocPyplotError Block)
-makePlot' cb @ (CodeBlock (id', cls, attrs) scriptSource) =
- case parseFigureSpec (M.fromList attrs) of
+makePlot' block =
+ case parseFigureSpec block of
-- Could not parse - leave code block unchanged
- Nothing -> return $ Right cb
+ Nothing -> return $ Right block
-- Could parse : run the script and capture output
Just spec -> do
let figurePath = target spec
+ figureDir = takeDirectory figurePath
+ scriptSource = script spec
+
+ -- Check that the directory in which to save the figure exists
+ validDirectory <- doesDirectoryExist figureDir
if | not (isValid figurePath) -> return $ Left $ InvalidTargetError figurePath
+ | not validDirectory -> return $ Left $ MissingDirectoryError figureDir
| hasBlockingShowCall scriptSource -> return $ Left $ BlockingCallError
| otherwise -> do
- script <- addPlotCapture figurePath scriptSource
- result <- runTempPythonScript script
+ -- 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)
case result of
ScriptFailure code -> return $ Left $ ScriptError code
@@ -95,31 +120,34 @@ makePlot' cb @ (CodeBlock (id', cls, attrs) scriptSource) =
-- 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 = replaceExtension figurePath ".txt"
- writeFile sourcePath $ formatScriptSource spec scriptSource
+ let sourcePath = scriptSourcePath spec
+ writeFile sourcePath (presentableScript spec)
-- Propagate attributes that are not related to pandoc-pyplot
- let inclusionKeys = [ targetKey, altTextKey, captionKey ]
- filteredAttrs = filter (\(k,_) -> k `notElem` inclusionKeys) attrs
- image = Image (id', cls, filteredAttrs) [Str $ alt spec] (figurePath, "")
- srcTarget = (sourcePath, "Click on this figure to see the source code")
+ let relevantAttrs = blockAttrs spec
+ image = Image relevantAttrs [Str $ alt spec] (figurePath, "")
+ srcTarget = (sourcePath, "Click on this figure to see the source code")
-- TODO: use FigureSpec caption
-- We make the figure be a link to the source code
return $ Right $ Para [
Link nullAttr [image] srcTarget
]
-
-makePlot' x = return $ Right x
-- | 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 BlockingCallError = "Script contains a blocking call to show, like 'plt.show()'"
+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 \ No newline at end of file
+makePlot = makePlot' >=> either (fail . showError) return
+
+-- | Walk over an entire Pandoc document, changing appropriate code blocks
+-- into figures.
+plotTransform :: Pandoc -> IO Pandoc
+plotTransform = walkM makePlot \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Scripting.hs b/src/Text/Pandoc/Filter/Scripting.hs
index b88b039..18cb79a 100644
--- a/src/Text/Pandoc/Filter/Scripting.hs
+++ b/src/Text/Pandoc/Filter/Scripting.hs
@@ -19,9 +19,8 @@ module Text.Pandoc.Filter.Scripting (
, ScriptResult(..)
) where
-import System.Directory (getCurrentDirectory)
import System.Exit (ExitCode(..))
-import System.FilePath ((</>), isAbsolute)
+import System.FilePath ((</>))
import System.IO.Temp (getCanonicalTemporaryDirectory)
import System.Process.Typed (runProcess, shell)
@@ -51,15 +50,12 @@ runTempPythonScript script = do
-- | Modify a Python plotting script to save the figure to a filename.
addPlotCapture :: FilePath -- ^ Path where to save the figure
-> PythonScript -- ^ Raw code block
- -> IO PythonScript -- ^ Code block with added capture
-addPlotCapture fname content = do
- absFname <- if isAbsolute fname
- then (return fname)
- else (</> fname) <$> getCurrentDirectory
- return $ mconcat [ content
- , "\nimport matplotlib.pyplot as plt" -- Just in case
- , "\nplt.savefig(" <> show absFname <> ")\n\n"
- ]
+ -> PythonScript -- ^ Code block with added capture
+addPlotCapture fname content =
+ mconcat [ content
+ , "\nimport matplotlib.pyplot as plt" -- Just in case
+ , "\nplt.savefig(" <> show fname <> ")\n\n"
+ ]
-- | Detect the presence of a blocking show call, for example "plt.show()"
hasBlockingShowCall :: PythonScript -> Bool