{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}

-- |

-- Module      : $header$

-- Copyright   : (c) Laurent P René de Cotret, 2019 - 2021

-- License     : GNU GPL, version 2 or above

-- Maintainer  : laurent.decotret@outlook.com

-- Stability   : internal

-- Portability : portable

--

-- Scripting

module Text.Pandoc.Filter.Plot.Scripting
  ( ScriptResult (..),
    runTempScript,
    runScriptIfNecessary,
    figurePath,
    sourceCodePath,
  )
where

import Control.Monad.Reader
import Data.Default (def)
import Data.Functor.Identity (Identity (..))
import Data.Hashable (hash)
import Data.Text (Text, pack, unpack)
import qualified Data.Text.IO as T
import Paths_pandoc_plot (version)
import System.Directory
  ( createDirectoryIfMissing,
    doesFileExist,
    getTemporaryDirectory,
  )
import System.Exit (ExitCode (..))
import System.FilePath
  ( addExtension,
    normalise,
    replaceExtension,
    takeDirectory,
    (</>),
  )
import Text.Pandoc.Class (runPure)
import Text.Pandoc.Definition
import Text.Pandoc.Filter.Plot.Monad
import Text.Pandoc.Filter.Plot.Scripting.Template
import Text.Pandoc.Options (WriterOptions (..))
import Text.Pandoc.SelfContained (makeSelfContained)
import Text.Pandoc.Templates
import Text.Pandoc.Writers (writeHtml5String)

-- Run script as described by the spec, only if necessary

runScriptIfNecessary :: FigureSpec -> PlotM ScriptResult
runScriptIfNecessary :: FigureSpec -> PlotM ScriptResult
runScriptIfNecessary FigureSpec
spec = do
  FilePath
target <- FigureSpec -> PlotM FilePath
figurePath FigureSpec
spec
  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
$ Bool -> FilePath -> IO ()
createDirectoryIfMissing Bool
True (FilePath -> IO ()) -> (FilePath -> FilePath) -> FilePath -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath
takeDirectory (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
target

  Bool
fileAlreadyExists <- 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)
-> (FilePath -> IO Bool)
-> FilePath
-> StateT PlotState (ReaderT RuntimeEnv IO) Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> IO Bool
doesFileExist (FilePath -> StateT PlotState (ReaderT RuntimeEnv IO) Bool)
-> FilePath -> StateT PlotState (ReaderT RuntimeEnv IO) Bool
forall a b. (a -> b) -> a -> b
$ FilePath
target
  ScriptResult
result <-
    if Bool
fileAlreadyExists
      then ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a. Monad m => a -> m a
return ScriptResult
ScriptSuccess
      else FigureSpec -> PlotM ScriptResult
runTempScript FigureSpec
spec

  ScriptResult -> StateT PlotState (ReaderT RuntimeEnv IO) ()
logScriptResult ScriptResult
result

  case ScriptResult
result of
    ScriptResult
ScriptSuccess -> FigureSpec -> StateT PlotState (ReaderT RuntimeEnv IO) ()
writeSource FigureSpec
spec StateT PlotState (ReaderT RuntimeEnv IO) ()
-> PlotM ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a. Monad m => a -> m a
return ScriptResult
ScriptSuccess
    ScriptResult
other -> ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a. Monad m => a -> m a
return ScriptResult
other
  where
    logScriptResult :: ScriptResult -> StateT PlotState (ReaderT RuntimeEnv IO) ()
