{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeSynonymInstances #-}

-- |
-- Module      : $header$
-- Copyright   : (c) Laurent P René de Cotret, 2019 - present
-- License     : GNU GPL, version 2 or above
-- Maintainer  : laurent.decotret@outlook.com
-- Stability   : internal
-- Portability : portable
--
-- This module defines the @PlotM@ monad and related capabilities.
module Text.Pandoc.Filter.Plot.Monad
  ( Configuration (..),
    PlotM,
    RuntimeEnv (..),
    PlotState (..),
    runPlotM,

    -- * Concurrent execution
    mapConcurrentlyN,

    -- * Running external commands
    runCommand,
    withPrependedPath,

    -- * Halting pandoc-plot
    throwStrictError,

    -- * Getting file hashes
    fileHash,

    -- * Getting executables
    executable,

    -- * Logging
    Verbosity (..),
    LogSink (..),
    debug,
    err,
    warning,
    info,

    -- * Lifting and other monadic operations
    liftIO,
    ask,
    asks,
    asksConfig,

    -- * Base types
    module Text.Pandoc.Filter.Plot.Monad.Types,
  )
where

import Control.Concurrent.Async.Lifted (mapConcurrently)
import Control.Concurrent.MVar (MVar, newMVar, putMVar, takeMVar)
import Control.Concurrent.QSemN
  ( QSemN,
    newQSemN,
    signalQSemN,
    waitQSemN,
  )
import Control.Exception.Lifted (bracket, bracket_)
import Control.Monad.Reader
  ( MonadIO (liftIO),
    MonadReader (ask),
    ReaderT (runReaderT),
    asks,
  )
import Control.Monad.State.Strict
  ( MonadState (get, put),
    StateT,
    evalStateT,
  )
import Data.ByteString.Lazy (toStrict)
import Data.Functor ((<&>))
import Data.Hashable (hash)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Text (Text, pack, unpack)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (lenientDecode)
import System.Directory
  ( doesFileExist,
    findExecutable,
    getCurrentDirectory,
    getModificationTime,
  )
import System.Environment (getEnv, setEnv)
import System.Exit (ExitCode (..), exitFailure)
import System.Process.Typed
  ( byteStringInput,
    byteStringOutput,
    nullStream,
    readProcessStderr,
    setStderr,
    setStdin,
    setStdout,
    setWorkingDir,
    shell,
  )
import Text.Pandoc.Definition (Format (..))
import Text.Pandoc.Filter.Plot.Monad.Logging
  ( LogSink (..),
    Logger,
    MonadLogger (..),
    Verbosity (..),
    debug,
    err,
    info,
    strict,
    terminateLogging,
    warning,
    withLogger,
  )
import Text.Pandoc.Filter.Plot.Monad.Types
import Prelude hiding (fst, log, snd)

-- | pandoc-plot monad
type PlotM = StateT PlotState (ReaderT RuntimeEnv IO)

instance MonadLogger PlotM where
  askLogger :: PlotM Logger
askLogger = (RuntimeEnv -> Logger) -> PlotM Logger
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks RuntimeEnv -> Logger
envLogger

data RuntimeEnv = RuntimeEnv
  { RuntimeEnv -> Maybe Format
envFormat :: Maybe Format, -- pandoc output format
    RuntimeEnv -> Configuration
envConfig :: Configuration,
    RuntimeEnv -> Logger
envLogger :: Logger,
    RuntimeEnv -> FilePath
envCWD :: FilePath
  }

-- | Get access to configuration within the @PlotM@ monad.
asksConfig :: (Configuration -> a) -> PlotM a
asksConfig :: (Configuration -> a) -> PlotM a
asksConfig Configuration -> a
f = (RuntimeEnv -> a) -> PlotM a
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (Configuration -> a
f (Configuration -> a)
-> (RuntimeEnv -> Configuration) -> RuntimeEnv -> a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RuntimeEnv -> Configuration
envConfig)

-- | Evaluate a @PlotM@ action.
runPlotM :: Maybe Format -> Configuration -> PlotM a -> IO a
runPlotM :: Maybe Format -> Configuration -> PlotM a -> IO a
runPlotM Maybe Format
fmt Configuration
conf PlotM a
v = do
  FilePath
cwd <- IO FilePath
getCurrentDirectory
  PlotState
st <-
    MVar (Map FilePath FileHash)
-> MVar (Map Toolkit (Maybe Renderer)) -> PlotState
PlotState (MVar (Map FilePath FileHash)
 -> MVar (Map Toolkit (Maybe Renderer)) -> PlotState)
-> IO (MVar (Map FilePath FileHash))
-> IO (MVar (Map Toolkit (Maybe Renderer)) -> PlotState)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Map FilePath FileHash -> IO (MVar (Map FilePath FileHash))
forall a. a -> IO (MVar a)
newMVar Map FilePath FileHash
forall a. Monoid a => a
mempty
      IO (MVar (Map Toolkit (Maybe Renderer)) -> PlotState)
-> IO (MVar (Map Toolkit (Maybe Renderer))) -> IO PlotState
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Map Toolkit (Maybe Renderer)
-> IO (MVar (Map Toolkit (Maybe Renderer)))
forall a. a -> IO (MVar a)
newMVar Map Toolkit (Maybe Renderer)
forall a. Monoid a => a
mempty
  let verbosity :: Verbosity
verbosity = Configuration -> Verbosity
logVerbosity Configuration
conf
      sink :: LogSink
sink = Configuration -> LogSink
logSink Configuration
conf
  Verbosity -> LogSink -> (Logger -> IO a) -> IO a
forall a. Verbosity -> LogSink -> (Logger -> IO a) -> IO a
withLogger Verbosity
verbosity LogSink
sink ((Logger -> IO a) -> IO a) -> (Logger -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$
    \Logger
logger -> ReaderT RuntimeEnv IO a -> RuntimeEnv -> IO a
forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT (PlotM a -> PlotState -> ReaderT RuntimeEnv IO a
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT PlotM a
v PlotState
st) (Maybe Format -> Configuration -> Logger -> FilePath -> RuntimeEnv
RuntimeEnv Maybe Format
fmt Configuration
conf Logger
logger FilePath
cwd)

-- | maps a function, performing at most @N@ actions concurrently.
mapConcurrentlyN :: Traversable t => Int -> (a -> PlotM b) -> t a -> PlotM (t b)
mapConcurrentlyN :: Int -> (a -> PlotM b) -> t a -> PlotM (t b)
mapConcurrentlyN Int
n a -> PlotM b
f t a
xs = do
  -- Emulating a pool of processes with locked access
  QSemN
sem <- IO QSemN -> StateT PlotState (ReaderT RuntimeEnv IO) QSemN
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO QSemN -> StateT PlotState (ReaderT RuntimeEnv IO) QSemN)
-> IO QSemN -> StateT PlotState (ReaderT RuntimeEnv IO) QSemN
forall a b. (a -> b) -> a -> b
$ Int -> IO QSemN
newQSemN Int
n
  (a -> PlotM b) -> t a -> PlotM (t b)
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, MonadBaseControl IO m) =>
(a -> m b) -> t a -> m (t b)
mapConcurrently (QSemN -> PlotM b -> PlotM b
forall a. QSemN -> PlotM a -> PlotM a
with QSemN
sem (PlotM b -> PlotM b) -> (a -> PlotM b) -> a -> PlotM b
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> PlotM b
f) t a
xs
  where
    with :: QSemN -> PlotM a -> PlotM a
    with :: QSemN -> PlotM a -> PlotM a
