summaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/Filter
diff options
context:
space:
mode:
authorLaurentRDC <>2019-04-03 01:01:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2019-04-03 01:01:00 (GMT)
commitb870c74b0ab480ca62602ebfd5fe89e3fc4f7094 (patch)
tree16847bd8b32e3d97a947e29b4cd073fd53938bd5 /src/Text/Pandoc/Filter
parent45a607a412483fde3e660e0bfea5cd0fbe9ac02b (diff)
version 2.0.1.02.0.1.0
Diffstat (limited to 'src/Text/Pandoc/Filter')
-rw-r--r--src/Text/Pandoc/Filter/FigureSpec.hs63
-rw-r--r--src/Text/Pandoc/Filter/Pyplot.hs122
2 files changed, 94 insertions, 91 deletions
diff --git a/src/Text/Pandoc/Filter/FigureSpec.hs b/src/Text/Pandoc/Filter/FigureSpec.hs
index ff835ec..ae48bbd 100644
--- a/src/Text/Pandoc/Filter/FigureSpec.hs
+++ b/src/Text/Pandoc/Filter/FigureSpec.hs
@@ -15,22 +15,55 @@ module Text.Pandoc.Filter.FigureSpec
( FigureSpec(..)
, SaveFormat(..)
, saveFormatFromString
+ , toImage
+ , sourceCodePath
, figurePath
- , hiresFigurePath
, addPlotCapture
-- for testing purposes
, extension
) where
+import Control.Monad (join)
+
import Data.Hashable (Hashable, hash, hashWithSalt)
+import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import System.FilePath (FilePath, addExtension,
replaceExtension, (</>))
-import Text.Pandoc.Definition (Attr)
+import Text.Pandoc.Definition
+import Text.Pandoc.Builder (imageWith, link, para, fromList, toList)
import Text.Pandoc.Filter.Scripting (PythonScript)
+import Text.Pandoc.Class (runPure)
+import Text.Pandoc.Extensions (extensionsFromList, Extension(..))
+import Text.Pandoc.Options (def, ReaderOptions(..))
+import Text.Pandoc.Readers (readMarkdown)
+
+readerOptions :: ReaderOptions
+readerOptions = def
+ {readerExtensions =
+ extensionsFromList
+ [ Ext_tex_math_dollars
+ , Ext_superscript
+ , Ext_subscript
+ ]
+ }
+
+-- | Read a figure caption in Markdown format.
+captionReader :: String -> Maybe [Inline]
+captionReader t = either (const Nothing) (Just . extractFromBlocks) $ runPure $ readMarkdown' (T.pack t)
+ where
+ readMarkdown' = readMarkdown readerOptions
+
+ extractFromBlocks (Pandoc _ blocks) = mconcat $ extractInlines <$> blocks
+
+ extractInlines (Plain inlines) = inlines
+ extractInlines (Para inlines) = inlines
+ extractInlines (LineBlock multiinlines) = join multiinlines
+ extractInlines _ = []
+
data SaveFormat
= PNG
| PDF
@@ -71,6 +104,21 @@ instance Hashable FigureSpec where
hashWithSalt salt spec =
hashWithSalt salt (caption spec, script spec, directory spec, dpi spec, blockAttrs spec)
+-- | Convert a FigureSpec to a Pandoc block component
+toImage :: FigureSpec -> Block
+toImage spec = head . toList $ para $ imageWith attrs' target' "fig:" caption'
+ -- To render images as figures with captions, the target title
+ -- must be "fig:"
+ -- Janky? yes
+ where
+ attrs' = blockAttrs spec
+ target' = figurePath spec
+ srcLink = link (replaceExtension target' ".txt") mempty "Source code"
+ hiresLink = link (hiresFigurePath spec) mempty "high res."
+ captionText = fromList $ fromMaybe mempty (captionReader $ caption spec)
+ captionLinks = mconcat [" (", srcLink, ", ", hiresLink, ")"]
+ caption' = captionText <> captionLinks
+
-- | Determine the path a figure should have.
figurePath :: FigureSpec -> FilePath
figurePath spec = (directory spec </> stem spec)
@@ -78,6 +126,10 @@ figurePath spec = (directory spec </> stem spec)
stem = flip addExtension ext . show . hash
ext = extension . saveFormat $ spec
+-- | Determine the path to the source code that generated the figure.
+sourceCodePath :: FigureSpec -> FilePath
+sourceCodePath = flip replaceExtension ".txt" . figurePath
+
-- | The path to the high-resolution figure.
hiresFigurePath :: FigureSpec -> FilePath
hiresFigurePath spec = flip replaceExtension (".hires" <> ext) . figurePath $ spec
@@ -85,10 +137,9 @@ hiresFigurePath spec = flip replaceExtension (".hires" <> ext) . figurePath $ sp
ext = extension . saveFormat $ spec
-- | Modify a Python plotting script to save the figure to a filename.
--- An additional file (with extension PNG) will also be captured.
-addPlotCapture ::
- FigureSpec -- ^ Path where to save the figure
- -> PythonScript -- ^ Code block with added capture
+-- An additional file will also be captured.
+addPlotCapture :: FigureSpec -- ^ Path where to save the figure
+ -> PythonScript -- ^ Code block with added capture
addPlotCapture spec =
mconcat
[ script spec
diff --git a/src/Text/Pandoc/Filter/Pyplot.hs b/src/Text/Pandoc/Filter/Pyplot.hs
index f0f692d..7c545d9 100644
--- a/src/Text/Pandoc/Filter/Pyplot.hs
+++ b/src/Text/Pandoc/Filter/Pyplot.hs
@@ -94,80 +94,63 @@ import Paths_pandoc_pyplot (version)
import System.Directory (createDirectoryIfMissing,
doesFileExist)
-import System.FilePath (isValid, makeValid,
- replaceExtension, takeDirectory)
+import System.FilePath (makeValid, takeDirectory)
import Text.Pandoc.Definition
import Text.Pandoc.Walk (walkM)
import Text.Pandoc.Filter.FigureSpec (FigureSpec (..),
SaveFormat (..), addPlotCapture,
- figurePath, hiresFigurePath,
- saveFormatFromString)
+ figurePath, sourceCodePath, saveFormatFromString,
+ toImage)
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
- | BlockingCallError -- ^ Python script contains a block call to 'show()'
+ = ScriptError Int -- ^ Running Python script has yielded an error
+ | BlockingCallError -- ^ Python script contains a block call to 'show()'
deriving (Eq)
--- | Translate filter error to an error message
instance Show PandocPyplotError where
- show (ScriptError exitcode) =
- "Script error: plot could not be generated. Exit code " <> (show exitcode)
- show (InvalidTargetError fname) = "Target filename " <> fname <> " is not valid."
- show BlockingCallError = "Script contains a blocking call to show, like 'plt.show()'"
+ show (ScriptError exitcode) = "Script error: plot could not be generated. Exit code " <> (show exitcode)
+ show BlockingCallError = "Script contains a blocking call to show, like 'plt.show()'"
-- | Keys that pandoc-pyplot will look for in code blocks. These are only exported for testing purposes.
directoryKey, captionKey, dpiKey, includePathKey, saveFormatKey :: String
-directoryKey = "directory"
-
-captionKey = "caption"
-
-dpiKey = "dpi"
-
+directoryKey = "directory"
+captionKey = "caption"
+dpiKey = "dpi"
includePathKey = "include"
-
-saveFormatKey = "format"
+saveFormatKey = "format"
-- | list of all keys related to pandoc-pyplot.
inclusionKeys :: [String]
inclusionKeys = [directoryKey, captionKey, dpiKey, includePathKey, saveFormatKey]
-- | Determine inclusion specifications from Block attributes.
--- Note that the target key is required, but all other parameters are optional
+-- Note that the @".pyplot"@ class is required, but all other parameters are optional
parseFigureSpec :: Block -> IO (Maybe FigureSpec)
parseFigureSpec (CodeBlock (id', cls, attrs) content)
| "pyplot" `elem` cls = Just <$> figureSpec
| otherwise = return Nothing
where
- attrs' = Map.fromList attrs
+ attrs' = Map.fromList attrs
filteredAttrs = filter (\(k, _) -> k `notElem` inclusionKeys) attrs
- dir = makeValid $ Map.findWithDefault "generated" directoryKey attrs'
- format = fromMaybe (PNG) $ saveFormatFromString $ Map.findWithDefault "png" saveFormatKey attrs'
- includePath = Map.lookup includePathKey attrs'
+ dir = makeValid $ Map.findWithDefault "generated" directoryKey attrs'
+ format = fromMaybe (PNG) $ saveFormatFromString $ Map.findWithDefault "png" saveFormatKey attrs'
+ includePath = Map.lookup includePathKey attrs'
+
figureSpec :: IO FigureSpec
figureSpec = do
- includeScript <- fromMaybe (return "") $ T.readFile <$> includePath
- let header = "# Generated by pandoc-pyplot " <> ((T.pack . showVersion) version)
- fullScript = mconcat $ intersperse "\n" [header, includeScript, T.pack content]
- caption' = Map.findWithDefault mempty captionKey attrs'
- dpi' = read $ Map.findWithDefault "80" dpiKey attrs'
+ includeScript <- fromMaybe (return mempty) $ T.readFile <$> includePath
+ let header = "# Generated by pandoc-pyplot " <> ((T.pack . showVersion) version)
+ fullScript = mconcat $ intersperse "\n" [header, includeScript, T.pack content]
+ caption' = Map.findWithDefault mempty captionKey attrs'
+ dpi' = read $ Map.findWithDefault "80" dpiKey attrs'
blockAttrs' = (id', filter (/= "pyplot") cls, filteredAttrs)
return $ FigureSpec caption' fullScript format dir dpi' blockAttrs'
-parseFigureSpec _ = return Nothing
--- | Check figure specifications for common mistakes
-validateSpec :: FigureSpec -> Maybe PandocPyplotError
-validateSpec spec
- | not (isValid path) = Just $ InvalidTargetError path
- | hasBlockingShowCall rendered = Just $ BlockingCallError
- | otherwise = Nothing
- where
- path = figurePath spec
- rendered = script spec
+parseFigureSpec _ = return Nothing
-- | Run the Python script. In case the file already exists, we can safely assume
-- there is no need to re-run it.
@@ -175,59 +158,28 @@ runScriptIfNecessary :: FigureSpec -> IO ScriptResult
runScriptIfNecessary spec = do
createDirectoryIfMissing True . takeDirectory $ figurePath spec
fileAlreadyExists <- doesFileExist $ figurePath spec
- if fileAlreadyExists
- then return ScriptSuccess
- else do
- result <- runTempPythonScript $ addPlotCapture spec
- case result of
- ScriptFailure code -> return $ ScriptFailure code
- ScriptSuccess
- -- Save the original script into a separate file
- -- so it can be inspected
- -- Note : using a .txt file allows to view source directly
- -- in the browser, in the case of HTML output
- -> do
- let sourcePath = replaceExtension (figurePath spec) ".txt"
- T.writeFile sourcePath $ script spec
- return ScriptSuccess
+ result <- if fileAlreadyExists
+ then return ScriptSuccess
+ else runTempPythonScript $ addPlotCapture spec
+ case result of
+ ScriptFailure code -> return $ ScriptFailure code
+ ScriptSuccess -> T.writeFile (sourceCodePath spec) (script spec) >> return ScriptSuccess
-- | Main routine to include Matplotlib plots.
-- Code blocks containing the attributes @.pyplot@ are considered
-- Python plotting scripts. All other possible blocks are ignored.
makePlot' :: Block -> IO (Either PandocPyplotError Block)
makePlot' block = do
- parsed <- parseFigureSpec block
+ parsed <- parseFigureSpec block
case parsed of
- Nothing -> return $ Right block
+ Nothing -> return $ Right block
Just spec ->
- case validateSpec spec of
- Just err -> return $ Left err
- Nothing -> do
- result <- runScriptIfNecessary spec
- case result of
- ScriptFailure code -> return $ Left $ ScriptError code
- ScriptSuccess -> do
- let relevantAttrs = blockAttrs spec
- sourcePath = replaceExtension (figurePath spec) ".txt"
- hiresPath = hiresFigurePath spec
- srcTarget = Link nullAttr [Str "Source code"] (sourcePath, "")
- hiresTarget = Link nullAttr [Str "high res."] (hiresPath, "")
- -- TODO: use pandoc-types Builder module
- caption' =
- [ Str $ caption spec
- , Space
- , Str "("
- , srcTarget
- , Str ","
- , Space
- , hiresTarget
- , Str ")"
- ]
- -- To render images as figures with captions, the target title
- -- must be "fig:"
- -- Janky? yes
- image = Image relevantAttrs caption' (figurePath spec, "fig:")
- return $ Right $ Para $ [image]
+ if hasBlockingShowCall (script spec)
+ then return $ Left BlockingCallError
+ else handleResult spec <$> runScriptIfNecessary spec
+ where
+ handleResult _ (ScriptFailure code) = Left $ ScriptError code
+ handleResult spec ScriptSuccess = Right $ toImage spec
-- | Highest-level function that can be walked over a Pandoc tree.
-- All code blocks that have the '.pyplot' parameter will be considered