logScriptResult ScriptResult
ScriptSuccess = () -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    logScriptResult ScriptResult
r = Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
err (Text -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> (ScriptResult -> Text)
-> ScriptResult
-> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> Text
pack (FilePath -> Text)
-> (ScriptResult -> FilePath) -> ScriptResult -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ScriptResult -> FilePath
forall a. Show a => a -> FilePath
show (ScriptResult -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> ScriptResult -> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ ScriptResult
r

-- | Possible result of running a script

data ScriptResult
  = ScriptSuccess
  | ScriptChecksFailed Text -- Message

  | ScriptFailure Text Int -- Command and exit code


instance Show ScriptResult where
  show :: ScriptResult -> FilePath
show ScriptResult
ScriptSuccess = FilePath
"Script success."
  show (ScriptChecksFailed Text
msg) = Text -> FilePath
unpack (Text -> FilePath) -> Text -> FilePath
forall a b. (a -> b) -> a -> b
$ Text
"Script checks failed: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
msg
  show (ScriptFailure Text
msg Int
ec) = [FilePath] -> FilePath
forall a. Monoid a => [a] -> a
mconcat [FilePath
"Script failed with exit code ", Int -> FilePath
forall a. Show a => a -> FilePath
show Int
ec, FilePath
" and the following message: ", Text -> FilePath
unpack Text
msg]

-- Run script as described by the spec

-- Checks are performed, according to the renderer

-- Note that stdout from the script is suppressed, but not

-- stderr.

runTempScript :: FigureSpec -> PlotM ScriptResult
runTempScript :: FigureSpec -> PlotM ScriptResult
runTempScript spec :: FigureSpec
spec@FigureSpec {Bool
Int
FilePath
[FilePath]
[(Text, Text)]
Attr
Text
Renderer
SaveFormat
blockAttrs :: FigureSpec -> Attr
extraAttrs :: FigureSpec -> [(Text, Text)]
dependencies :: FigureSpec -> [FilePath]
dpi :: FigureSpec -> Int
directory :: FigureSpec -> FilePath
saveFormat :: FigureSpec -> SaveFormat
script :: FigureSpec -> Text
withSource :: FigureSpec -> Bool
caption :: FigureSpec -> Text
renderer_ :: FigureSpec -> Renderer
blockAttrs :: Attr
extraAttrs :: [(Text, Text)]
dependencies :: [FilePath]
dpi :: Int
directory :: FilePath
saveFormat :: SaveFormat
script :: Text
withSource :: Bool
caption :: Text
renderer_ :: Renderer
..} = do
  let checks :: [Text -> CheckResult]
checks = Renderer -> [Text -> CheckResult]
rendererChecks Renderer
renderer_
      checkResult :: CheckResult
checkResult = [CheckResult] -> CheckResult
forall a. Monoid a => [a] -> a
mconcat ([CheckResult] -> CheckResult) -> [CheckResult] -> CheckResult
forall a b. (a -> b) -> a -> b
$ [Text -> CheckResult]
checks [Text -> CheckResult] -> [Text] -> [CheckResult]
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> [Text
script]
  case CheckResult
checkResult of
    CheckFailed Text
msg -> ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a. Monad m => a -> m a
return (ScriptResult -> PlotM ScriptResult)
-> ScriptResult -> PlotM ScriptResult
forall a b. (a -> b) -> a -> b
$ Text -> ScriptResult
ScriptChecksFailed Text
msg
    CheckResult
CheckPassed -> do
      FilePath
scriptPath <- FigureSpec -> PlotM FilePath
tempScriptPath FigureSpec
spec
      FilePath
target <- FigureSpec -> PlotM FilePath
figurePath FigureSpec
spec

      let scriptWithCapture :: Text
scriptWithCapture = Renderer -> FigureSpec -> FilePath -> Text
rendererCapture Renderer
renderer_ FigureSpec
spec FilePath
target

      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 -> Text -> IO ()
T.writeFile FilePath
scriptPath Text
scriptWithCapture
      let outputSpec :: OutputSpec
outputSpec =
            OutputSpec :: FigureSpec -> FilePath -> FilePath -> OutputSpec
OutputSpec
              { oFigureSpec :: FigureSpec
oFigureSpec = FigureSpec
spec,
                oScriptPath :: FilePath
oScriptPath = FilePath
scriptPath,
                oFigurePath :: FilePath
oFigurePath = FilePath
target
              }
      let command_ :: Text
command_ = Renderer -> OutputSpec -> Text
rendererCommand Renderer
renderer_ OutputSpec
outputSpec

      -- Change the PATH environment variable so the appropriate executable is

      -- found first

      let (Executable FilePath
exedir Text
_) = Renderer -> Executable
rendererExe Renderer
renderer_
      FilePath -> PlotM ScriptResult -> PlotM ScriptResult
forall a. FilePath -> PlotM a -> PlotM a
withPrependedPath FilePath
exedir (PlotM ScriptResult -> PlotM ScriptResult)
-> PlotM ScriptResult -> PlotM ScriptResult
forall a b. (a -> b) -> a -> b
$ do
        -- It is important that the CWD be inherited from the

        -- parent process. See #2.

        FilePath
cwd <- (RuntimeEnv -> FilePath) -> PlotM FilePath
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks RuntimeEnv -> FilePath
envCWD
        (ExitCode
ec, Text
_) <- FilePath -> Text -> PlotM (ExitCode, Text)
runCommand FilePath
cwd Text
command_
        case ExitCode
ec of
          ExitCode
ExitSuccess -> ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a. Monad m => a -> m a
return ScriptResult
ScriptSuccess
          ExitFailure Int
code -> ScriptResult -> PlotM ScriptResult
forall (m :: * -> *) a. Monad m => a -> m a
return (ScriptResult -> PlotM ScriptResult)
-> ScriptResult -> PlotM ScriptResult
forall a b. (a -> b) -> a -> b
$ Text -> Int -> ScriptResult
ScriptFailure Text
command_ Int
code

-- | Determine the temp script path from Figure specifications

-- Note that for certain renderers, the appropriate file extension

-- is important.

tempScriptPath :: FigureSpec -> PlotM FilePath
tempScriptPath :: FigureSpec -> PlotM FilePath
tempScriptPath FigureSpec {Bool
Int
FilePath
[FilePath]
[(Text, Text)]
Attr
Text
Renderer
SaveFormat
blockAttrs :: Attr
extraAttrs :: [(Text, Text)]
dependencies :: [FilePath]
dpi :: Int
directory :: FilePath
saveFormat :: SaveFormat
script :: Text
withSource :: Bool
caption :: Text
renderer_ :: Renderer
blockAttrs :: FigureSpec -> Attr
extraAttrs :: FigureSpec -> [(Text, Text)]
dependencies :: FigureSpec -> [FilePath]
dpi :: FigureSpec -> Int
directory :: FigureSpec -> FilePath
saveFormat :: FigureSpec -> SaveFormat
script :: FigureSpec -> Text
withSource :: FigureSpec -> Bool
caption :: FigureSpec -> Text
renderer_ :: FigureSpec -> Renderer
..} = do
  let ext :: FilePath
ext = Renderer -> FilePath
rendererScriptExtension Renderer
renderer_
  -- MATLAB will refuse to process files that don't start with

  -- a letter

  -- Note that this hash is only so that we are running scripts from unique

  -- file names; it does NOT determine whether this figure should

  -- be rendered or not.

  let hashedPath :: FilePath
hashedPath = FilePath
"pandocplot" FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> (Int -> FilePath
forall a. Show a => a -> FilePath
show (Int -> FilePath) -> (Text -> Int) -> Text -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Int
forall a. Num a => a -> a
abs (Int -> Int) -> (Text -> Int) -> Text -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Int
forall a. Hashable a => a -> Int
hash (Text -> FilePath) -> Text -> FilePath
forall a b. (a -> b) -> a -> b
$ Text
script) FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
ext
  IO FilePath -> PlotM FilePath
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO FilePath -> PlotM FilePath) -> IO FilePath -> PlotM FilePath
forall a b. (a -> b) -> a -> b
$ (FilePath -> FilePath -> FilePath
</> FilePath
hashedPath) (FilePath -> FilePath) -> IO FilePath -> IO FilePath
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO FilePath
getTemporaryDirectory

-- | Determine the path to the source code that generated the figure.

-- To ensure that the source code path is distinguished from HTML figures, we use the extension .src.html.

sourceCodePath :: FigureSpec -> PlotM FilePath
sourceCodePath :: FigureSpec -> PlotM FilePath
sourceCodePath = (FilePath -> FilePath) -> PlotM FilePath -> PlotM FilePath
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (FilePath -> FilePath
normalise (FilePath -> FilePath)
-> (FilePath -> FilePath) -> FilePath -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (FilePath -> FilePath -> FilePath)
-> FilePath -> FilePath -> FilePath
forall a b c. (a -> b -> c) -> b -> a -> c
flip FilePath -> FilePath -> FilePath
replaceExtension FilePath
".src.html") (PlotM FilePath -> PlotM FilePath)
-> (FigureSpec -> PlotM FilePath) -> FigureSpec -> PlotM FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FigureSpec -> PlotM FilePath
figurePath

-- | Hash of the content of a @FigureSpec@. Note that unlike usual hashes,

-- two @FigureSpec@ with the same @figureContentHash@ does not mean that they are equal!

--

-- Not all parts of a FigureSpec are related to running code.

-- For example, changing the caption should not require running the figure again.

figureContentHash :: FigureSpec -> PlotM Word
figureContentHash :: FigureSpec -> PlotM Word
figureContentHash FigureSpec {Bool
Int
FilePath
[FilePath]
[(Text, Text)]
Attr
Text
Renderer
SaveFormat
blockAttrs :: Attr
extraAttrs :: [(Text, Text)]
dependencies :: [FilePath]
dpi :: Int
directory :: FilePath
saveFormat :: SaveFormat
script :: Text
withSource :: Bool
caption :: Text
renderer_ :: Renderer
blockAttrs :: FigureSpec -> Attr
extraAttrs :: FigureSpec -> [(Text, Text)]
dependencies :: FigureSpec -> [FilePath]
dpi :: FigureSpec -> Int
directory :: FigureSpec -> FilePath
saveFormat :: FigureSpec -> SaveFormat
script :: FigureSpec -> Text
withSource :: FigureSpec -> Bool
caption :: FigureSpec -> Text
renderer_ :: FigureSpec -> Renderer
..} = do
  [Word]
dependenciesHash <- [PlotM Word] -> StateT PlotState (ReaderT RuntimeEnv IO) [Word]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([PlotM Word] -> StateT PlotState (ReaderT RuntimeEnv IO) [Word])
-> [PlotM Word] -> StateT PlotState (ReaderT RuntimeEnv IO) [Word]
forall a b. (a -> b) -> a -> b
$ FilePath -> PlotM Word
fileHash (FilePath -> PlotM Word) -> [FilePath] -> [PlotM Word]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [FilePath]
dependencies
  -- hash looks strange because instances only exist for 7-tuples or less

  Word -> PlotM Word
forall (m :: * -> *) a. Monad m => a -> m a
return (Word -> PlotM Word) -> Word -> PlotM Word
forall a b. (a -> b) -> a -> b
$
    Int -> Word
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Word) -> Int -> Word
forall a b. (a -> b) -> a -> b
$
      ((Int, Text, Int, FilePath),
 (Int, [Word], [(Text, Text)], FilePath))