with QSemN
s = StateT PlotState (ReaderT RuntimeEnv IO) ()
-> StateT PlotState (ReaderT RuntimeEnv IO) ()
-> PlotM a
-> PlotM a
forall (m :: * -> *) a b c.
MonadBaseControl IO m =>
m a -> m b -> m c -> m c
bracket_ (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ QSemN -> Int -> IO ()
waitQSemN QSemN
s Int
1) (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ QSemN -> Int -> IO ()
signalQSemN QSemN
s Int
1)

-- | Run a command within the @PlotM@ monad. Stderr stream
-- is read and decoded, while Stdout is ignored.
-- Logging happens at the debug level if the command succeeds, or at
-- the error level if it does not succeed.
runCommand ::
  FilePath -> -- Directory from which to run the command
  Text -> -- Command to run, including executable
  PlotM (ExitCode, Text)
runCommand :: FilePath -> Text -> PlotM (ExitCode, Text)
runCommand FilePath
wordir Text
command = do
  (ExitCode
ec, ByteString
processOutput') <-
    IO (ExitCode, ByteString)
-> StateT PlotState (ReaderT RuntimeEnv IO) (ExitCode, ByteString)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (ExitCode, ByteString)
 -> StateT PlotState (ReaderT RuntimeEnv IO) (ExitCode, ByteString))
