packages feed

pandoc-pyplot-1.0.0.1: src/Text/Pandoc/Filter/Scripting.hs

{-# LANGUAGE Unsafe #-}
{-|
Module      : Text.Pandoc.Filter.Scripting
Copyright   : (c) Laurent P René de Cotret, 2018
License     : MIT
Maintainer  : laurent.decotret@outlook.com
Stability   : internal
Portability : portable

This module defines types and functions that help
with running Python scripts.
-}

module Text.Pandoc.Filter.Scripting (
      runTempPythonScript
    , addPlotCapture
    , hasBlockingShowCall
    , PythonScript
    , ScriptResult(..)
) where

import System.Directory     (getCurrentDirectory)
import System.Exit          (ExitCode(..))
import System.FilePath      ((</>), isAbsolute)
import System.IO.Temp       (getCanonicalTemporaryDirectory)
import System.Process.Typed (runProcess, shell)

import Data.Monoid          (Any(..))

-- | String representation of a Python script
type PythonScript = String

-- | Possible result of running a Python script
data ScriptResult = ScriptSuccess 
                  | ScriptFailure Int

-- | Take a python script in string form, write it in a temporary directory,
-- then execute it. 
runTempPythonScript :: PythonScript    -- ^ Content of the script
                    -> IO ScriptResult -- ^ Result with exit code.
runTempPythonScript script = do
            -- Write script to temporary directory
            scriptPath <- (</> "pandoc-pyplot.py") <$>  getCanonicalTemporaryDirectory
            writeFile scriptPath script
            -- Execute script
            ec <- runProcess $ shell $ "python " <> (show scriptPath)
            case ec of
                ExitSuccess -> return ScriptSuccess
                ExitFailure code -> return $ ScriptFailure code

-- | 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"
                     ]

-- | Detect the presence of a blocking show call, for example "plt.show()"
hasBlockingShowCall :: PythonScript -> Bool
hasBlockingShowCall script = anyOf
        [ "plt.show()" `elem` scriptLines
        , "matplotlib.pyplot.show()" `elem` scriptLines
        ]
    where
        scriptLines = lines script
        anyOf xs = getAny $ mconcat $ Any <$> xs