-> Int
forall a. Hashable a => a -> Int
hash
        ( ( Toolkit -> Int
forall a. Enum a => a -> Int
fromEnum (Renderer -> Toolkit
rendererToolkit Renderer
renderer_),
            Text
script,
            SaveFormat -> Int
forall a. Enum a => a -> Int
fromEnum SaveFormat
saveFormat,
            FilePath
directory
          ),
          ( Int
dpi,
            [Word]
dependenciesHash,
            [(Text, Text)]
extraAttrs,
            Version -> FilePath
forall a. Show a => a -> FilePath
show Version
version -- Included version because capture

          ) -- scripts may change between releases

        )

-- | Determine the path a figure should have.

-- The path for this file is unique to the content of the figure,

-- so that @figurePath@ can be used to determine whether a figure should

-- be rendered again or not.

figurePath :: FigureSpec -> PlotM FilePath
figurePath :: FigureSpec -> PlotM FilePath
figurePath FigureSpec
spec = do
  Word
fh <- FigureSpec -> PlotM Word
figureContentHash FigureSpec
spec
  let ext :: FilePath
ext = SaveFormat -> FilePath
extension (SaveFormat -> FilePath)
-> (FigureSpec -> SaveFormat) -> FigureSpec -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FigureSpec -> SaveFormat
saveFormat (FigureSpec -> FilePath) -> FigureSpec -> FilePath
forall a b. (a -> b) -> a -> b
$ FigureSpec
spec
      stem :: FilePath