-> IO (ExitCode, ByteString)
-> StateT PlotState (ReaderT RuntimeEnv IO) (ExitCode, ByteString)
forall a b. (a -> b) -> a -> b
$
      ProcessConfig () () (STM ByteString) -> IO (ExitCode, ByteString)
forall (m :: * -> *) stdin stdout stderrIgnored.
MonadIO m =>
ProcessConfig stdin stdout stderrIgnored
-> m (ExitCode, ByteString)
readProcessStderr (ProcessConfig () () (STM ByteString) -> IO (ExitCode, ByteString))
-> ProcessConfig () () (STM ByteString)
-> IO (ExitCode, ByteString)
forall a b. (a -> b) -> a -> b
$
        -- For Julia specifically, if the line below is not there (`setStdin (byteStringInput "")`),
        -- the following error is thrown on Windows:
        --    ERROR: error initializing stdin in uv_dup:
        --           Unknown system error 50 (Unknown system error 50 50)
        StreamSpec 'STInput ()
-> ProcessConfig () () (STM ByteString)
-> ProcessConfig () () (STM ByteString)
forall stdin stdin0 stdout stderr.
StreamSpec 'STInput stdin
-> ProcessConfig stdin0 stdout stderr
-> ProcessConfig stdin stdout stderr
setStdin (ByteString -> StreamSpec 'STInput ()
byteStringInput ByteString
"") (ProcessConfig () () (STM ByteString)
 -> ProcessConfig () () (STM ByteString))
-> ProcessConfig () () (STM ByteString)
-> ProcessConfig () () (STM ByteString)
forall a b. (a -> b) -> a -> b
$
          StreamSpec 'STOutput ()
-> ProcessConfig () () (STM ByteString)
-> ProcessConfig () () (STM ByteString)
forall stdout stdin stdout0 stderr.
StreamSpec 'STOutput stdout
-> ProcessConfig stdin stdout0 stderr
-> ProcessConfig stdin stdout stderr
setStdout StreamSpec 'STOutput ()
forall (anyStreamType :: StreamType). StreamSpec anyStreamType ()
nullStream (ProcessConfig () () (STM ByteString)
 -> ProcessConfig () () (STM ByteString))
-> ProcessConfig () () (STM ByteString)
-> ProcessConfig () () (STM ByteString)
forall a b. (a -> b) -> a -> b
$
            StreamSpec 'STOutput (STM ByteString)
-> ProcessConfig () () () -> ProcessConfig () () (STM ByteString)
forall stderr stdin stdout stderr0.
StreamSpec 'STOutput stderr
-> ProcessConfig stdin stdout stderr0
-> ProcessConfig stdin stdout stderr
setStderr StreamSpec 'STOutput (STM ByteString)
byteStringOutput (ProcessConfig () () () -> ProcessConfig () () (STM ByteString))
-> ProcessConfig () () () -> ProcessConfig () () (STM ByteString)
forall a b. (a -> b) -> a -> b
$
              FilePath -> ProcessConfig () () () -> ProcessConfig () () ()
