summaryrefslogtreecommitdiff
path: root/src/Text
diff options
context:
space:
mode:
authorLaurentRDC <>2019-05-29 19:39:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2019-05-29 19:39:00 (GMT)
commitea33899f29e83431ba6ff02c9c2acf172bc75384 (patch)
tree4c5b2443a704c59a5d7082a7eb5fd1f6ceaae4a9 /src/Text
parentd0b25ab26c2ecc6ab5e94a4fbda855615cdd60fa (diff)
version 2.1.2.02.1.2.0
Diffstat (limited to 'src/Text')
-rw-r--r--src/Text/Pandoc/Filter/Pyplot.hs45
-rw-r--r--src/Text/Pandoc/Filter/Pyplot/Configuration.hs3
-rw-r--r--src/Text/Pandoc/Filter/Pyplot/Scripting.hs86
-rw-r--r--src/Text/Pandoc/Filter/Pyplot/Types.hs54
4 files changed, 127 insertions, 61 deletions
diff --git a/src/Text/Pandoc/Filter/Pyplot.hs b/src/Text/Pandoc/Filter/Pyplot.hs
index 83e2221..13b22c0 100644
--- a/src/Text/Pandoc/Filter/Pyplot.hs
+++ b/src/Text/Pandoc/Filter/Pyplot.hs
@@ -126,31 +126,22 @@ import Data.Version (showVersion)
import Paths_pandoc_pyplot (version)
-import System.Directory (createDirectoryIfMissing,
- doesFileExist)
-import System.FilePath (makeValid, takeDirectory)
+import System.FilePath (makeValid)
import Text.Pandoc.Definition
import Text.Pandoc.Walk (walkM)
import Text.Pandoc.Filter.Pyplot.Internal
-
--- | Possible errors returned by the filter
-data PandocPyplotError
- = ScriptError Int -- ^ Running Python script has yielded an error
- | BlockingCallError -- ^ Python script contains a block call to 'show()'
- deriving (Eq)
-
-instance Show PandocPyplotError where
- 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()'"
+-- | Code block class that will trigger the filter
+filterClass :: String
+filterClass = "pyplot"
-- | Determine inclusion specifications from Block attributes.
-- Note that the @".pyplot"@ class is required, but all other parameters are optional
parseFigureSpec :: Configuration -> Block -> IO (Maybe FigureSpec)
parseFigureSpec config (CodeBlock (id', cls, attrs) content)
- | "pyplot" `elem` cls = Just <$> figureSpec
+ | filterClass `elem` cls = Just <$> figureSpec
| otherwise = return Nothing
where
attrs' = Map.fromList attrs
@@ -166,24 +157,11 @@ parseFigureSpec config (CodeBlock (id', cls, attrs) content)
format = fromMaybe (defaultSaveFormat config) $ join $ saveFormatFromString <$> Map.lookup saveFormatKey attrs'
dir = makeValid $ Map.findWithDefault (defaultDirectory config) directoryKey attrs'
dpi' = fromMaybe (defaultDPI config) $ read <$> Map.lookup dpiKey attrs'
- blockAttrs' = (id', filter (/= "pyplot") cls, filteredAttrs)
+ blockAttrs' = (id', filter (/= filterClass) cls, filteredAttrs)
return $ FigureSpec caption' fullScript format dir dpi' blockAttrs'
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.
-runScriptIfNecessary :: Configuration -> FigureSpec -> IO ScriptResult
-runScriptIfNecessary config spec = do
- createDirectoryIfMissing True . takeDirectory $ figurePath spec
- fileAlreadyExists <- doesFileExist $ figurePath spec
- result <- if fileAlreadyExists
- then return ScriptSuccess
- else runTempPythonScript (interpreter config) (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.
@@ -192,13 +170,12 @@ makePlot' config block = do
parsed <- parseFigureSpec config block
case parsed of
Nothing -> return $ Right block
- Just spec ->
- if hasBlockingShowCall (script spec)
- then return $ Left BlockingCallError
- else handleResult spec <$> runScriptIfNecessary config spec
+ Just spec -> handleResult spec <$> runScriptIfNecessary config spec
+
where
- handleResult _ (ScriptFailure code) = Left $ ScriptError code
- handleResult spec ScriptSuccess = Right $ toImage spec
+ handleResult _ (ScriptChecksFailed msg) = Left $ ScriptChecksFailedError msg
+ 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
diff --git a/src/Text/Pandoc/Filter/Pyplot/Configuration.hs b/src/Text/Pandoc/Filter/Pyplot/Configuration.hs
index 8bfa877..c8766ef 100644
--- a/src/Text/Pandoc/Filter/Pyplot/Configuration.hs
+++ b/src/Text/Pandoc/Filter/Pyplot/Configuration.hs
@@ -63,6 +63,7 @@ data ConfigPrecursor
, defaultSaveFormat_ :: String
, defaultDPI_ :: Int
, interpreter_ :: String
+ , flags_ :: [String]
}
instance FromJSON ConfigPrecursor where
@@ -72,6 +73,7 @@ instance FromJSON ConfigPrecursor where
<*> v .:? (T.pack saveFormatKey) .!= (extension $ defaultSaveFormat def)
<*> v .:? (T.pack dpiKey) .!= (defaultDPI def)
<*> v .:? "interpreter" .!= (interpreter def)
+ <*> v .:? "flags" .!= (flags def)
parseJSON _ = fail "Could not parse the configuration"
@@ -84,6 +86,7 @@ renderConfiguration prec = do
, defaultSaveFormat = saveFormat'
, defaultDPI = defaultDPI_ prec
, interpreter = interpreter_ prec
+ , flags = flags_ prec
}
diff --git a/src/Text/Pandoc/Filter/Pyplot/Scripting.hs b/src/Text/Pandoc/Filter/Pyplot/Scripting.hs
index d867363..0ed4642 100644
--- a/src/Text/Pandoc/Filter/Pyplot/Scripting.hs
+++ b/src/Text/Pandoc/Filter/Pyplot/Scripting.hs
@@ -13,47 +13,83 @@ with running Python scripts.
-}
module Text.Pandoc.Filter.Pyplot.Scripting
( runTempPythonScript
- , hasBlockingShowCall
+ , runScriptIfNecessary
) where
import Data.Hashable (hash)
-import Data.Monoid (Any(..))
+import Data.List (intersperse)
+import Data.Monoid (Any(..), (<>))
import qualified Data.Text as T
import qualified Data.Text.IO as T
+import System.Directory (createDirectoryIfMissing,
+ doesFileExist)
import System.Exit (ExitCode (..))
-import System.FilePath ((</>))
+import System.FilePath ((</>), takeDirectory)
import System.IO.Temp (getCanonicalTemporaryDirectory)
import System.Process.Typed (runProcess, shell)
import Text.Pandoc.Filter.Pyplot.Types
+import Text.Pandoc.Filter.Pyplot.FigureSpec
+
+-- | Detect the presence of a blocking show call, for example "plt.show()"
+checkBlockingShowCall :: PythonScript -> CheckResult
+checkBlockingShowCall script' =
+ if hasShowCall
+ then CheckFailed "The script has a blocking call to `matplotlib.pyplot.show`. "
+ else CheckPassed
+ where
+ scriptLines = T.lines script'
+ hasShowCall = getAny $ mconcat $ Any <$>
+ [ "plt.show()" `elem` scriptLines
+ , "pyplot.show()" `elem` scriptLines
+ , "matplotlib.pyplot.show()" `elem` scriptLines
+ ]
+
+-- | List of all script checks
+-- This might be overkill right now but extension to other languages will be easier
+scriptChecks :: [PythonScript -> CheckResult]
+scriptChecks = [checkBlockingShowCall]
-- | Take a python script in string form, write it in a temporary directory,
-- then execute it.
runTempPythonScript :: String -- ^ Interpreter (e.g. "python" or "python35")
+ -> [String] -- ^ Command-line flags
-> PythonScript -- ^ Content of the script
- -> IO ScriptResult -- ^ Result with exit code.
-runTempPythonScript interpreter' script' = do
- -- We involve the script hash as a temporary filename
- -- so that there is never any collision
- scriptPath <- (</> hashedPath) <$> getCanonicalTemporaryDirectory
- T.writeFile scriptPath script'
-
- ec <- runProcess $ shell $ mconcat [interpreter', " ", show scriptPath]
- case ec of
- ExitSuccess -> return ScriptSuccess
- ExitFailure code -> return $ ScriptFailure code
+ -> IO ScriptResult -- ^ Result.
+runTempPythonScript interpreter' flags' script' = case checkResult of
+ CheckFailed msg -> return $ ScriptChecksFailed msg
+ CheckPassed -> do
+ -- We involve the script hash as a temporary filename
+ -- so that there is never any collision
+ scriptPath <- (</> hashedPath) <$> getCanonicalTemporaryDirectory
+ T.writeFile scriptPath script'
+
+ let command = mconcat . intersperse " " $ [interpreter'] <> flags' <> [show scriptPath]
+
+ ec <- runProcess . shell $ command
+ case ec of
+ ExitSuccess -> return ScriptSuccess
+ ExitFailure code -> return $ ScriptFailure code
where
+ checkResult = mconcat $ scriptChecks <*> [script']
hashedPath = show . hash $ script'
--- | Detect the presence of a blocking show call, for example "plt.show()"
-hasBlockingShowCall :: PythonScript -> Bool
-hasBlockingShowCall script' =
- anyOf
- [ "plt.show()" `elem` scriptLines
- , "pyplot.show()" `elem` scriptLines
- , "matplotlib.pyplot.show()" `elem` scriptLines
- ]
- where
- scriptLines = T.lines script'
- anyOf xs = getAny $ mconcat $ Any <$> xs \ No newline at end of file
+-- | Run the Python script. In case the file already exists, we can safely assume
+-- there is no need to re-run it.
+runScriptIfNecessary :: Configuration -> FigureSpec -> IO ScriptResult
+runScriptIfNecessary config spec = do
+ createDirectoryIfMissing True . takeDirectory $ figurePath spec
+
+ fileAlreadyExists <- doesFileExist $ figurePath spec
+ result <- if fileAlreadyExists
+ then return ScriptSuccess
+ else runTempPythonScript (interpreter config) (flags config) scriptWithCapture
+
+ case result of
+ ScriptSuccess -> T.writeFile (sourceCodePath spec) (script spec) >> return ScriptSuccess
+ ScriptFailure code -> return $ ScriptFailure code
+ ScriptChecksFailed msg -> return $ ScriptChecksFailed msg
+
+ where
+ scriptWithCapture = addPlotCapture spec \ No newline at end of file
diff --git a/src/Text/Pandoc/Filter/Pyplot/Types.hs b/src/Text/Pandoc/Filter/Pyplot/Types.hs
index 9021b78..faf6616 100644
--- a/src/Text/Pandoc/Filter/Pyplot/Types.hs
+++ b/src/Text/Pandoc/Filter/Pyplot/Types.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
{-|
Module : Text.Pandoc.Filter.Pyplot.Types
Copyright : (c) Laurent P René de Cotret, 2019
@@ -14,18 +15,53 @@ module Text.Pandoc.Filter.Pyplot.Types where
import Data.Char (toLower)
import Data.Default.Class (Default, def)
import Data.Hashable (Hashable, hashWithSalt)
+import Data.Semigroup as Sem
import Data.Text (Text)
import Text.Pandoc.Definition (Attr)
+
-- | String representation of a Python script
type PythonScript = Text
+
-- | Possible result of running a Python script
data ScriptResult
= ScriptSuccess
+ | ScriptChecksFailed String
| ScriptFailure Int
+
+-- | Result of checking scripts for problems
+data CheckResult
+ = CheckPassed
+ | CheckFailed String
+ deriving (Eq)
+
+instance Sem.Semigroup CheckResult where
+ (<>) CheckPassed a = a
+ (<>) a CheckPassed = a
+ (<>) (CheckFailed msg1) (CheckFailed msg2) = CheckFailed (msg1 <> msg2)
+
+instance Monoid CheckResult where
+ mempty = CheckPassed
+
+#if !(MIN_VERSION_base(4,11,0))
+ mappend = (<>)
+#endif
+
+
+-- | Possible errors returned by the filter
+data PandocPyplotError
+ = ScriptError Int -- ^ Running Python script has yielded an error
+ | ScriptChecksFailedError String -- ^ Python script did not pass all checks
+ deriving (Eq)
+
+instance Show PandocPyplotError where
+ show (ScriptError exitcode) = "Script error: plot could not be generated. Exit code " <> (show exitcode)
+ show (ScriptChecksFailedError msg) = "Script did not pass all checks: " <> msg
+
+
-- | Generated figure file format supported by pandoc-pyplot.
data SaveFormat
= PNG
@@ -62,6 +98,17 @@ saveFormatFromString s
extension :: SaveFormat -> String
extension fmt = mconcat [".", fmap toLower . show $ fmt]
+-- | Default interpreter should be Python 3, which has a different
+-- name on Windows ("python") vs Unix ("python3")
+--
+-- @since 2.1.2.0
+defaultPlatformInterpreter :: String
+#if defined(mingw32_HOST_OS)
+defaultPlatformInterpreter = "python"
+#else
+defaultPlatformInterpreter = "python3"
+#endif
+
-- | Configuration of pandoc-pyplot, describing the default behavior
-- of the filter.
--
@@ -76,6 +123,7 @@ data Configuration
, defaultSaveFormat :: SaveFormat -- ^ The default save format of generated figures.
, defaultDPI :: Int -- ^ The default dots-per-inch value for generated figures.
, interpreter :: String -- ^ The name of the interpreter to use to render figures.
+ , flags :: [String] -- ^ Command-line flags to be passed to the Python interpreger, e.g. ["-O", "-Wignore"]
}
deriving (Eq, Show)
@@ -85,9 +133,11 @@ instance Default Configuration where
, defaultIncludeScript = mempty
, defaultSaveFormat = PNG
, defaultDPI = 80
- , interpreter = "python"
+ , interpreter = defaultPlatformInterpreter
+ , flags = mempty
}
+
-- | Datatype containing all parameters required to run pandoc-pyplot
data FigureSpec = FigureSpec
{ caption :: String -- ^ Figure caption.
@@ -105,4 +155,4 @@ instance Hashable FigureSpec where
, fromEnum . saveFormat $ spec
, directory spec, dpi spec
, blockAttrs spec
- ) \ No newline at end of file
+ )