stem = (FilePath -> FilePath -> FilePath)
-> FilePath -> FilePath -> FilePath
forall a b c. (a -> b -> c) -> b -> a -> c
flip FilePath -> FilePath -> FilePath
addExtension FilePath
ext (FilePath -> FilePath) -> (Word -> FilePath) -> Word -> FilePath
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word -> FilePath
forall a. Show a => a -> FilePath
show (Word -> FilePath) -> Word -> FilePath
forall a b. (a -> b) -> a -> b
$ Word
fh
  FilePath -> PlotM FilePath
forall (m :: * -> *) a. Monad m => a -> m a
return (FilePath -> PlotM FilePath) -> FilePath -> PlotM FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath
normalise (FilePath -> FilePath) -> FilePath -> FilePath
forall a b. (a -> b) -> a -> b
$ FigureSpec -> FilePath
directory FigureSpec
spec FilePath -> FilePath -> FilePath
</> FilePath
stem

-- | Write the source code of a figure to an HTML file with appropriate syntax highlighting.

writeSource :: FigureSpec -> PlotM ()
writeSource :: FigureSpec -> StateT PlotState (ReaderT RuntimeEnv IO) ()
writeSource FigureSpec
spec = do
  let rdr :: Renderer
rdr = FigureSpec -> Renderer
renderer_ FigureSpec
spec
      language :: Text