forall stdin stdout stderr.
FilePath
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setWorkingDir FilePath
wordir (ProcessConfig () () () -> ProcessConfig () () ())
-> ProcessConfig () () () -> ProcessConfig () () ()
forall a b. (a -> b) -> a -> b
$
                FilePath -> ProcessConfig () () ()
shell (Text -> FilePath
unpack Text
command)
  let processOutput :: Text
processOutput = OnDecodeError -> ByteString -> Text
decodeUtf8With OnDecodeError
lenientDecode (ByteString -> Text) -> ByteString -> Text
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
toStrict ByteString
processOutput'
      logFunc :: Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
logFunc =
        if ExitCode
ec ExitCode -> ExitCode -> Bool
forall a. Eq a => a -> a -> Bool
== ExitCode
ExitSuccess
          then Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *). (MonadLogger m, MonadIO m) => Text -> m ()
debug
          else Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *). (MonadLogger m, MonadIO m) => Text -> m ()
err
      message :: Text
message =
        [Text] -> Text
T.unlines
          [ Text
"Running command",
            Text
"    " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
command,
            Text
"ended with exit code " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (FilePath -> Text
pack (FilePath -> Text) -> (ExitCode -> FilePath) -> ExitCode -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ExitCode -> FilePath
forall a. Show a => a -> FilePath
show (ExitCode -> Text) -> ExitCode -> Text
forall a b. (a -> b) -> a -> b
$ ExitCode
ec)
          ]
      errorMessage :: Text
errorMessage =
        if Text
processOutput Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
forall a. Monoid a => a
mempty
          then Text
forall a. Monoid a => a
mempty
          else
            [Text] -> Text
T.unlines
              [ Text
"*******",
                Text
processOutput,
                Text
"*******"
              ]

  Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
logFunc (Text -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ Text
message Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
errorMessage
  (ExitCode, Text) -> PlotM (ExitCode, Text)
forall (m :: * -> *) a. Monad m => a -> m a
return (ExitCode
ec, Text
processOutput)

-- | Prepend a directory to the PATH environment variable for the duration
-- of a computation.
--
-- This function is exception-safe; even if an exception happens during the
-- computation, the PATH environment variable will be reverted back to
-- its initial value.
withPrependedPath :: FilePath -> PlotM a -> PlotM a
withPrependedPath :: FilePath -> PlotM a -> PlotM a
withPrependedPath FilePath
dir PlotM a
f = do
  FilePath
pathVar <- IO FilePath -> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FilePath -> StateT PlotState (ReaderT RuntimeEnv IO) FilePath)
-> IO FilePath -> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> IO FilePath
getEnv FilePath
"PATH"
  let pathVarPrepended :: FilePath
pathVarPrepended = [FilePath] -> FilePath
forall a. Monoid a => [a] -> a
mconcat [FilePath
dir, FilePath
";", FilePath
pathVar]
  StateT PlotState (ReaderT RuntimeEnv IO) ()
-> (() -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> (() -> PlotM a)
-> PlotM a
forall (m :: * -> *) a b c.
MonadBaseControl IO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket
    (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> IO ()
setEnv FilePath
"PATH" FilePath
pathVarPrepended)
    (\()
_ -> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> IO ()
setEnv FilePath
"PATH" FilePath
pathVar)
    (PlotM a -> () -> PlotM a
forall a b. a -> b -> a
const PlotM a
f)

-- | Throw an error that halts the execution of pandoc-plot due to a strict-mode.
throwStrictError :: Text -> PlotM ()
throwStrictError :: Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
throwStrictError Text
msg = do
  Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *). (MonadLogger m, MonadIO m) => Text -> m ()
strict Text
msg
  Logger
logger <- PlotM Logger
forall (m :: * -> *). MonadLogger m => m Logger
askLogger
  IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ Logger -> IO ()
terminateLogging Logger
logger IO () -> IO () -> IO ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> IO ()
forall a. IO a
exitFailure

