{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE OverloadedStrings   #-}
{- |
   Module      : Text.Pandoc.Readers.Custom
   Copyright   : Copyright (C) 2021-2022 John MacFarlane
   License     : GNU GPL, version 2 or above

   Maintainer  : John MacFarlane <jgm@berkeley.edu>
   Stability   : alpha
   Portability : portable

Supports custom parsers written in Lua which produce a Pandoc AST.
-}
module Text.Pandoc.Readers.Custom ( readCustom ) where
import Control.Exception
import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO)
import Data.Maybe (fromMaybe)
import HsLua as Lua hiding (Operation (Div))
import Text.Pandoc.Definition
import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback, report)
import Text.Pandoc.Logging
import Text.Pandoc.Lua (Global (..), runLua, setGlobals)
import Text.Pandoc.Lua.PandocLua
import Text.Pandoc.Lua.Marshal.Pandoc (peekPandoc)
import Text.Pandoc.Options
import Text.Pandoc.Sources (ToSources(..), sourcesToText)
import qualified Data.Text as T

-- | Convert custom markup to Pandoc.
readCustom :: (PandocMonad m, MonadIO m, ToSources s)
            => FilePath -> ReaderOptions -> s -> m Pandoc
readCustom :: forall (m :: * -> *) s.
(PandocMonad m, MonadIO m, ToSources s) =>
FilePath -> ReaderOptions -> s -> m Pandoc
readCustom FilePath
luaFile ReaderOptions
opts s
srcs = do
  let globals :: [Global]
globals = [ FilePath -> Global
PANDOC_SCRIPT_FILE FilePath
luaFile ]
  FilePath
luaFile' <- forall a. a -> Maybe a -> a
fromMaybe FilePath
luaFile forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (m :: * -> *).
PandocMonad m =>
FilePath -> FilePath -> m (Maybe FilePath)
findFileWithDataFallback FilePath
"readers" FilePath
luaFile
  Either PandocError Pandoc
res <- forall (m :: * -> *) a.
(PandocMonad m, MonadIO m) =>
LuaE PandocError a -> m (Either PandocError a)
runLua forall a b. (a -> b) -> a -> b
$ do
    [Global] -> LuaE PandocError ()
setGlobals [Global]
globals
    Status
stat <- forall e. FilePath -> LuaE e Status
dofileTrace FilePath
luaFile'
    -- check for error in lua script (later we'll change the return type
    -- to handle this more gracefully):
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Status
stat forall a. Eq a => a -> a -> Bool
/= Status
Lua.OK)
      forall e a. LuaError e => LuaE e a
Lua.throwErrorAsException
    LuaE PandocError Pandoc
parseCustom
  case Either PandocError Pandoc
res of
    Left PandocError
msg -> forall a e. Exception e => e -> a
throw PandocError
msg
    Right Pandoc
doc -> forall (m :: * -> *) a. Monad m => a -> m a
return Pandoc
doc
 where
  parseCustom :: LuaE PandocError Pandoc
parseCustom = do
    let input :: Sources
input = forall a. ToSources a => a -> Sources
toSources s
srcs
    forall e. LuaError e => Name -> LuaE e Type
getglobal Name
"Reader"
    forall a e. (Pushable a, LuaError e) => a -> LuaE e ()
push Sources
input
    forall a e. (Pushable a, LuaError e) => a -> LuaE e ()
push ReaderOptions
opts
    forall e. NumArgs -> NumResults -> LuaE e Status
pcallTrace NumArgs
2 NumResults
1 forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
      Status
OK -> forall e a. LuaError e => Peek e a -> LuaE e a
forcePeek forall a b. (a -> b) -> a -> b
$ forall e. LuaError e => Peeker e Pandoc
peekPandoc StackIndex
top
      Status
ErrRun -> do
        -- Caught a runtime error. Check if parsing might work if we
        -- pass a string instead of a Sources list, then retry.
        forall e a. Peek e a -> LuaE e (Result a)
runPeek (forall e. Peeker e Text
peekText StackIndex
top) forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
          Failure {} ->
            -- not a string error object. Bail!
            forall e a. LuaError e => LuaE e a
throwErrorAsException
          Success Text
errmsg -> do
            if Text
"string expected, got pandoc Sources" Text -> Text -> Bool
`T.isInfixOf` Text
errmsg
              then do
                forall e. Int -> LuaE e ()
pop Int
1
                ()
_ <- forall a. PandocLua a -> LuaE PandocError a
unPandocLua forall a b. (a -> b) -> a -> b
$ do
                  forall (m :: * -> *). PandocMonad m => LogMessage -> m ()
report forall a b. (a -> b) -> a -> b
$ Text -> Text -> LogMessage
Deprecated Text
"old Reader function signature" forall a b. (a -> b) -> a -> b
$
                    [Text] -> Text
T.unlines
                    [ Text
"Reader functions should accept a sources list; "
                    , Text
"functions expecting `string` input are deprecated. "
                    , Text
"Use `tostring` to convert the first argument to a "
                    , Text
"string."
                    ]
                forall e. LuaError e => Name -> LuaE e Type
getglobal Name
"Reader"
                forall a e. (Pushable a, LuaError e) => a -> LuaE e ()
push forall a b. (a -> b) -> a -> b
$ Sources -> Text
sourcesToText Sources
input  -- push sources as string
                forall a e. (Pushable a, LuaError e) => a -> LuaE e ()
push ReaderOptions
opts
                forall e. LuaError e => NumArgs -> NumResults -> LuaE e ()
callTrace NumArgs
2 NumResults
1
                forall e a. LuaError e => Peek e a -> LuaE e a
forcePeek forall a b. (a -> b) -> a -> b
$ forall e. LuaError e => Peeker e Pandoc
peekPandoc StackIndex
top
              else
                -- nothing we can do here
                forall e a. LuaError e => LuaE e a
throwErrorAsException
      Status
_ ->  -- not a runtime error, we won't be able to recover from that
        forall e a. LuaError e => LuaE e a
throwErrorAsException