language = Renderer -> Text
rendererLanguage Renderer
rdr
  FilePath
scp <- FigureSpec -> PlotM FilePath
sourceCodePath FigureSpec
spec
  let doc :: Pandoc
doc = Meta -> [Block] -> Pandoc
Pandoc Meta
forall a. Monoid a => a
mempty [Attr -> Text -> Block
CodeBlock (Text
forall a. Monoid a => a
mempty, [Text
language], [(Text, Text)]
forall a. Monoid a => a
mempty) (FigureSpec -> Text
script FigureSpec
spec)]
      renderSource :: Template Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
renderSource = \Template Text
template -> do
        let opts :: WriterOptions
opts = WriterOptions
forall a. Default a => a
def {writerTemplate :: Maybe (Template Text)
writerTemplate = Template Text -> Maybe (Template Text)
forall a. a -> Maybe a
Just Template Text
template}
            -- Note that making the document self-contained is absolutely required so that the CSS for

            -- syntax highlighting is included directly in the document.

            t :: Text
t = (PandocError -> Text)
-> (Text -> Text) -> Either PandocError Text -> Text
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Text -> PandocError -> Text
forall a b. a -> b -> a
const Text
forall a. Monoid a => a
mempty) Text -> Text
forall a. a -> a
id (Either PandocError Text -> Text)
-> Either PandocError Text -> Text
forall a b. (a -> b) -> a -> b
$ PandocPure Text -> Either PandocError Text
forall a. PandocPure a -> Either PandocError a
runPure (WriterOptions -> Pandoc -> PandocPure Text
forall (m :: * -> *).
PandocMonad m =>
WriterOptions -> Pandoc -> m Text
writeHtml5String WriterOptions
opts Pandoc
doc PandocPure Text -> (Text -> PandocPure Text) -> PandocPure Text
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> PandocPure Text
forall (m :: * -> *). PandocMonad m => Text -> m Text
makeSelfContained)
        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 -> Text -> IO ()
T.writeFile FilePath
scp Text
t

  (FilePath -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> (Template Text -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> Either FilePath (Template Text)
-> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
err (Text -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> (FilePath -> Text)
-> FilePath
-> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> Text
pack) Template Text -> StateT PlotState (ReaderT RuntimeEnv IO) ()
renderSource (Either FilePath (Template Text)
 -> StateT PlotState (ReaderT RuntimeEnv IO) ())
-> Either FilePath (Template Text)
-> StateT PlotState (ReaderT RuntimeEnv IO) ()
forall a b. (a -> b) -> a -> b
$ Identity (Either FilePath (Template Text))
-> Either FilePath (Template Text)
forall a. Identity a -> a
runIdentity (Identity (Either FilePath (Template Text))
 -> Either FilePath (Template Text))
-> Identity (Either FilePath (Template Text))
-> Either FilePath (Template Text)
forall a b. (a -> b) -> a -> b
$ FilePath -> Text -> Identity (Either FilePath (Template Text))
forall (m :: * -> *) a.
(TemplateMonad m, TemplateTarget a) =>
FilePath -> Text -> m (Either FilePath (Template a))
compileTemplate FilePath
forall a. Monoid a => a
mempty Text
sourceTemplate

sourceTemplate :: Text
sourceTemplate :: Text
sourceTemplate = FilePath -> Text
pack $(FilePath
FilePath -> FilePath
forall a. IsString a => FilePath -> a
sourceTemplate_)