-- Plot state is used for caching.
-- One part consists of a map of filepaths to hashes
-- This allows multiple plots to depend on the same file/directory, and the file hashes
-- will only be calculated once. This is OK because pandoc-plot will not run for long.
-- We note that because figures are rendered possibly in parallel, access to
-- the state must be synchronized; otherwise, each thread might compute its own
-- hashes.
-- The other part is comprised of a map of toolkits to renderers (possibly missing)
-- This means that checking if renderers are available will only be done once.
type FileHash = Word

data PlotState
  = PlotState
      (MVar (Map FilePath FileHash))
      (MVar (Map Toolkit (Maybe Renderer)))

-- | Get a filehash. If the file hash has been computed before,
-- it is reused. Otherwise, the filehash is calculated and stored.
fileHash :: FilePath -> PlotM FileHash
fileHash :: FilePath -> PlotM FileHash
fileHash FilePath
path = do
  PlotState MVar (Map FilePath FileHash)
varHashes MVar (Map Toolkit (Maybe Renderer))
varExes <- StateT PlotState (ReaderT RuntimeEnv IO) PlotState
forall s (m :: * -> *). MonadState s m => m s
get
  Map FilePath FileHash
hashes <- IO (Map FilePath FileHash)
-> StateT PlotState (ReaderT RuntimeEnv IO) (Map FilePath FileHash)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Map FilePath FileHash)
 -> StateT
      PlotState (ReaderT RuntimeEnv IO) (Map FilePath FileHash))
-> IO (Map FilePath FileHash)
-> StateT PlotState (ReaderT RuntimeEnv IO) (Map FilePath FileHash)
forall a b. (a -> b) -> a -> b
$ MVar (Map FilePath FileHash) -> IO (Map FilePath FileHash)
forall a. MVar a -> IO a
takeMVar MVar (Map FilePath FileHash)
varHashes
  (FileHash
fh, Map FilePath FileHash
hashes') <- case FilePath -> Map FilePath FileHash -> Maybe FileHash
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup FilePath
path Map FilePath FileHash
hashes of
    Maybe FileHash
Nothing -> do
      Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *). (MonadLogger m, MonadIO m) => Text -> m ()
debug (Text -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. Monoid a => [a] -> a
mconcat [Text
"Calculating hash of dependency ", FilePath -> Text
pack FilePath
path]
      FileHash
fh <- FilePath -> PlotM FileHash
fileHash' FilePath
path
      let hashes' :: Map FilePath FileHash
hashes' = FilePath
-> FileHash -> Map FilePath FileHash -> Map FilePath FileHash
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert FilePath
path FileHash
fh Map FilePath FileHash
hashes
      (FileHash, Map FilePath FileHash)
-> StateT
     PlotState (ReaderT RuntimeEnv IO) (FileHash, Map FilePath FileHash)
forall (m :: * -> *) a. Monad m => a -> m a
return (FileHash
fh, Map FilePath FileHash
hashes')
    Just FileHash
h -> do
      Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *). (MonadLogger m, MonadIO m) => Text -> m ()
debug (Text -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
forall a. Monoid a => [a] -> a
mconcat [Text
"Hash of dependency ", FilePath -> Text
pack FilePath
path, Text
" already calculated."]
      (FileHash, Map FilePath FileHash)
-> StateT
     PlotState (ReaderT RuntimeEnv IO) (FileHash, Map FilePath FileHash)
forall (m :: * -> *) a. Monad m => a -> m a
return (FileHash
h, Map FilePath FileHash
hashes)
  IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> IO () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ MVar (Map FilePath FileHash) -> Map FilePath FileHash -> IO ()
forall a. MVar a -> a -> IO ()
putMVar MVar (Map FilePath FileHash)
varHashes Map FilePath FileHash
hashes'
  PlotState -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put (PlotState -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> PlotState -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ MVar (Map FilePath FileHash)
-> MVar (Map Toolkit (Maybe Renderer)) -> PlotState
PlotState MVar (Map FilePath FileHash)
varHashes MVar (Map Toolkit (Maybe Renderer))
varExes
  FileHash -> PlotM FileHash
forall (m :: * -> *) a. Monad m => a -> m a
return FileHash
fh
  where
    -- As a proxy for the state of a file dependency, we use the modification time
    -- This is much faster than actual file hashing
    fileHash' :: FilePath -> PlotM FileHash
    fileHash' :: FilePath -> PlotM FileHash
fileHash' FilePath
fp = do
      Bool
fileExists <- IO Bool -> StateT PlotState (ReaderT RuntimeEnv IO) Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> StateT PlotState (ReaderT RuntimeEnv IO) Bool)
-> IO Bool -> StateT PlotState (ReaderT RuntimeEnv IO) Bool
forall a b. (a -> b) -> a -> b
$ FilePath -> IO Bool
doesFileExist FilePath
fp
      if Bool
fileExists
        then IO FileHash -> PlotM FileHash
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FileHash -> PlotM FileHash)
-> (FilePath -> IO FileHash) -> FilePath -> PlotM FileHash
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (UTCTime -> FileHash) -> IO UTCTime -> IO FileHash
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Int -> FileHash
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> FileHash) -> (UTCTime -> Int) -> UTCTime -> FileHash
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> Int
forall a. Hashable a => a -> Int
hash (FilePath -> Int) -> (UTCTime -> FilePath) -> UTCTime -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. UTCTime -> FilePath
forall a. Show a => a -> FilePath
show) (IO UTCTime -> IO FileHash)
-> (FilePath -> IO UTCTime) -> FilePath -> IO FileHash
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> IO UTCTime
getModificationTime (FilePath -> PlotM FileHash) -> FilePath -> PlotM FileHash
forall a b. (a -> b) -> a -> b
$ FilePath
fp
        else Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *). (MonadLogger m, MonadIO m) => Text -> m ()
err ([Text] -> Text
forall a. Monoid a => [a] -> a
mconcat [Text
"Dependency ", FilePath -> Text
pack FilePath
fp, Text
" does not exist."]) StateT PlotState (ReaderT RuntimeEnv IO) ()
-> PlotM FileHash -> PlotM FileHash
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> FileHash -> PlotM FileHash
forall (m :: * -> *) a. Monad m => a -> m a
return FileHash
0

-- | Find an executable.
executable :: Toolkit -> PlotM (Maybe Executable)
executable :: Toolkit -> PlotM (Maybe Executable)
executable Toolkit
tk =
  Toolkit -> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
exeSelector Toolkit
tk
    StateT PlotState (ReaderT RuntimeEnv IO) FilePath
-> (FilePath -> PlotM (Maybe Executable))
-> PlotM (Maybe Executable)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \FilePath
name ->
      IO (Maybe Executable) -> PlotM (Maybe Executable)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe Executable) -> PlotM (Maybe Executable))
-> IO (Maybe Executable) -> PlotM (Maybe Executable)
forall a b. (a -> b) -> a -> b
$
        FilePath -> IO (Maybe FilePath)
findExecutable FilePath
name IO (Maybe FilePath)
-> (Maybe FilePath -> Maybe Executable) -> IO (Maybe Executable)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> (FilePath -> Executable) -> Maybe FilePath -> Maybe Executable
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap FilePath -> Executable
exeFromPath
  where
    exeSelector :: Toolkit -> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
exeSelector Toolkit
Matplotlib = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
matplotlibExe
    exeSelector Toolkit
PlotlyPython = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
plotlyPythonExe
    exeSelector Toolkit
PlotlyR = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
plotlyRExe
    exeSelector Toolkit
Matlab = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
matlabExe
    exeSelector Toolkit
Mathematica = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
mathematicaExe
    exeSelector Toolkit
Octave = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
octaveExe
    exeSelector Toolkit
GGPlot2 = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
ggplot2Exe
    exeSelector Toolkit
GNUPlot = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
gnuplotExe
    exeSelector Toolkit
Graphviz = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
graphvizExe
    exeSelector Toolkit
Bokeh = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
bokehExe
    exeSelector Toolkit
Plotsjl = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
plotsjlExe
    exeSelector Toolkit
PlantUML = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
plantumlExe
    exeSelector Toolkit
SageMath = (Configuration -> FilePath)
-> StateT PlotState (ReaderT RuntimeEnv IO) FilePath
forall a. (Configuration -> a) -> PlotM a
asksConfig Configuration -> FilePath
sagemathExe

-- | The @Configuration@ type holds the default values to use
-- when running pandoc-plot. These values can be overridden in code blocks.
--
-- You can create an instance of the @Configuration@ type from file using the @configuration@ function.
--
-- You can store the path to a configuration file in metadata under the key @plot-configuration@. For example, in Markdown:
--
-- @
--     ---
--     title: My document
--     author: John Doe
--     plot-configuration: path\to\file.yml
--     ---
-- @
--
-- The same can be specified via the command line using Pandoc's @-M@ flag:
--
-- > pandoc --filter pandoc-plot -M plot-configuration="path/to/file.yml" ...
--
-- In this case, use @configurationPathMeta@ to extact the path from @Pandoc@ documents.
data Configuration = Configuration
  { -- | The default directory where figures will be saved.
    Configuration -> FilePath
defaultDirectory :: !FilePath,
    -- | The default behavior of whether or not to include links to source code and high-res
    Configuration -> Bool
defaultWithSource :: !Bool,
    -- | The default dots-per-inch value for generated figures. Renderers might ignore this.
    Configuration -> Int
defaultDPI :: !Int,
    -- | The default save format of generated figures.
    Configuration -> SaveFormat
defaultSaveFormat :: !SaveFormat,
    -- | List of files/directories on which all figures depend.
    Configuration -> [FilePath]
defaultDependencies :: ![FilePath],
    -- | Caption format, in the same notation as Pandoc format, e.g. "markdown+tex_math_dollars"
    Configuration -> Format
captionFormat :: !Format,
    -- | The text label to which the source code is linked. Change this if you are writing non-english documents.
    Configuration -> Text
sourceCodeLabel :: !Text,
    -- | Whether to halt pandoc-plot when an error is encountered or not.
    Configuration -> Bool
strictMode :: !Bool,
    -- | Level of logging verbosity.
    Configuration -> Verbosity
logVerbosity :: !Verbosity,
    -- | Method of logging, i.e. printing to stderr or file.
    Configuration -> LogSink
logSink :: !LogSink,
    -- | The default preamble script for the matplotlib toolkit.
    Configuration -> Text
matplotlibPreamble :: !Script,
    -- | The default preamble script for the Plotly/Python toolkit.
    Configuration -> Text
plotlyPythonPreamble :: !Script,
    -- | The default preamble script for the Plotly/R toolkit.
    Configuration -> Text
plotlyRPreamble :: !Script,
    -- | The default preamble script for the MATLAB toolkit.
    Configuration -> Text
matlabPreamble :: !Script,
    -- | The default preamble script for the Mathematica toolkit.
    Configuration -> Text
mathematicaPreamble :: !Script,
    -- | The default preamble script for the GNU Octave toolkit.
    Configuration -> Text
octavePreamble :: !Script,
    -- | The default preamble script for the GGPlot2 toolkit.
    Configuration -> Text
ggplot2Preamble :: !Script,
    -- | The default preamble script for the gnuplot toolkit.
    Configuration -> Text
gnuplotPreamble :: !Script,
    -- | The default preamble script for the Graphviz toolkit.
    Configuration -> Text
graphvizPreamble :: !Script,
    -- | The default preamble script for the Python/Bokeh toolkit.
    Configuration -> Text
bokehPreamble :: !Script,
    -- | The default preamble script for the Julia/Plots.jl toolkit.
    Configuration -> Text
plotsjlPreamble :: !Script,
    -- | The default preamble script for the PlantUML toolkit.
    Configuration -> Text
plantumlPreamble :: !Script,
    -- | The default preamble script for the SageMath toolkit.
    Configuration -> Text
sagemathPreamble :: !Script,
    -- | The executable to use to generate figures using the matplotlib toolkit.
    Configuration -> FilePath
matplotlibExe :: !FilePath,
    -- | The executable to use to generate figures using the MATLAB toolkit.
    Configuration -> FilePath
matlabExe :: !FilePath,
    -- | The executable to use to generate figures using the Plotly/Python toolkit.
    Configuration -> FilePath
plotlyPythonExe :: !FilePath,
    -- | The executable to use to generate figures using the Plotly/R toolkit.
    Configuration -> FilePath
plotlyRExe :: !FilePath,
    -- | The executable to use to generate figures using the Mathematica toolkit.
    Configuration -> FilePath
mathematicaExe :: !FilePath,
    -- | The executable to use to generate figures using the GNU Octave toolkit.
    Configuration -> FilePath
octaveExe :: !FilePath,
    -- | The executable to use to generate figures using the GGPlot2 toolkit.
    Configuration -> FilePath
ggplot2Exe :: !FilePath,
    -- | The executable to use to generate figures using the gnuplot toolkit.
    Configuration -> FilePath
gnuplotExe :: !FilePath,
    -- | The executable to use to generate figures using the Graphviz toolkit.
    Configuration -> FilePath
graphvizExe :: !FilePath,
    -- | The executable to use to generate figures using the Python/Bokeh toolkit.
    Configuration -> FilePath
bokehExe :: !FilePath,
    -- | The executable to use to generate figures using the Julia/Plots.jl toolkit.
    Configuration -> FilePath
plotsjlExe :: !FilePath,
    -- | The executable to use to generate figures using the PlantUML toolkit.
    Configuration -> FilePath
plantumlExe :: !FilePath,
    -- | The executable to use to generate figures using SageMath.
    Configuration -> FilePath
sagemathExe :: !FilePath,
    -- | Command-line arguments to pass to the Python interpreter for the Matplotlib toolkit
    Configuration -> Text
matplotlibCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the MATLAB toolkit.
    Configuration -> Text
matlabCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the Plotly/Python toolkit.
    Configuration -> Text
plotlyPythonCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the Plotly/R toolkit.
    Configuration -> Text
plotlyRCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the Mathematica toolkit.
    Configuration -> Text
mathematicaCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the GNU Octave toolkit.
    Configuration -> Text
octaveCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the GGPlot2 toolkit.
    Configuration -> Text
ggplot2CmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the gnuplot toolkit.
    Configuration -> Text
gnuplotCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the Graphviz toolkit.
    Configuration -> Text
graphvizCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the Python/Bokeh toolkit.
    Configuration -> Text
bokehCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the Julia/Plots.jl toolkit.
    Configuration -> Text
plotsjlCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the plantUML toolkit.
    Configuration -> Text
plantumlCmdArgs :: !Text,
    -- | Command-line arguments to pass to the interpreter for the SageMath toolkit.
    Configuration -> Text
sagemathCmdArgs :: !Text,
    -- | Whether or not to make Matplotlib figures tight by default.
    Configuration -> Bool
matplotlibTightBBox :: !Bool,
    -- | Whether or not to make Matplotlib figures transparent by default.
    Configuration -> Bool
matplotlibTransparent :: !Bool
  }
  deriving (Configuration -> Configuration -> Bool
(Configuration -> Configuration -> Bool)
-> (Configuration -> Configuration -> Bool) -> Eq Configuration
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Configuration -> Configuration -> Bool
$c/= :: Configuration -> Configuration -> Bool
== :: Configuration -> Configuration -> Bool
$c== :: Configuration -> Configuration -> Bool
Eq, Int -> Configuration -> ShowS
[Configuration] -> ShowS
Configuration -> FilePath
(Int -> Configuration -> ShowS)
-> (Configuration -> FilePath)
-> ([Configuration] -> ShowS)
-> Show Configuration
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [Configuration] -> ShowS
$cshowList :: [Configuration] -> ShowS
show :: Configuration -> FilePath
$cshow :: Configuration -> FilePath
showsPrec :: Int -> Configuration -> ShowS
$cshowsPrec :: Int -> Configuration -> ShowS
Show)